Android 13 截屏流程

前言

代码贴的比较多,请耐心看;整个截屏流程是详细的,其他的或许就没分析了。

一般截屏都是电源键+音量减键,而这些按键的处理都是在 PhoneWindowManager 中进行的,但在该类中有两个主要处理按键的方法:

  • interceptKeyBeforeQueueing():主要处理音量键、电源键(Power键)、耳机键等。
  • interceptKeyBeforeDispatching():处理一般性的按键和动作。

参数含义:

  • interactive:是否亮屏
  • KeyEvent.FLAG_FALLBACK:不被应用处理的按键事件或一些在键值映射中不被处理的事件(例:轨迹球事件等).

这里我们直接看 PhoneWindowManager#interceptKeyBeforeQueueing() 方法:

// PhoneWindowManager.java
    @Override    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    
           
     final int keyCode = event.getKeyCode();       
     final boolean down = event.getAction() == KeyEvent.ACTION\_DOWN;      
      boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG\_WAKE) != 0               
       || event.isWakeKey();       
        if (!mSystemBooted) {
    
               
         // 省略部分代码......           
         return 0;       
          }      
            // 省略部分代码......       
             // This could prevent some wrong state in multi-displays environment,       
              // the default display may turned off but interactive is true.        final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());      
                final boolean interactiveAndOn = interactive && isDefaultDisplayOn;       
                 if ((event.getFlags() & KeyEvent.FLAG\_FALLBACK) == 0) {
    
                // 这里面有·组合键处理,Android 13 与之前版本不一样            
                 // 在Android 13 有专门的组合键处理,可自行添加规则(即:组合键)           
                  handleKeyGesture(event, interactiveAndOn);     
                     }       
                      // 省略部分代码......       
                       switch (keyCode) {
    
             
                          // 省略部分代码......       
                           return result;   
                   }

上述代码里说的组合键添加,在 initKeyCombinationRules() 方法中,并在 PhoneWindowManager的 init() 方法中初始化。关于 initKeyCombinationRules() 方法,下文会有讲述。

下面接着看 PhoneWindowManager#handleKeyGesture() 方法:

// PhoneWindowManager.java
    private void handleKeyGesture(KeyEvent event, boolean interactive) {        
    // 在 if 判断中,调用组合键判断;      
      // 在将键事件发送到窗口之前,检查键事件是否可以被组合键规则拦截。       
       // 如果键事件可以触发任何活动规则,则返回 true,否则返回 false。       
        if (mKeyCombinationManager.interceptKey(event, interactive)) {            // handled by combo keys manager.            mSingleKeyGestureDetector.reset();          
          return;        
          }       
           if (event.getKeyCode() == KEYCODE\_POWER && event.getAction() == KeyEvent.ACTION\_DOWN) {           
            // 触发KEYCODE\_POWER 和 ACTION\_DOWN事件            mPowerKeyHandled = handleCameraGesture(event, interactive);            if (mPowerKeyHandled) {               
             // handled by camera gesture.                mSingleKeyGestureDetector.reset();              
               return;            
               }      
                 }        
                 mSingleKeyGestureDetector.interceptKey(event, interactive);  
                   }

这里我们主要看 mKeyCombinationManager.interceptKey(event, interactive) 方法就行了;

KeyCombinationManager#interceptKey():

// KeyCombinationManager.java
    boolean interceptKey(KeyEvent event, boolean interactive) {        synchronized (mLock) {          
      return interceptKeyLocked(event, interactive);      
        }   
         }   
          private boolean interceptKeyLocked(KeyEvent event, boolean interactive) {      
            final boolean down = event.getAction() == KeyEvent.ACTION\_DOWN;      
              final int keyCode = event.getKeyCode();     
                 final int count = mActiveRules.size();      
                   final long eventTime = event.getEventTime();     
                      if (interactive && down) {          
                        // 省略部分代码......           
                         if (mDownTimes.size() == 1) {              
                            // 省略部分代码......          
                              } else {               
                               // 如果规则已经触发则忽略.              
                                 if (mTriggeredRule != null) {                  
                                   return true;             
                                      }              
                                        // 发送给客户端之前的过度延迟。                forAllActiveRules((rule) -> {                  
                                          if (!rule.shouldInterceptKeys(mDownTimes)) {                       
                                           return false; 
                                                              }                 
                              
                
                 Log.v(TAG, "Performing combination rule : " + rule);                   
                // 主要是这个方法,会执行 execute() 方法,                   
               // 该方法是一个 抽象方法,会在添加组合键规则的地方实现;                    mHandler.post(rule::execute);                    mTriggeredRule = rule;                  
                 return true;              
                   });               
                    mActiveRules.clear();             
                       if (mTriggeredRule != null) {                    mActiveRules.add(mTriggeredRule);                 
                          return true;              
                            }         
                               }      
                                 } else {             
                                   // 省略部分代码......      
                                     }       
                                      return false;  
                                        }

这里我们看下组合键添加,及触发回调。

initKeyCombinationRules()

// PhoneWindowManager.java
    private void initKeyCombinationRules() {        mKeyCombinationManager = new KeyCombinationManager(mHandler);     
       final boolean screenshotChordEnabled = mContext.getResources().getBoolean(                com.android.internal.R.bool.config\_enableScreenshotChord);      
         if (screenshotChordEnabled) {            mKeyCombinationManager.addRule(                  
           // 截屏组合键的添加                    new TwoKeysCombinationRule(KEYCODE\_VOLUME\_DOWN, KEYCODE\_POWER) {                        
           // 触发组合键后回调                        
           @Override                        void execute() {                            mPowerKeyHandled = true;                         
              // 发消息准备屏幕截图                            interceptScreenshotChord(TAKE\_SCREENSHOT\_FULLSCREEN,                                    SCREENSHOT\_KEY\_CHORD, getScreenshotChordLongPressDelay());                       
               }                       
                // 取消                        @Override                        void cancel() {                           
                 cancelPendingScreenshotChordAction();                     
                    }            
                            });   
                                 }     
                                    // 省略部分代码......  
                                     }

上面通过 handle 发了一个消息,将会调用 handleScreenShot() 方法,处理截屏:

PhoneWindowManager# handleScreenShot()

// PhoneWindowManager.java
    private void handleScreenShot(@WindowManager.ScreenshotType int type,            @WindowManager.ScreenshotSource int source) {      
      // 回调到 DisplayPolicy.java        mDefaultDisplayPolicy.takeScreenshot(type, source);    }

DisplayPolicy#takeScreenshot()

// DisplayPolicy.java
    // 请求截取屏幕截图    public void takeScreenshot(int screenshotType, int source) {       
     if (mScreenshotHelper != null) {     
            mScreenshotHelper.takeScreenshot(screenshotType,    
             getStatusBar() != null && getStatusBar().isVisible(),                      	getNavigationBar() != null && getNavigationBar().isVisible(),  
           source, mHandler, null /\* completionConsumer \*/);     
              }   
               }

继续往下看 ScreenshotHelper#takeScreenshot()

// ScreenshotHelper.java
    public void takeScreenshot(final int screenshotType, final boolean hasStatus,            final boolean hasNav, int source, @NonNull Handler handler,            @Nullable Consumer<Uri> completionConsumer) {        // 截图请求        ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);        takeScreenshot(screenshotType, SCREENSHOT\_TIMEOUT\_MS, handler, screenshotRequest,                completionConsumer);    }    //到了 Binder调用环节, 此为客户端, 服务端为SystemUI中的 TakeScreenshotService    private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,                ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {            synchronized (mScreenshotLock) {                final Runnable mScreenshotTimeout = () -> {                    synchronized (mScreenshotLock) {                        if (mScreenshotConnection != null) {                            // 在获取屏幕截图捕获响应之前超时                            Log.e(TAG, "Timed out before getting screenshot capture response");                            // 重置连接                            resetConnection();                            // 通知截屏错误                            notifyScreenshotError();                        }                    }                    if (completionConsumer != null) {                        completionConsumer.accept(null);                    }                };                Message msg = Message.obtain(null, screenshotType, screenshotRequest);                Handler h = new Handler(handler.getLooper()) {                    @Override                    public void handleMessage(Message msg) {                        switch (msg.what) {                            case SCREENSHOT\_MSG\_URI:                                if (completionConsumer != null) {                                    completionConsumer.accept((Uri) msg.obj);                                }                                handler.removeCallbacks(mScreenshotTimeout);                                break;                            case SCREENSHOT\_MSG\_PROCESS\_COMPLETE:                                synchronized (mScreenshotLock) {                                    resetConnection();                                }                                break;                        }                    }                };                msg.replyTo = new Messenger(h);                if (mScreenshotConnection == null || mScreenshotService == null) {                    // 一个标准的Service连接                    // config\_screenshotServiceComponent == com.android.systemui/com.android.systemui.screenshot.TakeScreenshotService                    final ComponentName serviceComponent = ComponentName.unflattenFromString(                            mContext.getResources().getString(                                    com.android.internal.R.string.config\_screenshotServiceComponent));                    final Intent serviceIntent = new Intent();                    serviceIntent.setComponent(serviceComponent);                    ServiceConnection conn = new ServiceConnection() {                        @Override                        // 当Service连接成功之后                        public void onServiceConnected(ComponentName name, IBinder service) {                            synchronized (mScreenshotLock) {                                if (mScreenshotConnection != this) {                                    return;                                }                                mScreenshotService = service;                                Messenger messenger = new Messenger(mScreenshotService);                                try {                                    // 进程通信,发送请求截图消息                                    messenger.send(msg);                                } catch (RemoteException e) {                                    Log.e(TAG, "Couldn't take screenshot: " + e);                                    if (completionConsumer != null) {                                        completionConsumer.accept(null);                                    }                                }                            }                        }                        @Override                        // 当Service断开连接时                        public void onServiceDisconnected(ComponentName name) {                            synchronized (mScreenshotLock) {                                if (mScreenshotConnection != null) {                                    resetConnection();                                    // only log an error if we're still within the timeout period                                    if (handler.hasCallbacks(mScreenshotTimeout)) {                                        handler.removeCallbacks(mScreenshotTimeout);                                        notifyScreenshotError();                                    }                                }                            }                        }                    };                    // 绑定服务 TakeScreenshotService;                    // 绑定成功为true,不成功则发绑定超时消息                    if (mContext.bindServiceAsUser(serviceIntent, conn,                            Context.BIND\_AUTO\_CREATE | Context.BIND\_FOREGROUND\_SERVICE,                            UserHandle.CURRENT)) {                        mScreenshotConnection = conn;                        handler.postDelayed(mScreenshotTimeout, timeoutMs);                    }                } else {                    // 如果已经连接则直接发送Message                    Messenger messenger = new Messenger(mScreenshotService);                    try {                        messenger.send(msg);                    } catch (RemoteException e) {                        Log.e(TAG, "Couldn't take screenshot: " + e);                        if (completionConsumer != null) {                            completionConsumer.accept(null);                        }                    }                    handler.postDelayed(mScreenshotTimeout, timeoutMs);                }            }    }

客户端通过向服务端发送 message 来将截屏任务交给 service,由 service 处理后面的操作。

// TakeScreenshotService.java
    // 通过 Binder (Messenger) 响应传入消息    @MainThread    private boolean handleMessage(Message msg) {        // 获取客户端传的 Messenger 对象        final Messenger replyTo = msg.replyTo;        // reportUri(replyTo, uri)  方法,Messenger 双向通信,        // 在服务端用远程客户端的 Messenger 对象给客户端发送信息        final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri);        RequestCallback requestCallback = new RequestCallbackImpl(replyTo);        // 如果此用户的存储空间被锁定,我们就没有地方可以存储屏幕截图,        // 因此请跳过截屏,而不是显示误导性的动画和错误通知。        if (!mUserManager.isUserUnlocked()) {            mNotificationsController.notifyScreenshotError(                    R.string.screenshot\_failed\_to\_save\_user\_locked\_text);            requestCallback.reportError();            return true;        }        if (mDevicePolicyManager.getScreenCaptureDisabled(null, UserHandle.USER\_ALL)) {            mBgExecutor.execute(() -> {                // 跳过屏幕截图,因为 IT 管理员已禁用设备上的“+”屏幕截图                String blockedByAdminText = mDevicePolicyManager.getResources().getString(                        SCREENSHOT\_BLOCKED\_BY\_ADMIN,                        () -> mContext.getString(R.string.screenshot\_blocked\_by\_admin));                mHandler.post(() ->                        Toast.makeText(mContext, blockedByAdminText, Toast.LENGTH\_SHORT).show());                requestCallback.reportError();            });            return true;        }        ScreenshotHelper.ScreenshotRequest screenshotRequest =                (ScreenshotHelper.ScreenshotRequest) msg.obj;        ComponentName topComponent = screenshotRequest.getTopComponent();        mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()), 0,                topComponent == null ? "" : topComponent.getPackageName());        switch (msg.what) {            case WindowManager.TAKE\_SCREENSHOT\_FULLSCREEN:                // 全屏截图                mScreenshot.takeScreenshotFullscreen(topComponent, uriConsumer, requestCallback);                break;            case WindowManager.TAKE\_SCREENSHOT\_SELECTED\_REGION:                // 截取所选区域                mScreenshot.takeScreenshotPartial(topComponent, uriConsumer, requestCallback);                break;            case WindowManager.TAKE\_SCREENSHOT\_PROVIDED\_IMAGE:                // 截取提供的图像                Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(                        screenshotRequest.getBitmapBundle());                Rect screenBounds = screenshotRequest.getBoundsInScreen();                Insets insets = screenshotRequest.getInsets();                int taskId = screenshotRequest.getTaskId();                int userId = screenshotRequest.getUserId();                if (screenshot == null) {                    // 从屏幕截图消息中获得空位图                    mNotificationsController.notifyScreenshotError(                            R.string.screenshot\_failed\_to\_capture\_text);                    requestCallback.reportError();                } else {                    mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,                            taskId, userId, topComponent, uriConsumer, requestCallback);                }                break;            default:                // 无效的屏幕截图选项                Log.w(TAG, "Invalid screenshot option: " + msg.what);                return false;        }        return true;    }

TakeScreenshotService 调用 ScreenshotController.java 的 takeScreenshotFullscreen();

ScreenshotController#takeScreenshotFullscreen()

// ScreenshotController.java
    void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,            RequestCallback requestCallback) {        // 断言,是主线程则继续执行,不是则抛出异常。        Assert.isMainThread();        mCurrentRequestCallback = requestCallback;        DisplayMetrics displayMetrics = new DisplayMetrics();        getDefaultDisplay().getRealMetrics(displayMetrics);        takeScreenshotInternal(                topComponent, finisher,                new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));    }    // 获取当前显示的屏幕截图并显示动画。    private void takeScreenshotInternal(ComponentName topComponent, Consumer<Uri> finisher,            Rect crop) {        mScreenshotTakenInPortrait =                mContext.getResources().getConfiguration().orientation == ORIENTATION\_PORTRAIT;        // 复制输入 Rect,因为 SurfaceControl.screenshot 可以改变它        Rect screenRect = new Rect(crop);        // 截图        Bitmap screenshot = captureScreenshot(crop);        // 屏幕截图位图为空        if (screenshot == null) {            mNotificationsController.notifyScreenshotError(                    R.string.screenshot\_failed\_to\_capture\_text);            if (mCurrentRequestCallback != null) {                mCurrentRequestCallback.reportError();            }            return;        }        // 保存截图        saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, topComponent, true);        mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT\_ACTION),                ClipboardOverlayController.SELF\_PERMISSION);    }

如何截图的呢?这里我们看 captureScreenshot() 方法;

ScreenshotController#captureScreenshot()

// ScreenshotController.java
    private Bitmap captureScreenshot(Rect crop) {        int width = crop.width();        int height = crop.height();        Bitmap screenshot = null;        final Display display = getDefaultDisplay();        final DisplayAddress address = display.getAddress();        if (!(address instanceof DisplayAddress.Physical)) {            Log.e(TAG, "Skipping Screenshot - Default display does not have a physical address: "                    + display);        } else {            final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;            final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(                    physicalAddress.getPhysicalDisplayId());            // 捕获参数            final SurfaceControl.DisplayCaptureArgs captureArgs =                    new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)                            .setSourceCrop(crop)                            .setSize(width, height)                            .build();            // 屏幕截图硬件缓存            final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =                    SurfaceControl.captureDisplay(captureArgs);            // 截图缓存            screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();        }        return screenshot;    }

上面是捕获图片的过程,里面到底如何捕获的。这点我目前还没弄清。

接着拿到截屏的 Bitmap 后就可以进行图片保存,显示等等一些操作。

接着看 ScreenshotController#saveScreenshot()

// ScreenshotController.java
    private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,            Insets screenInsets, ComponentName topComponent, boolean showFlash) {        withWindowAttached(() ->                mScreenshotView.announceForAccessibility(                        mContext.getResources().getString(R.string.screenshot\_saving\_title)));        // 判断缩略图的那个窗口是否已附加上去了。        // ScreenshotView :附件窗口的布局;有:略缩图,编辑按钮、长截屏按钮等一些其他布局。        if (mScreenshotView.isAttachedToWindow()) {            // if we didn't already dismiss for another reason            if (!mScreenshotView.isDismissing()) {                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT\_REENTERED, 0, mPackageName);            }            if (DEBUG\_WINDOW) {                Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "                        + "(dismissing=" + mScreenshotView.isDismissing() + ")");            }            // 视图的状态重置,例如:可见性、透明度等。            mScreenshotView.reset();        }        // 省略部分代码......        // 在工作线程中保存屏幕截图。        saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady,                this::showUiOnQuickShareActionReady);        // The window is focusable by default        setWindowFocusable(true);        // Wait until this window is attached to request because it is        // the reference used to locate the target window (below).        // 这个方法没看明白。        withWindowAttached(() -> {            // 请求滚动捕获,捕获长截屏的。            requestScrollCapture();            mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(                    new ViewRootImpl.ActivityConfigCallback() {                        @Override                        public void onConfigurationChanged(Configuration overrideConfig,                                int newDisplayId) {                                  // 省略部分代码......                        }                        @Override                        public void requestCompatCameraControl(boolean showControl,                                boolean transformationApplied,                                ICompatCameraControlCallback callback) {                                // 省略部分代码......                        }                    });        });        // 创建附加窗口        attachWindow();        // 省略部分代码......        
        // 设置缩略图,ScreenBitmap 为所截的图片        mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);        // 将 ScreenshotView 添加到附加窗口        setContentView(mScreenshotView);        // 省略部分代码......    }

截屏布局 screenshot_static.xml

<com.android.systemui.screenshot.DraggableConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout\_width="match\_parent"    android:layout\_height="match\_parent">    <ImageView        android:id="@+id/actions\_container\_background"        android:visibility="gone"        android:layout\_height="0dp"        android:layout\_width="0dp"        android:elevation="4dp"        android:background="@drawable/action\_chip\_container\_background"        android:layout\_marginStart="@dimen/overlay\_action\_container\_margin\_horizontal"        app:layout\_constraintBottom\_toBottomOf="@+id/actions\_container"        app:layout\_constraintStart\_toStartOf="parent"        app:layout\_constraintTop\_toTopOf="@+id/actions\_container"        app:layout\_constraintEnd\_toEndOf="@+id/actions\_container"/>    <!-- 缩略图下方的几个按钮 -->    <HorizontalScrollView        android:id="@+id/actions\_container"        android:layout\_width="0dp"        android:layout\_height="wrap\_content"        android:layout\_marginEnd="@dimen/overlay\_action\_container\_margin\_horizontal"        android:layout\_marginBottom="4dp"        android:paddingEnd="@dimen/overlay\_action\_container\_padding\_right"        android:paddingVertical="@dimen/overlay\_action\_container\_padding\_vertical"        android:elevation="4dp"        android:scrollbars="none"        app:layout\_constraintHorizontal\_bias="0"        app:layout\_constraintWidth\_percent="1.0"        app:layout\_constraintWidth\_max="wrap"        app:layout\_constraintBottom\_toBottomOf="parent"        app:layout\_constraintStart\_toEndOf="@+id/screenshot\_preview\_border"        app:layout\_constraintEnd\_toEndOf="parent">        <LinearLayout            android:id="@+id/screenshot\_actions"            android:layout\_width="wrap\_content"            android:layout\_height="wrap\_content">            <include layout="@layout/overlay\_action\_chip"                     android:id="@+id/screenshot\_share\_chip"/>            <include layout="@layout/overlay\_action\_chip"                     android:id="@+id/screenshot\_edit\_chip"/>            <include layout="@layout/overlay\_action\_chip"                     android:id="@+id/screenshot\_scroll\_chip"                     android:visibility="gone" />        </LinearLayout>    </HorizontalScrollView>    <!-- 缩略图边框,使用 android:elevation="7dp" 属性,确定哪个覆盖在哪个上面,值大的布局显示在上方 -->    <View        android:id="@+id/screenshot\_preview\_border"        android:layout\_width="0dp"        android:layout\_height="0dp"        android:layout\_marginStart="@dimen/overlay\_offset\_x"        android:layout\_marginBottom="12dp"        android:elevation="7dp"        android:alpha="0"        android:background="@drawable/overlay\_border"        app:layout\_constraintStart\_toStartOf="parent"        app:layout\_constraintBottom\_toBottomOf="parent"        app:layout\_constraintEnd\_toEndOf="@id/screenshot\_preview\_end"        app:layout\_constraintTop\_toTopOf="@id/screenshot\_preview\_top"/>    <!-- constraintlayout 这种布局方式的,一个属性。 -->    <androidx.constraintlayout.widget.Barrier        android:id="@+id/screenshot\_preview\_end"        android:layout\_width="wrap\_content"        android:layout\_height="wrap\_content"        app:barrierMargin="@dimen/overlay\_border\_width"        app:barrierDirection="end"        app:constraint\_referenced\_ids="screenshot\_preview"/>    <androidx.constraintlayout.widget.Barrier        android:id="@+id/screenshot\_preview\_top"        android:layout\_width="wrap\_content"        android:layout\_height="wrap\_content"        app:barrierDirection="top"        app:barrierMargin="@dimen/overlay\_border\_width\_neg"        app:constraint\_referenced\_ids="screenshot\_preview"/>    <!-- 缩略图 -->    <ImageView        android:id="@+id/screenshot\_preview"        android:visibility="invisible"        android:layout\_width="@dimen/overlay\_x\_scale"        android:layout\_margin="@dimen/overlay\_border\_width"        android:layout\_height="wrap\_content"        android:layout\_gravity="center"        android:elevation="7dp"        android:contentDescription="@string/screenshot\_edit\_description"        android:scaleType="fitEnd"        android:background="@drawable/overlay\_preview\_background"        android:adjustViewBounds="true"        android:clickable="true"        app:layout\_constraintBottom\_toBottomOf="@id/screenshot\_preview\_border"        app:layout\_constraintStart\_toStartOf="@id/screenshot\_preview\_border"        app:layout\_constraintEnd\_toEndOf="@id/screenshot\_preview\_border"        app:layout\_constraintTop\_toTopOf="@id/screenshot\_preview\_border">    </ImageView>    <!--add by jingtao.guo TFBAAA-2325 添加截图"X"图标-->    <FrameLayout        android:id="@+id/screenshot\_dismiss\_button"        android:layout\_width="@dimen/overlay\_dismiss\_button\_tappable\_size"        android:layout\_height="@dimen/overlay\_dismiss\_button\_tappable\_size"        android:elevation="10dp"        app:layout\_constraintStart\_toEndOf="@id/screenshot\_preview"        app:layout\_constraintEnd\_toEndOf="@id/screenshot\_preview"        app:layout\_constraintTop\_toTopOf="@id/screenshot\_preview"        app:layout\_constraintBottom\_toTopOf="@id/screenshot\_preview"        android:contentDescription="@string/screenshot\_dismiss\_description">        <ImageView            android:id="@+id/screenshot\_dismiss\_image"            android:layout\_width="match\_parent"            android:layout\_height="match\_parent"            android:layout\_margin="@dimen/overlay\_dismiss\_button\_margin"            android:src="@drawable/overlay\_cancel"/>    </FrameLayout>    <ImageView        android:id="@+id/screenshot\_scrollable\_preview"        android:layout\_width="wrap\_content"        android:layout\_height="wrap\_content"        android:scaleType="matrix"        android:visibility="gone"        app:layout\_constraintStart\_toStartOf="@id/screenshot\_preview"        app:layout\_constraintTop\_toTopOf="@id/screenshot\_preview"        android:elevation="7dp"/>
</com.android.systemui.screenshot.DraggableConstraintLayout>

至此,全截屏流程就到此结束,saveScreenshotInWorkerThread() 这里不做分析。

下面分析长截屏:

在上述代码中,有讲到 requestScrollCapture(),请求滚动捕获,即长截屏。

ScreenshotController#requestScrollCapture()

// ScreenshotController.java
    private void requestScrollCapture() {        if (!allowLongScreenshots()) {            Log.d(TAG, "Long screenshots not supported on this device");            return;        }        mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());        if (mLastScrollCaptureRequest != null) {            mLastScrollCaptureRequest.cancel(true);        }        // 请求长截图捕获        final ListenableFuture<ScrollCaptureResponse> future =                mScrollCaptureClient.request(DEFAULT\_DISPLAY);        mLastScrollCaptureRequest = future;        mLastScrollCaptureRequest.addListener(() ->                onScrollCaptureResponseReady(future), mMainExecutor);    }

长截图捕获流程: mScrollCaptureClient.request() 请求捕获→mWindowManagerService.requestScrollCapture()→ViewRootImpl#requestScrollCapture()→ViewRootImpl#handleScrollCaptureRequest() 处理滚动捕获请求,拿到捕获目标。→ViewGroup#dispatchScrollCaptureSearch() 通过检查此视图,处理滚动捕获搜索请求,然后检查每个子视图。该隐藏的隐藏,设置视图偏移等等。

走完上述流程,才会继续往下执行;

接着看 ScreenshotController#onScrollCaptureResponseReady()

// ScreenshotController.java
    private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) {        try {            // 上次滚动捕获响应            if (mLastScrollCaptureResponse != null) {                mLastScrollCaptureResponse.close();                mLastScrollCaptureResponse = null;            }            // 长截屏响应,这和 网络请求中,响应头类似, response 里有很多的数据。            if (responseFuture != null) {                if (responseFuture.isCancelled()) {                    return;                }                // 将本次滚动捕获响应 设置成 滚动捕获响应                mLastScrollCaptureResponse = responseFuture.get();            } else {                Log.e(TAG, "onScrollCaptureResponseReady responseFuture is null!");            }            if (mLastScrollCaptureResponse != null && !mLastScrollCaptureResponse.isConnected()) {                // No connection means that the target window wasn't found                // or that it cannot support scroll capture.                Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " \["                        + mLastScrollCaptureResponse.getWindowTitle() + "\]");                return;            }            Log.d(TAG, "ScrollCapture: connected to window \["                    + mLastScrollCaptureResponse.getWindowTitle() + "\]");            // 滚动捕获响应,这和 网络请求中,响应头类似, response 里有很多的数据。            final ScrollCaptureResponse response = mLastScrollCaptureResponse;            // 截取更多内容按钮,即长截屏按钮;这里确实奇怪:还没点击长截屏,有些数据就已经捕获好了,例如:显示范围内的窗口边界、窗口空间中滚动内容的边界、当前窗口标题等等数据。            mScreenshotView.showScrollChip(response.getPackageName(), /\* onClick \*/ () -> {                DisplayMetrics displayMetrics = new DisplayMetrics();                getDefaultDisplay().getRealMetrics(displayMetrics);                // 新的位图 Bitmap  。                Bitmap newScreenshot = captureScreenshot(                        new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));                // 设置视图,这里只是一个缩略图,和普通截图一样大;                mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,                        mScreenshotTakenInPortrait);                // delay starting scroll capture to make sure the scrim is up before the app moves                // 捕获视图。长截图会在 LongScreenshotActivity 显示。                mScreenshotView.post(() -> runBatchScrollCapture(response));            });        } catch (InterruptedException | ExecutionException e) {            Log.e(TAG, "requestScrollCapture failed", e);        }    }
private void runBatchScrollCapture(ScrollCaptureResponse response) {
    // Clear the reference to prevent close() in dismissScreenshot    mLastScrollCaptureResponse = null;    if (mLongScreenshotFuture != null) {        mLongScreenshotFuture.cancel(true);    }    // 通过 response 得到 LongScreen 的视图。    mLongScreenshotFuture = mScrollCaptureController.run(response);    mLongScreenshotFuture.addListener(() -> {        ScrollCaptureController.LongScreenshot longScreenshot;        try {            // 获取 longScreenshot 。            longScreenshot = mLongScreenshotFuture.get();        } catch (CancellationException e) {            Log.e(TAG, "Long screenshot cancelled");            return;        } catch (InterruptedException | ExecutionException e) {            Log.e(TAG, "Exception", e);            mScreenshotView.restoreNonScrollingUi();            return;        }        if (longScreenshot.getHeight() == 0) {            mScreenshotView.restoreNonScrollingUi();            return;        }        // 相当于数据保存,把截图数据设置进去,但这里不是存储在本地。        mLongScreenshotHolder.setLongScreenshot(longScreenshot);        mLongScreenshotHolder.setTransitionDestinationCallback(                (transitionDestination, onTransitionEnd) ->                        mScreenshotView.startLongScreenshotTransition(                                transitionDestination, onTransitionEnd,                                longScreenshot));        final Intent intent = new Intent(mContext, LongScreenshotActivity.class);        intent.setFlags(                Intent.FLAG\_ACTIVITY\_NEW\_TASK | Intent.FLAG\_ACTIVITY\_CLEAR\_TOP);        // 跳转到编辑界面,也可以叫预览界面吧。        mContext.startActivity(intent,                ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());        RemoteAnimationAdapter runner = new RemoteAnimationAdapter(                SCREENSHOT\_REMOTE\_RUNNER, 0, 0);        try {            WindowManagerGlobal.getWindowManagerService()                    .overridePendingAppTransitionRemote(runner, DEFAULT\_DISPLAY);        } catch (Exception e) {            Log.e(TAG, "Error overriding screenshot app transition", e);        }    }, mMainExecutor);
}

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

猜你喜欢

转载自blog.csdn.net/YoungOne2333/article/details/129934704
今日推荐