Android T TaskSnapshot creation and removal process

data structure

  • WindowManagerService
    window management service, TaskSnapshoController is one of its members
  • TaskSnapshotController
    is the external interface of TaskSnapShot and manages the creation, acquisition and destruction of TaskSnapshot.
  • TaskSnapshotLoader
    loads TaskSnapshot from disk. TaskSnapshot will not only be cached in memory, but also written to disk. TaskSnapshotLoader is responsible for restoring application snapshots from disk. (Example scenario: Start several applications -> Restart the system -> Enter multitasking. In this scenario, after the system restarts, the display of multitasking requires page snapshots. The system process does not have the cache of TaskSnapshot at this time, so it will start from Load the snapshot from disk.)
  • TaskSnapshotPersister
    is responsible for the creation, reading and destruction of TaskSnapshot in the disk.
  • TaskSnapshotCache
    is responsible for the creation, reading and destruction of TaskSnaphshot in memory. When the application returns from the foreground to the background, TaskSnapShot will be cached in TaskSnapshotCache. And there is a mapping between ActivityRecord and TaskId, and there is a mapping between TaskId and TaskSnapshot. In this way, when the application returns to the foreground from the background, the TaskSnapshot can be obtained through the taskId; when the application exits, the TaskSnapshot can be obtained through the ActivityRecord and the TaskSnapshot can be removed.
  • The snapshot of the TaskSnapshot
    application stores the data of the application snapshot, such as the Task's Id, Size, TopActivity's ComponentName and the most critical GraphicBuffer. TaskSnapshot implements Parcelable because other application pages require snapshots such as Launcher for multitasking.
    Please add image description

Main process

Insert image description here
App Switches to Background
Because switching an app from the background to the foreground requires a snapshot to populate the launch window, the snapshot needs to be saved when it enters the background. The switching field of the application will be filled with transition animation, and the starting point of the task's snapshot process is designed to be the starting point of the transition animation.
Insert image description here
The Back key exits the application.
When the Back key is used to exit the application, the Activity will be destroyed. Therefore, when the application is started again, it is not considered a hot start and does not require an application snapshot. But the multitasking page still needs to apply the snapshot, so just remove the cache of the application snapshot.

Insert image description here
Exiting an app from multitasking
When you remove an app from a multitasking page, the process will be destroyed, so starting the app again is a cold start. At this time, there is no need to use the application snapshot in any scenario, so its cache and disk storage are all removed.

Creation of TashSnapshot

transition animation

The creation of TaskSnapshot is executed at the beginning of the transition animation. The general process is: prepare transition animation -> execute transition animation.
When the application starts and when the application retreats to the background, the system process will prepare the transition animation for the application.
The timing of executing the transition animation is in the layout process of WMS. The transition animation can only be executed after the window has completed drawing.
Taking RootWindowContainer.java#performSurfacePlacementNoTrace() as a starting point, analyze the TaskSnapshot creation process

 void performSurfacePlacementNoTrace() {
    
    
 	......
 	final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
 	......
 	checkAppTransitionReady(surfacePlacer);
 	......
 }

When the layout loop starts, first check whether the transition animation is ready

    private void checkAppTransitionReady(WindowSurfacePlacer surfacePlacer) {
    
    
        // Trace all displays app transition by Z-order for pending layout change.
        for (int i = mChildren.size() - 1; i >= 0; --i) {
    
    
            final DisplayContent curDisplay = mChildren.get(i);

            // If we are ready to perform an app transition, check through all of the app tokens
            // to be shown and see if they are ready to go.
            if (curDisplay.mAppTransition.isReady()) {
    
    
                // handleAppTransitionReady may modify curDisplay.pendingLayoutChanges.
                curDisplay.mAppTransitionController.handleAppTransitionReady();
				......
            }

            ......
        }
    }

Prepare the transition animation when starting the application, and execute the transition animation when the application completes onresume, so you can handle the transition animation at this time. That is prepare-execute-handle
DisplayContent.java
mAppTransitionController = new AppTransitionController(mWmService, this);
AppTransitionController.java

void handleAppTransitionReady() {
    ......
    mService.mTaskSnapshotController.onTransitionStarting(mDisplayContent);
}

At this time, the animation has been configured in the window, notifying TaskSnapshotController that the transition animation is starting.

GetClosingTasks

Following the above code process
TaskSnapshotController.java

    void onTransitionStarting(DisplayContent displayContent) {
    
    
        handleClosingApps(displayContent.mClosingApps);
    }

When activityRecord sets visibility, if activityrecord is about to become invisible, activityRecord will be added to displayContent.mClosingApps

    private void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
    
    
        if (shouldDisableSnapshots()) {
    
    
            return;
        }
        // We need to take a snapshot of the task if and only if all activities of the task are
        // either closing or hidden.
        getClosingTasks(closingApps, mTmpTasks);
        snapshotTasks(mTmpTasks);
        mSkipClosingAppSnapshotTasks.clear();
    }
   /**
     * Retrieves all closing tasks based on the list of closing apps during an app transition.
     */
    @VisibleForTesting
    void getClosingTasks(ArraySet<ActivityRecord> closingApps, ArraySet<Task> outClosingTasks) {
    
    
        outClosingTasks.clear();
        for (int i = closingApps.size() - 1; i >= 0; i--) {
    
    
            final ActivityRecord activity = closingApps.valueAt(i);
            final Task task = activity.getTask();
            if (task == null) continue;

            // 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 (isAnimatingByRecents(task)) {
    
    
                mSkipClosingAppSnapshotTasks.add(task);
            }
            // If the task of the app is not visible anymore, it means no other app in that task
            // is opening. Thus, the task is closing.
            if (!task.isVisible() && !mSkipClosingAppSnapshotTasks.contains(task)) {
    
    
                outClosingTasks.add(task);
            }
        }
    }

Not all closingApps require screenshots. As can be seen from getClosingTasks(closingApps, mTmpTasks)the implementation, screenshots are for tasks, and only tasks that are about to become invisible need to take screenshots.
snapshotTasks(mTmpTasks)Cache and persist storage operations for filtered closingApps
from snapshotTasks() to recordTaskSnapshot()

    void snapshotTasks(ArraySet<Task> tasks) {
    
    
        snapshotTasks(tasks, false /* allowSnapshotHome */);
    }
    private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
    
    
        for (int i = tasks.size() - 1; i >= 0; i--) {
    
    
            recordTaskSnapshot(tasks.valueAt(i), allowSnapshotHome);
        }
    }

recordTaskSnapshot

    void recordTaskSnapshot(Task task, boolean allowSnapshotHome) {
    
    
        final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
        final TaskSnapshot snapshot = captureTaskSnapshot(task, snapshotHome);
        
        final HardwareBuffer buffer = snapshot.getHardwareBuffer();
        if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
    
    
            buffer.close();
            Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
                    + buffer.getHeight());
        } else {
    
    
        	//缓存snapshot
            mCache.putSnapshot(task, snapshot);
            // Don't persist or notify the change for the temporal snapshot.
            if (!snapshotHome) {
    
    
            	//如果不是home类型的task,持久化存储snapshot
                mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
                task.onSnapshotChanged(snapshot);
            }
        }
    }

1. Obtain TaskSnapshot according to different conditions
2. Cache snapshot
3. Persistent storage snapshot

Get TaskSnapshot

   /**
    * This is different than {@link #recordTaskSnapshot(Task, boolean)} because it doesn't store
    * the snapshot to the cache and returns the TaskSnapshot immediately.
    *
    * This is only used for testing so the snapshot content can be verified.
    */
   @VisibleForTesting
   TaskSnapshot captureTaskSnapshot(Task task, boolean snapshotHome) {
    
    
       final TaskSnapshot snapshot;
       if (snapshotHome) {
    
    
       	//allowSnapshotHome在本流程中为false,所以不会进入该分支。
       	//如果锁屏被设置了密码,那么在息屏流程中就会设置allowSnapshotHome为true,会进入该分支。
           snapshot = snapshotTask(task);
       } else {
    
    
           switch (getSnapshotMode(task)) {
    
    
               case SNAPSHOT_MODE_NONE:
                   return null;
               case SNAPSHOT_MODE_APP_THEME:
               	//SNAPSHOT_MODE_APP_THEME模式表示不希望使用窗口的内容构建snapshot,只使用App的theme。
               	//例如被标记了WindowManager。LayoutParams.FLAG_SECURE的安全窗口;
               	//像PipMenuActivity设置了ATMS.setDisablePreviewScreenshots()
                   snapshot = drawAppThemeSnapshot(task);
                   break;
               case SNAPSHOT_MODE_REAL:
               	//SNAPSHOT_MODE_REAL模式使用task的页面的内容构建snapshot
                   snapshot = snapshotTask(task);
                   break;
               default:
                   snapshot = null;
                   break;
           }
       }
       return snapshot;
   }
   @VisibleForTesting
   int getSnapshotMode(Task task) {
    
    
       final ActivityRecord topChild = task.getTopMostActivity();
       if (!task.isActivityTypeStandardOrUndefined() && !task.isActivityTypeAssistant()) {
    
    
       //如果ActivityType不是ACTIVITY_TYPE_UNDEFINED、ACTIVITY_TYPE_STANDARD、
       //ACTIVITY_TYPE_ASSISTANT中的一种,那么就不会为其创建snapshot。
       //从前面final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
       //可以看出ACTIVITY_TYPE_HOME(即launcher)也是被排除在外的
           return SNAPSHOT_MODE_NONE;
       } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
    
    
           return SNAPSHOT_MODE_APP_THEME;
       } else {
    
    
           return SNAPSHOT_MODE_REAL;
       }
   }

snapshot = drawAppThemeSnapshot(task);Let’s take a look at the sums in captureTaskSnapshot respectively.snapshot = snapshotTask(task);

drawAppThemeSnapshot
    /**
     * If we are not allowed to take a real screenshot, this attempts to represent the app as best
     * as possible by using the theme's window background.
     */
    private TaskSnapshot drawAppThemeSnapshot(Task task) {
    
    
    	......
        final int color = ColorUtils.setAlphaComponent(
                task.getTaskDescription().getBackgroundColor(), 255);
        ......
        final RenderNode node = RenderNode.create("TaskSnapshotController", null);
        node.setLeftTopRightBottom(0, 0, width, height);
        node.setClipToBounds(false);
        final RecordingCanvas c = node.start(width, height);
        c.drawColor(color);//设置页面颜色,color值从task背景色获取,alpha值255不透明
		......
        final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
		......

        // Note, the app theme snapshot is never translucent because we enforce a non-translucent
        // color above
        return new TaskSnapshot(
                System.currentTimeMillis() /* id */,
                topChild.mActivityComponent, hwBitmap.getHardwareBuffer(),
                hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
                mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
                contentInsets, letterboxInsets, false /* isLowResolution */,
                false /* isRealSnapshot */, task.getWindowingMode(),
                getAppearance(task), false /* isTranslucent */, false /* hasImeSurface */);
    }

Get the GraphicBuffer from the Bitmap as the content of the TaskSnapshot, so drawAppThemeSnapshot() uses the task background color to build the snapshot.

snapshotTask
    @Nullable
    TaskSnapshot snapshotTask(Task task) {
    
    
        return snapshotTask(task, PixelFormat.UNKNOWN);
    }

    @Nullable
    TaskSnapshot snapshotTask(Task task, int pixelFormat) {
    
    
        TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
        ......
		//构建TaskSnapshot的核心还是在于GraphicBuffer的获取
        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
                createTaskSnapshot(task, builder);
		......
        return builder.build();
    }
    
    @Nullable
    SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
            TaskSnapshot.Builder builder) {
    
    
        Point taskSize = new Point();
        final SurfaceControl.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task,
                mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder);
        builder.setTaskSize(taskSize);
        return taskSnapshot;
    }

    @Nullable
    SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
            float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) {
    
    
        ......
        //使用Task的SurfaceControl获取GraphicBuffer,窗口的buffer本就是SurfaceControl提供的
        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
                SurfaceControl.captureLayersExcluding(
                        task.getSurfaceControl(), mTmpRect, scaleFraction,
                        pixelFormat, excludeLayers);
        ......
        return screenshotBuffer;
    }
    

Caching Tasksnapshot

Continue to see that mCache in the recordTaskSnapshotmCache.putSnapshot(task, snapshot)
method is the object of TaskSnapshotCache
TaskSnapshotCache.java

    void putSnapshot(Task task, TaskSnapshot snapshot) {
    
    
        final CacheEntry entry = mRunningCache.get(task.mTaskId);
        if (entry != null) {
    
    
        	//更新操作,为了将新的ActivityRecord的task绑定在一起
            mAppTaskMap.remove(entry.topApp);
        }
        final ActivityRecord top = task.getTopMostActivity();
        mAppTaskMap.put(top, task.mTaskId);
        mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top));
    }

mAppTaskMap.put(top, task.mTaskId);Indicates that ActivityRecord and taskId establish a mapping relationship.
Why not use RunningCache directly instead of using two maps? This is to monitor the update of RunningCache through ActivityRecord's remove.

mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top));Indicates that taskId and CacheEntry have a mapping relationship.
Why do you need to use CacheEntry? This is because the application needs to find the ActivityRecord through the taskId, so that the taskId and ActivityRecord complete the two-way binding. When the ActivityRecord is removed, the snapshot can be removed along the two maps; when a new snapshot is added, the old ActivityRecord is removed, and the CacheEntry will be directly overwritten by the new one.

Persistent storage TaskSnapshot

Android's persistent storage basically uses the Persister thread + Item queue model.
Considering the time-consuming and multi-threading reasons, create a thread to handle the file operation tasks created by each scene.
Abstract the file operation task into an Item, here WriteQueueItem. Then derive different Items to describe different types of operations

Continue to look at the recordTaskSnapshot method mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);where mPersister is the TaskSnapshotPersister object
TaskSnapshotPersister.java

    /**
     * Persists a snapshot of a task to disk.
     *
     * @param taskId The id of the task that needs to be persisted.
     * @param userId The id of the user this tasks belongs to.
     * @param snapshot The snapshot to persist.
     */
    void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
    
    
        synchronized (mLock) {
    
    
            mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
            sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
        }
    }

Persist TaskSnapshot to disk

StoreWriteQueueItem
    private class StoreWriteQueueItem extends WriteQueueItem {
    
     
		......
        @Override
        void write() {
    
    
            if (!createDirectory(mUserId)) {
    
    
                Slog.e(TAG, "Unable to create snapshot directory for user dir="
                        + getDirectory(mUserId));
            }
            boolean failed = false;
            if (!writeProto()) {
    
    
                failed = true;
            }
            if (!writeBuffer()) {
    
    
                failed = true;
            }
            if (failed) {
    
    
                deleteSnapshot(mTaskId, mUserId);
            }
        }
		......
    }

StoreWriteQueueItem describes the task of storing the disk. In the overridden write method, a file will be created to store the snapshot's task information and buffer information.

sendToQueueLocked
    @GuardedBy("mLock")
    private void sendToQueueLocked(WriteQueueItem item) {
    
    
        mWriteQueue.offer(item); //入队操作
        item.onQueuedLocked();
        ensureStoreQueueDepthLocked();
        if (!mPaused) {
    
    
            mLock.notifyAll();//唤醒Persist线程
        }
    }
mPersister thread
    private Thread mPersister = new Thread("TaskSnapshotPersister") {
    
    
        public void run() {
    
    
            android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            while (true) {
    
    
                WriteQueueItem next;
                boolean isReadyToWrite = false;
                synchronized (mLock) {
    
    
                    if (mPaused) {
    
    
                        next = null;
                    } else {
    
    
                        next = mWriteQueue.poll();
                        if (next != null) {
    
    
                            if (next.isReady()) {
    
    
                                isReadyToWrite = true;
                                next.onDequeuedLocked();
                            } else {
    
    
                                mWriteQueue.addLast(next);
                            }
                        }
                    }
                }
                if (next != null) {
    
    
                    if (isReadyToWrite) {
    
    
                        next.write();//取出Item任务后执行
                    }
                    SystemClock.sleep(DELAY_MS);
                }
                synchronized (mLock) {
    
    
                    final boolean writeQueueEmpty = mWriteQueue.isEmpty();
                    if (!writeQueueEmpty && !mPaused) {
    
    
                        continue;
                    }
                    try {
    
    
                        mQueueIdling = writeQueueEmpty;
                        mLock.wait();//执行完队列的所有任务进入等待状态,等待另一个线程来唤醒
                        mQueueIdling = false;
                    } catch (InterruptedException e) {
    
    
                    }
                }
            }
        }
    };

TaskSnapshot removal

When do I need to remove TaskSnapshot? When the task is destroyed, there are two removal scenarios:
exiting the application from the back key and removing the application from multitasking.

Exit the app from the back key

Generally speaking, the Activity's handling of the back event is to remove the Activity. TaskSnapshotController is more interested in the removal of Activity, so when Activity is removed, TaskSnapshotController will try to cache the abnormal TaskSnapshot.

Starting point for removal

ActivityClientController#activityDestroyed

class ActivityClientController extends IActivityClientController.Stub {
    
    
@Override
    public void activityDestroyed(IBinder token) {
    
    
        if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token);
        final long origId = Binder.clearCallingIdentity();
        synchronized (mGlobalLock) {
    
    
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityDestroyed");
            try {
    
    
                final ActivityRecord r = ActivityRecord.forTokenLocked(token);
                if (r != null) {
    
    
                    r.destroyed("activityDestroyed");
                }
            } finally {
    
    
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                Binder.restoreCallingIdentity(origId);
            }
        }
    }
}

Called here r.destroyed("activityDestroyed");where r is the ActivityRecord object

ActivityRecord.java

 /**
     * This method is to only be called from the client via binder when the activity is destroyed
     * AND finished.
     */
    void destroyed(String reason) {
    
    
        removeDestroyTimeout();

        ProtoLog.d(WM_DEBUG_CONTAINERS, "activityDestroyedLocked: r=%s", this);

        if (!isState(DESTROYING, DESTROYED)) {
    
    
            throw new IllegalStateException(
                    "Reported destroyed for activity that is not destroying: r=" + this);
        }

        if (isInRootTaskLocked()) {
    
    
            cleanUp(true /* cleanServices */, false /* setState */);
            removeFromHistory(reason);
        }

        mRootWindowContainer.resumeFocusedTasksTopActivities();
    }

transferremoveFromHistory(reason);

 /** Note: call {@link #cleanUp(boolean, boolean)} before this method. */
    void removeFromHistory(String reason) {
    
    
        finishActivityResults(Activity.RESULT_CANCELED,
                null /* resultData */, null /* resultGrants */);
        makeFinishingLocked();

        ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing activity %s, reason= %s "
                        + "callers=%s", this, reason, Debug.getCallers(5));

        takeFromHistory();
        removeTimeouts();
        ProtoLog.v(WM_DEBUG_STATES, "Moving to DESTROYED: %s (removed from history)",
                this);
        setState(DESTROYED, "removeFromHistory");
        if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + this);
        detachFromProcess();
        // Resume key dispatching if it is currently paused before we remove the container.
        resumeKeyDispatchingLocked();
        mDisplayContent.removeAppToken(token);

        cleanUpActivityServices();
        removeUriPermissionsLocked();
    }

Called mDisplayContent.removeAppToken(token);as DisplayContent object

DisplayContent.java

    void removeAppToken(IBinder binder) {
    
    
        final WindowToken token = removeWindowToken(binder, true /* animateExit */);
        if (token == null) {
    
    
            Slog.w(TAG_WM, "removeAppToken: Attempted to remove non-existing token: " + binder);
            return;
        }

        final ActivityRecord activity = token.asActivityRecord();

        if (activity == null) {
    
    
            Slog.w(TAG_WM, "Attempted to remove non-App token: " + binder + " token=" + token);
            return;
        }

        activity.onRemovedFromDisplay();
        if (activity == mFixedRotationLaunchingApp) {
    
    
            // Make sure the states of associated tokens are also cleared.
            activity.finishFixedRotationTransform();
            setFixedRotationLaunchingAppUnchecked(null);
        }
    }

Called activity.onRemovedFromDisplay(), activity is ActivityRecord object

ActivityRecord.java

   void onRemovedFromDisplay() {
    
    
        ......
		mWmService.mTaskSnapshotController.onAppRemoved(this);
		......
    }

Finally, onAppRemoved of TaskSnapshotController was called.

Remove cache

TaskSnapshotController.java

/**
     * Called when an {@link ActivityRecord} has been removed.
     */
    void onAppRemoved(ActivityRecord activity) {
    
    
        mCache.onAppRemoved(activity);
    }

Call mCache.onAppRemoved(activity);mCache as TaskSnapshotCache object

TaskSnapshotCache.java

    /**
     * Called when an app token has been removed
     */
    void onAppRemoved(ActivityRecord activity) {
    
    
        final Integer taskId = mAppTaskMap.get(activity);
        if (taskId != null) {
    
    
        	//断开ActivityRecord、taskId与TaskSnapshot之间的映射关系
            removeRunningEntry(taskId);
        }
    }
    void removeRunningEntry(int taskId) {
    
    
        final CacheEntry entry = mRunningCache.get(taskId);
        if (entry != null) {
    
    
            mAppTaskMap.remove(entry.topApp);
            mRunningCache.remove(taskId);
        }
    }

During the taskSnapshot creation process, the last visible ActivityRecord is mapped to taskId, so the cache will be removed if and only when the previously mapped ActivityRecord is removed.

Remove apps from multitasking

Removing an app from multitasking often means that the task has been removed. That is, there is no need to restore this task from hot start or multi-task, so the cache and persistent storage of TaskSnapshot need to be removed.

Starting point for removal

ActivityTaskManagerService#removeTask

public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
    
    
	@Override
    public boolean removeTask(int taskId) {
    
    
        mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()");
        synchronized (mGlobalLock) {
    
    
            final long ident = Binder.clearCallingIdentity();
            try {
    
    
                final Task task = mRootWindowContainer.anyTaskForId(taskId,
                        MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
                if (task == null) {
    
    
                    Slog.w(TAG, "removeTask: No task remove with id=" + taskId);
                    return false;
                }

                if (task.isLeafTask()) {
    
    
                    mTaskSupervisor.removeTask(task, true, REMOVE_FROM_RECENTS, "remove-task");
                } else {
    
    
                    mTaskSupervisor.removeRootTask(task);
                }
                return true;
            } finally {
    
    
                Binder.restoreCallingIdentity(ident);
            }
        }
    }
}

Call mTaskSupervisor.removeTask(task, true, REMOVE_FROM_RECENTS, "remove-task");mTaskSupervisor as ActivityTaskSupervisor object

ActivityTaskSupervisor.java

    void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
    
    
        if (task.mInRemoveTask) {
    
    
            // Prevent recursion.
            return;
        }
        task.mTransitionController.requestCloseTransitionIfNeeded(task);
        task.mInRemoveTask = true;
        try {
    
    
            task.removeActivities(reason, false /* excludingTaskOverlay */);
            cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
            mService.getLockTaskController().clearLockedTask(task);
            mService.getTaskChangeNotificationController().notifyTaskStackChanged();
            if (task.isPersistable) {
    
    
                mService.notifyTaskPersisterLocked(null, true);
            }
        } finally {
    
    
            task.mInRemoveTask = false;
        }
    }

calledcleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);

    void cleanUpRemovedTaskLocked(Task task, boolean killProcess, boolean removeFromRecents) {
    
    
        if (removeFromRecents) {
    
    
            mRecentTasks.remove(task);
        }
		......
    }

Call mRecentTasks.remove(task);mRecentTasks as the RecentTasks object

RecentTasks.java

    /**
     * Remove a task from the recent tasks list.
     */
    void remove(Task task) {
    
    
        mTasks.remove(task);
        notifyTaskRemoved(task, false /* wasTrimmed */, false /* killProcess */);
    }
    private void notifyTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
    
    
        for (int i = 0; i < mCallbacks.size(); i++) {
    
    
            mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed, killProcess);
        }
        mTaskNotificationController.notifyTaskListUpdated();
    }
    interface Callbacks {
    
    
        /**
         * Called when a task is added to the recent tasks list.
         */
        void onRecentTaskAdded(Task task);

        /**
         * Called when a task is removed from the recent tasks list.
         */
        void onRecentTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess);
    }

mCallbacks is called mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed, killProcess);as the Callbacks interface object, which is implemented by ActivityTaskSupervisor.
public class ActivityTaskSupervisor implements RecentTasks.Callbacks
Therefore, onRecentTaskRemovedthe implementation of the method is in ActivityTaskSupervisor.

ActivityTaskSupervisor.java

    @Override
    public void onRecentTaskRemoved(Task task, boolean wasTrimmed, boolean killProcess) {
    
    
        if (wasTrimmed) {
    
    
            // Task was trimmed from the recent tasks list -- remove the active task record as well
            // since the user won't really be able to go back to it
            removeTaskById(task.mTaskId, killProcess, false /* removeFromRecents */,
                    "recent-task-trimmed");
        }
        task.removedFromRecents();
    }

Here the task is called task.removedFromRecents();as Task object

Task.java

    void removedFromRecents() {
    
    
		......
		mAtmService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents(
                mTaskId, mUserId);
    }

Finally call notifyTaskRemovedFromRecents() of TaskSnapshotController

TaskSnapshotController.java

    void notifyTaskRemovedFromRecents(int taskId, int userId) {
    
    
        mCache.onTaskRemoved(taskId);
        mPersister.onTaskRemovedFromRecents(taskId, userId);
    }

This method does two things: remove cache and remove persistent storage

Remove cache

TaskSnapshotCache.java

    void onTaskRemoved(int taskId) {
    
    
        removeRunningEntry(taskId);//移除两个map中的映射关系

    }

    void removeRunningEntry(int taskId) {
    
    
        final CacheEntry entry = mRunningCache.get(taskId);
        if (entry != null) {
    
    
            mAppTaskMap.remove(entry.topApp);
            mRunningCache.remove(taskId);
        }
    }

Remove cache when task is removed recently

Remove persistent storage

TaskSnapshotPersister.java

    /**
     * Callend when a task has been removed.
     *
     * @param taskId The id of task that has been removed.
     * @param userId The id of the user the task belonged to.
     */
    void onTaskRemovedFromRecents(int taskId, int userId) {
    
    
        synchronized (mLock) {
    
    
        	//在之前的TaskSnapshot的持久化存储流程中有解锁Item队列会被Persist线程按序处理
            mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
            sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId));
        }
    }
    
DeleteWriteQueueItem
    private class DeleteWriteQueueItem extends WriteQueueItem {
    
    
        private final int mTaskId;
        private final int mUserId;

        DeleteWriteQueueItem(int taskId, int userId) {
    
    
            mTaskId = taskId;
            mUserId = userId;
        }

        @Override
        void write() {
    
    
        	//通过taskId和userId找到TaskSnapshot文件存储的位置,并将其删除
            deleteSnapshot(mTaskId, mUserId);
        }
    }
deleteSnapshot
    private void deleteSnapshot(int taskId, int userId) {
    
    
        final File protoFile = getProtoFile(taskId, userId);
        final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
        protoFile.delete();
        if (bitmapLowResFile.exists()) {
    
    
            bitmapLowResFile.delete();
        }
        final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
        if (bitmapFile.exists()) {
    
    
            bitmapFile.delete();
        }
    }

Guess you like

Origin blog.csdn.net/yimelancholy/article/details/130274044