Recents和AMS中历史任务的区别

1 概述

任务历史保存方式关系图
图中左上部分是任务历史在AMS中保存的列表,右上部分是多任务界面里所会展示的任务列表,左下则是实际上还在前后台运行的task列表,可以看到,这三者是有不小区别的,并不是同步的。
下面讲首先分别讲下它们三者负责什么、意义所在,然后通过一些具体方法的使用看下它们之间的关系。

1.1 任务和返回栈 - 实际数据模型 (左下)

这个是指在调度体系里实际保存的TaskRecord实例,而ActivityRecord-TaskRecord-ActivityStack之间的关系建议看官方文档
任务栈是实际在后台的任务,因此这些任务也都有对应的显示层实例。

其创建与删除通过stack控制: ActivityStack#createTaskRecord(),ActivityStack#removeTask()
当前activity栈可以通过adb shell dumpsys activity activities 命令打印出来,这个命令最终会调用到方法:
AMS#dumpActivitiesLocked() -> ActivityStackSupervisor#dumpActivitiesLocked()

1.2 AMS.mRecentTasks - 历史任务记录(左上)

RecentTasks用于在AMS中保存最近使用的task记录,可以通过adb shell dumpsys activity recents命令打印其列表。

RecentTasks应该被看作任务的历史记录而不是实例,虽然保留了TaskRecord对象,但并不一定有对应的activity。

RecentTasks列表是始终有序的,最近使用的task在列表中的位置最靠前。之所以有序,是因为框架里,每次resume后都会把当前应用重新添加到RecentTasks中,典型代码如下:

ActivityStack#resumeTopActivityInnerLocked() {  
            ......  
            if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next + " (in existing)");  
            next.state = ActivityState.RESUMED;  
            mResumedActivity = next;  
            next.task.touchActiveTime();  
            mRecentTasks.addLocked(next.task);  
            mService.updateLruProcessLocked(next.app, true, null);  
            updateLRUListLocked(next);  
            mService.updateOomAdjLocked();  
            ......  
}  

如上代码第7行即将task置于mRecentTasks头部,而6行是更新task的activeTime。

与add对应的remove,则基本只有AMS#cleanUpRemovedTaskLocked()这一个地方:

private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess,  
            boolean removeFromRecents) {  
        if (removeFromRecents) {  
            mRecentTasks.remove(tr);  
            tr.removedFromRecents();  
            // zeusis : clear the paired TaskRecord and resize fullscreenStack to normal  
            ......  
        }  
        .....  
    }  

通常的remove途径,就是用户在多任务上滑快照回收一个应用或在多任务点清理内存按钮批量回收应用。
应用按back键退出做finish,虽然activity都destroy掉了,在整个的应用栈里被删掉,但是taskrecord其实还是保留下来的,保存在mRecentTasks中直到历史记录过多<span class=”hint–top hint–error hint–medium hint–rounded hint–bounce” aria-label=”如果应用历史记录数超过设定的MAX值(48),会将RecentTasks表末的记录删掉,详见AMS#addAppTask(). L10731

[^reboot_restore]:重启后persistent的应用taskRecord会在RecentTasks中重新生成。 “>1或重启<span class=”hint–top hint–error hint–medium hint–rounded hint–bounce” aria-label=”如果应用历史记录数超过设定的MAX值(48),会将RecentTasks表末的记录删掉,详见AMS#addAppTask(). L10731

[^reboot_restore]:重启后persistent的应用taskRecord会在RecentTasks中重新生成。 “>1才可能销毁。

####TaskPersister
与RecentTasks相关的还有TaskPersister,用于保存被设定persistent属性的任务列表,并在手机重启后从本地保存的xml重新加载TaskRecord。有兴趣的话可以看TaskRecord#restoreFromXml()及相关流程。

1.3 多任务 - 展示给用户的任务概览(右上)

Recents(多任务)中记录的任务列表与其它两者是不一样的,由于是展示给用户的,所以要尽可能符合用户期待,这就造成一些实际已销毁或回收的任务也要保存显示,而一些无关紧要的或特别的应用又需要隐藏起来。

如此说来,这个任务列表首先就是与任务栈解耦的,实际上,多任务的列表是每次启动多任务时从任务历史记录处获取列表,然后再做各种过滤动作获得真正适合展示给用户的列表。在下文3.1节具体讲解了多任务的列表是如何从AMS获取并过滤的。

[Recents官方文档][3]

2 关系

AMS#RecentTasks和SystemUI.Recents.TaskStack区别

  • 两者数据形式上都是线性有序的
  • 后者列表包含于前者,后者是前者过滤而来的,具体过滤步骤见3.1的流程
  • 前者的更新会触发回调改变后者

AMS#RecentTasks和任务栈区别

  • 任务栈是个大的数据模型,taskRecord按序排列在不同栈中,而RecentTasks是线性记录
  • 应用destory后,在RecentTasks中仍保有其taskrecord,但在任务栈中已将其移除

3 具体代码例子

3.1 Recents从AMS.RecentTasks更新列表

Recents作为系统界面,虽然与AMS关系紧密,但毕竟是一个独立出来的app模块,所以其列表很难做到与server一侧的情况保持同步,为此,Recents每次启动时都要重刷整个列表,确保符合现场。重刷列表是比较费时的操作,故此,AOSP将Recents设计启动前先做预处理,从server一侧获取列表并作大致过滤,然后启动recentsActivity。

多任务界面的启动通过PhoneStatusBar.showRecentApps方法,在向AMS发起启动activity请求前,会先preload,一个典型的多任务界面启动时调用栈如下:

at com.android.systemui.recents.model.RecentsTaskLoadPlan.preloadRawTasks(RecentsTaskLoadPlan.java:125)
at com.android.systemui.recents.model.RecentsTaskLoadPlan.preloadPlan(RecentsTaskLoadPlan.java:153)
at com.android.systemui.recents.model.RecentsTaskLoader.preloadTasks(RecentsTaskLoader.java:384)
at com.android.systemui.recents.RecentsImpl.startRecentsActivity(RecentsImpl.java:924)
at com.android.systemui.recents.RecentsImpl.showRecents(RecentsImpl.java:316)
at com.android.systemui.recents.Recents.showRecents(Recents.java:308)

通过以下两层方法从AMS获得rawTasks列表SystemServiceProxy#getRecentTasks() -> AMS#getRecentTasks()。
这是最先的两层过滤,AMS一侧的方法用来获取最近符合要求应用列表,而SSP的方法是在调用前者后再根据多任务另外设置的黑名单再过滤一遍。

我们先看AMS一侧的代码。

3.1.1 第一层过滤

AMS#getRecentTasks()

@Override  
public ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,  
        int userId) {  
    final int callingUid = Binder.getCallingUid();  
    userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,  
            false, ALLOW_FULL_ONLY, "getRecentTasks", null);  

    final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;  
    final boolean withExcluded = (flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0;  
    synchronized (this) {  
        final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(),  
                callingUid);  
        final boolean detailed = checkCallingPermission(  
                android.Manifest.permission.GET_DETAILED_TASKS)  
                == PackageManager.PERMISSION_GRANTED;  

        if (!isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) {  
            Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");  
            return ParceledListSlice.emptyList();  
        }  
        mRecentTasks.loadUserRecentsLocked(userId);  

        final int recentsCount = mRecentTasks.size();  
        ArrayList<ActivityManager.RecentTaskInfo> res =  
                new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount);  

        final Set<Integer> includedUsers;  
        if (includeProfiles) {  
            includedUsers = mUserController.getProfileIds(userId);  
        } else {  
            includedUsers = new HashSet<>();  
        }  
        includedUsers.add(Integer.valueOf(userId));  

        for (int i = 0; i < recentsCount && maxNum > 0; i++) {  
            TaskRecord tr = mRecentTasks.get(i);  
            // Only add calling user or related users recent tasks   
            if (!includedUsers.contains(Integer.valueOf(tr.userId))) {  
                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr);  
                continue;//不属于该用户组的跳过  
            }  

            if (tr.realActivitySuspended) {  
                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr);  
                continue;  
            }  

            // Return the entry if desired by the caller.  We always return  
            // the first entry, because callers always expect this to be the  
            // foreground app.  We may filter others if the caller has  
            // not supplied RECENT_WITH_EXCLUDED and there is some reason  
            // we should exclude the entry.  

            if (i == 0  
                    || withExcluded  
                    || (tr.intent == null)  
                    || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)  
                            == 0)) {  
                if (!allowed) {  
                    // If the caller doesn't have the GET_TASKS permission, then only  
                    // allow them to see a small subset of tasks -- their own and home.  
                    if (!tr.isHomeTask() && tr.effectiveUid != callingUid) {  
                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr);  
                        continue;//没有GET_TASKS权限的不能获取其它应用的列表  
                    }  
                }  

                if ((flags & ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS) != 0) {  
                    if (tr.stack != null && tr.stack.isHomeStack()) {  
                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,  
                                "Skipping, home stack task: " + tr);  
                        continue;  
                    }  
                }  
                if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK) != 0) {  
                    final ActivityStack stack = tr.stack;  
                    if (stack != null && stack.isDockedStack() && stack.topTask() == tr) {  
                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,  
                                "Skipping, top task in docked stack: " + tr);  
                        continue;//原生逻辑,在A/r状态下,下屏miniRecents中不会有上屏应用的快照  
                    }  
                }  
                if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) {  
                    if (tr.stack != null && tr.stack.isPinnedStack()) {  
                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,  
                                "Skipping, pinned stack task: " + tr);  
                        continue;  
                    }  
                }  
                if (tr.autoRemoveRecents && tr.getTopActivity() == null) {  
                    // Don't include auto remove tasks that are finished or finishing.  
                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,  
                            "Skipping, auto-remove without activity: " + tr);  
                    continue;//autoRemoveRecents的应用在销毁后会从mRecentsTasks列表中删除,这种情况只是还没来得及删除,但也要过滤掉  
                }  
                if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0  
                        && !tr.isAvailable) {  
                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,  
                            "Skipping, unavail real act: " + tr);  
                    continue;  
                }  

                if (!tr.mUserSetupComplete) {  
                    // Don't include task launched while user is not done setting-up.  
                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,  
                            "Skipping, user setup not complete: " + tr);  
                    String record = tr.toString();  
                    if(record.contains(QQ_NAME) || record.contains(WEIBO_NAME) || record.contains(WECHAT_NAME)){  
                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,"not skip for dualapp" + tr);  
                    }else{  
                        continue;  
                    }  
                }  

                ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr);  
                if (!detailed) {  
                    rti.baseIntent.replaceExtras((Bundle)null);  
                }  

                res.add(rti);  
                maxNum--;  
            }  
        }  
        return new ParceledListSlice<>(res);  
    }  
}  

这个方法看起来比较长,但逻辑其实很简单。
首先从入参看,maxNum是所需的列表长度,满足数量即返回。flag是过滤条件。userId用于过滤掉不属于该应用组的应用。
从35行开始,遍历任务历史记录mRecentTasks,根据方法入参携带的flag做相应过滤,不符合要求的跳过,符合要求的则增加到结果列表,直到结果数目符合要求,结束遍历返回结果。

54~60行的逻辑主要针对EXCLUDE_FROM_RECENTS这个标记位。EXCLUDE_FROM_RECENTS,顾名思义,在Recents中不显示,多任务获取列表时,flag不会带有RECENT_WITH_EXCLUDED标识,withExcluded为false,此时应用如果设置了EXCLUDE_FROM_RECENTS就会被跳过不作为结果返回,不过有个特例,表头的应用不受此限制,就是说,从应用A进入多任务仍会有A的快照,也正因此,在SSP中需要另外增加黑名单逻辑对一些特殊的应用再做一次过滤。

60行之后的代码是针对flag中每一个过滤需求跳过相应task。

67~99行是在处理分屏问题过程中我们增加的一些过滤机制,相对应的也增加了各种flag。之所以在这里加过滤机制,是因为许多地方要判断当前后台运行的应用是否支持分屏之类的,这就一定要通过getRecent获取最近任务列表,而多任务的列表有其自己一套过滤机制,且与后台并不完全同步,不能直接拿来用,因此我们只好模仿着增加特别分屏需要的过滤。

从AMS一侧获取列表后,还要继续在ssp的方法中筛掉黑名单里的应用。

3.1.2 第二层过滤

SystemServiceProxy#getRecentTasks()

public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,  
        boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {  
    ......  

    // Remove home/recents/excluded tasks  
    int minNumTasksToQuery = 10;  
    int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);  
    int flags = ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |  
            //ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |  
            ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |  
            ActivityManager.RECENT_IGNORE_UNAVAILABLE |  
            ActivityManager.RECENT_INCLUDE_PROFILES;  
    if(mIsInMultiWindowMode == true) {  
        flags |= ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK;  
    }  
    if (includeFrontMostExcludedTask) {  
        flags |= ActivityManager.RECENT_WITH_EXCLUDED;  
    }  
    List<ActivityManager.RecentTaskInfo> tasks = null;  
    try {  
        tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId);  
    } catch (Exception e) {  
        Log.e(TAG, "Failed to get recent tasks", e);  
    }  

    // Break early if we can't get a valid set of tasks  
    if (tasks == null) {  
        return new ArrayList<>();  
    }  

    boolean isFirstValidTask = true;  
    Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();  
    while (iter.hasNext()) {  
        ActivityManager.RecentTaskInfo t = iter.next();  

        // NOTE: The order of these checks happens in the expected order of the traversal of the  
        // tasks  

        // Remove the task if it or it's package are blacklsited  
        if (sRecentsBlacklist.contains(t.realActivity.getClassName()) ||  
                sRecentsBlacklist.contains(t.realActivity.getPackageName())) {  
            iter.remove();  
            continue;  
        }  
        ......  
    }  

    return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));  
}  

8~12行是多任务从AMS获取列表的默认flag。

上述代码中的21行从AMS获得列表,然后在40~44行里,将黑名单中的去掉。

上文提到,某些特例下,设置了从多任务排除掉的应用仍会在多任务显示,像这种无论如何都不希望在多任务显示的应用,可以在此处加入黑名单,就能够确保从列表中去掉了。

除了这些基本的过滤,Recents还有进行自己的一套过滤,比如说丢弃掉已经太久没有激活过的应用。

3.1.3 第三层过滤

在RecentsTaskLoadPlan#preloadPlan中,上述的preloadRawTasks()执行完后,还会遍历得到的mRawTasks做深一步的预处理和过滤。
其中有个判定项isStackTask将会在后面用作过滤。

boolean isStackTask = isFreeformTask || !isHistoricalTask(t) ||  
        (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));  

/**  
 * Returns whether this task is too old to be shown.  
 */  
private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {  
    return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME /* 6h */);  
}  

如上代码,如果应用有太久没有使用,isHistoricalTask将会为true,isStackTask将可能为false(后面一个条件具体解释起来比较复杂,有兴趣的可以继续阅读源码相关部分思考其用处)。

在随后的处理中,mRawTasks会继续被处理成FilteredTaskList:mStackTaskList,并根据acceptTask()接口返回的值决定是否保留在FilterdTaskList。

at com.android.systemui.recents.model.TaskStack$2.acceptTask(TaskStack.java:608)
at com.android.systemui.recents.model.FilteredTaskList.updateFilteredTasks(TaskStack.java:204)
at com.android.systemui.recents.model.FilteredTaskList.set(TaskStack.java:159)
at com.android.systemui.recents.model.TaskStack.setTasks(TaskStack.java:851)
at com.android.systemui.recents.model.RecentsTaskLoadPlan.preloadPlan(RecentsTaskLoadPlan.java:228)
at com.android.systemui.recents.model.RecentsTaskLoader.preloadTasks(RecentsTaskLoader.java:384)

public TaskStack() {  
        // Ensure that we only show non-docked tasks  
        mStackTaskList.setFilter(new TaskFilter() {  
            @Override  
            public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {  
                if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {  
                    if (t.isAffiliatedTask()) {  
                        // If this task is affiliated with another parent in the stack, then the  
                        // historical state of this task depends on the state of the parent task  
                        Task parentTask = taskIdMap.get(t.affiliationTaskId);  
                        if (parentTask != null) {  
                            t = parentTask;  
                        }  
                    }  
                }  
                return t.isStackTask;  
            }  
        });  
    }  

如上代码中16行,如果之前计算得出的isStackTask为false,那就会被过滤掉。mStackTaskList才是最后在多任务中被拿来用的任务列表。

3.2 应用如何设定自己不在多任务中显示

首先要增加EXCLUDE_FROM_RECENTS属性,具体来说,在模块manifest中的里增加如下代码

<activity  
  android:name="XYZ"  
  android:excludeFromRecents="true">  

但原生逻辑下,从应用直接进入多任务的时候,及时加了exclude属性,当前应用的快照也会保留,如果这种情况也不希望显示。那么需要将自己加入多任务黑名单。
SystemServiceProxy.sRecentsBlacklist:

final static List<String> sRecentsBlacklist;  
static {  
    sRecentsBlacklist = new ArrayList<>();  
    sRecentsBlacklist.add("com.android.systemui.tv.pip.PipOnboardingActivity");  
    sRecentsBlacklist.add("com.android.systemui.tv.pip.PipMenuActivity");  
}  

上面是我们ROM里当前的黑名单,头两个是原生就有的,后面是针对JUI系统界面需求所增加的,像全局搜索、bigbang这类的,对用户算是系统界面的一部分,但实际上却是通过app实现的应用适合加入黑名单。

3.3 如何获取后台应用列表而不是历史记录

前面一直讲的getRecentTasks()获取的列表包含了已经处于destoryed状态的tasks,如果只想要后台运行应用的列表,可以使用mAm.getRunningTasks(maxNum)方法,这个方法会调用到AMS#getTasks():

@Override  
    public List<RunningTaskInfo> getTasks(int maxNum, int flags) {  
        final int callingUid = Binder.getCallingUid();  
        ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();  
        synchronized(this) {  
            final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(),  
                    callingUid);  

            // TODO: Improve with MRU list from all ActivityStacks.  
            mStackSupervisor.getTasksLocked(maxNum, list, callingUid, allowed);  
        }  
        return list;  
    }  

StackSuperVisor#getTasksLocked()方法会深搜遍历activity任务栈,然后截取所需数目的列表并返回。
不过mAm.getRunningTasks()这个方法已经是@Deprecated的了。
我们看到第9行有个TODO,但这个已经好几年没有变化了,大概是RecentTasks已经基本够用了。

3.2 mRecentTasks中的taskRecord重回任务栈舞台

从多任务点击快照与一般启动应用的方式不一样。一般从Launcher启动或是应用间跳转都是借助Intent,在新建task之前,会遍历任务栈中的应用看是否有intent相同的task并复用之。
而从多任务启动应用,却与intent无关,是直接使用taskId的:
ASS#startActivityFromRecentsInner

final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {  
    ......  
    task = anyTaskForIdLocked(taskId, RESTORE_FROM_RECENTS, launchStackId);  
    if (task == null) {  
        continueUpdateBounds(HOME_STACK_ID);  
        mWindowManager.executeAppTransition();  
        throw new IllegalArgumentException(  
                "startActivityFromRecentsInner: Task " + taskId + " not found.");  
    }  
    ......  
}  

ASS#anyTaskForIdLocked

TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents, int stackId) {  
        int numDisplays = mActivityDisplays.size();  
        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {  
            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;  
            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {  
                ActivityStack stack = stacks.get(stackNdx);  
                TaskRecord task = stack.taskForIdLocked(id);  
                if (task != null) {  
                    return task;  
                }  
            }  
        }  

        // Don't give up! Look in recents.//如果任务栈中没有,尝试在RecentTasks中搜索  
        if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents");  
        TaskRecord task = mRecentTasks.taskForIdLocked(id);  
        if (task == null) {  
            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "\tDidn't find task id=" + id + " in recents");  
            return null;  
        }  

        if (!restoreFromRecents) {  
            return task;  
        }  

        if (!restoreRecentTaskLocked(task, stackId)) {  
            if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,  
                    "Couldn't restore task id=" + id + " found in recents");  
            return null;  
        }  
        if (DEBUG_RECENTS) Slog.w(TAG_RECENTS, "Restored task id=" + id + " from in recents");  
        return task;  
    }  

由于多任务中显示的是最近任务列表,对用户来说,更是所谓在后台运行的应用,正常情况通过taskid是一定能找到一个可重用的taskrecord的。在anyTaskForIdLocked()中,首先遍历任务栈寻找相同taskid应用,如果找不到则在RecentTasks中继续找,找到后通过restoreRecentTaskLocked将taskRecord重新加入合适的ActivityStack中去。这样,本已被销毁的应用从RecentTasks中被加回任务栈,taskId等信息都不变。
与上面相对的,直接通过intent方式启动activity时,虽然也会尽可能寻找可重用的task,但却只是从任务栈中遍历寻找intent相同的Task,不会从RecentTasks中再寻找一边。
可以说,一个taskRecord实例的唯一标识是taskId,而一个应用task的唯一标识是intent。


:

[3]: https://developer.android.com/guide/components/recents.html


  1. 1.如果应用历史记录数超过设定的MAX值(48),会将RecentTasks表末的记录删掉,详见AMS#addAppTask(). L10731

[^reboot_restore]:重启后persistent的应用taskRecord会在RecentTasks中重新生成。