最近任务截图空白的一种场景-灭屏下的task变动

143 阅读2分钟

这里提到一种可能导致截图空白的场景->灭屏下的task变动

最近任务中保留的app截图,一般在当前应用从可见->不可见时进行截图

cs.android.com/android/pla…

    // For shell transition, record snapshots before transaction start.
    void onTransactionReady(@WindowManager.TransitionType int type,
            ArrayList<Transition.ChangeInfo> changeInfos) {
        final boolean isTransitionOpen = isTransitionOpen(type);
        final boolean isTransitionClose = isTransitionClose(type);
        if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM) {
            return;
        }
        ActivitiesByTask activityTargets = null;
        for (int i = changeInfos.size() - 1; i >= 0; --i) {
            Transition.ChangeInfo info = changeInfos.get(i);
            // Intentionally skip record snapshot for changes originated from PiP.
            if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue;
            if (info.mContainer.isActivityTypeHome()) continue;
            final Task task = info.mContainer.asTask();
            if (task != null && !task.mCreatedByOrganizer && !task.isVisibleRequested()) {
           ** // 一般从此处捕获,在shell动画的流程里**
                mTaskSnapshotController.recordSnapshot(task, info);
            }
            // Won't need to capture activity snapshot in close transition.
            if (isTransitionClose) {
                continue;
            }
            if (info.mContainer.asActivityRecord() != null
                    || info.mContainer.asTaskFragment() != null) {
                final TaskFragment tf = info.mContainer.asTaskFragment();
                final ActivityRecord ar = tf != null ? tf.getTopMostActivity()
                        : info.mContainer.asActivityRecord();
                if (ar != null && ar.getTask().isVisibleRequested()) {
                    if (activityTargets == null) {
                        activityTargets = new ActivitiesByTask();
                    }
                    activityTargets.put(ar);
                }
            }
        }
        if (activityTargets != null) {
            activityTargets.recordSnapshot(mActivitySnapshotController);
        }
    }

在该处调用到

mTaskSnapshotController.recordSnapshot(task, info);

这一段在shellTransition的逻辑里,一般是和动画有关系

recordSnapshot捕获截图的逻辑中,会提前进行check当前状态是否可捕获

cs.android.com/android/pla…

    /**
     * Check if the state of the Task is appropriate to capture a snapshot, such like the task
     * snapshot or the associated IME surface snapshot.
     *
     * @param source the target object to capture the snapshot
     * @return Pair of (the top activity of the task, the main window of the task) if passed the
     * state checking. Returns {@code null} if the task state isn't ready to snapshot.
     */
    Pair<ActivityRecord, WindowState> checkIfReadyToSnapshot(TYPE source) {
    **// 此处check是否灭屏**
        if (!mService.mPolicy.isScreenOn()) {
            if (DEBUG_SCREENSHOT) {
                Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
            }
            return null;
        }
... ...
        return new Pair<>(activity, mainWindow);
    }

第一条就是灭屏下禁止截图

所以灭屏下,不可截图,如果你的逻辑涉及灭屏瞬间有task变动,那很有可能会导致截图失败

但实际上灭屏前的瞬间,谷歌也有一段逻辑考虑了去捕获截图

cs.android.com/android/pla…

/** Called when the device is going to sleep (e.g. screen off, AOD without screen off). */
void snapshotForSleeping(int displayId) {
    if (shouldDisableSnapshots() || !mService.mDisplayEnabled) {
        return;
    }
    final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
    if (displayContent == null) {
        return;
    }
    // Allow taking snapshot of home when turning screen off to reduce the delay of waking from
    // secure lock to home.
    final boolean allowSnapshotHome = displayId == Display.DEFAULT_DISPLAY
            && mService.mPolicy.isKeyguardSecure(mService.mCurrentUserId);
    mTmpTasks.clear();
    displayContent.forAllLeafTasks(task -> {
        if (!allowSnapshotHome && task.isActivityTypeHome()) {
            return;
        }
        // Since RecentsAnimation will handle task snapshot while switching apps with the best
        // capture timing (e.g. IME window capture), No need additional task capture while task
        // is controlled by RecentsAnimation.
        if (task.isVisible() && !isAnimatingByRecents(task)) {
            mTmpTasks.add(task);
        }
    }, true /* traverseTopToBottom */);
    **//此处即将去捕获截图**
    snapshotTasks(mTmpTasks);
}
​
    void snapshotTasks(ArraySet<Task> tasks) {
        for (int i = tasks.size() - 1; i >= 0; i--) {
            recordSnapshot(tasks.valueAt(i));
        }
    }

但比较遗憾的是

snapshotForSleeping是在灭屏瞬间被异步调用的,(毕竟捕获截图是个耗时操作

cs.android.com/android/pla…;

    /**
     * Called when screen is being turned off.
     */
    void screenTurningOff(int displayId, ScreenOffListener listener) {
        if (shouldDisableSnapshots()) {
            listener.onScreenOff();
            return;
        }
​
        // We can't take a snapshot when screen is off, so take a snapshot now!
        mHandler.post(() -> {
            try {
                synchronized (mService.mGlobalLock) {
                    snapshotForSleeping(displayId);
                }
            } finally {
                listener.onScreenOff();
            }
        });
    }

这意味着当流程走到真正截图前的check工作时,很有可能已经灭屏,导致截图请求被驳回

所以尽量不要在灭屏时触及移动task变动,如果设计到该场景,需要额外在checkIfReadyToSnapshot方法里面加豁免