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);
}

勉勉强强长截图也完成了吧,自己也还是有点不太清楚,完全没有任何资料可以参考。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容