1.1 任务和返回栈 - 实际数据模型 html
这个是指在调度体系里实际保存的TaskRecord实例,而ActivityRecord-TaskRecord-ActivityStack之间的关系建议看官方文档。
任务栈是实际在后台的任务,所以这些任务也都有对应的显示层实例。java
其建立与删除经过stack控制: ActivityStack#createTaskRecord(),ActivityStack#removeTask()
当前activity栈能够经过adb shell dumpsys activity activities 命令打印出来,这个命令最终会调用到方法:
AMS#dumpActivitiesLocked() -> ActivityStackSupervisor#dumpActivitiesLocked()android
RecentTasks
用于在AMS中保存最近使用的task记录,能够经过adb shell dumpsys activity recents命令打印其列表。ecentTasks
应该被看做任务的历史记录而不是实例,虽然保留了TaskRecord对象,但并不必定有对应的activity。RecentTasks
列表是始终有序的,最近使用的task在列表中的位置最靠前。之因此有序,是由于框架里,每次resume后都会把当前应用从新添加到RecentTasks中,典型代码以下:shell
1 ActivityStack#resumeTopActivityInnerLocked() { 2 ...... 3 if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next + " (in existing)"); 4 next.state = ActivityState.RESUMED; 5 mResumedActivity = next; 6 next.task.touchActiveTime(); 7 mRecentTasks.addLocked(next.task); 8 mService.updateLruProcessLocked(next.app, true, null); 9 updateLRUListLocked(next); 10 mService.updateOomAdjLocked(); 11 ...... 12 }
如上代码第7行即将task置于mRecentTasks头部,而6行是更新task的activeTime。app
与add对应的remove,则基本只有AMS#cleanUpRemovedTaskLocked()这一个地方:框架
1 private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, 2 boolean removeFromRecents) { 3 if (removeFromRecents) { 4 mRecentTasks.remove(tr); 5 tr.removedFromRecents(); 6 // zeusis : clear the paired TaskRecord and resize fullscreenStack to normal 7 ...... 8 } 9 ..... 10 }
一般的remove途径,就是用户在多任务上滑快照回收一个应用或在多任务点清理内存按钮批量回收应用。ide
应用按back键退出作finish,虽然activity都destroy掉了,在整个的应用栈里被删掉,可是taskrecord其实仍是保留下来的,保存在mRecentTasks中直到历史记录过多。
ui
####TaskPersister
与RecentTasks相关的还有TaskPersister,用于保存被设定persistent属性的任务列表,并在手机重启后从本地保存的xml从新加载TaskRecord。有兴趣的话能够看TaskRecord#restoreFromXml()及相关流程。this
Recents(多任务)中记录的任务列表与其它二者是不同的,因为是展现给用户的,因此要尽量符合用户期待,这就形成一些实际已销毁或回收的任务也要保存显示,而一些可有可无的或特别的应用又须要隐藏起来。spa
如此说来,这个任务列表首先就是与任务栈解耦的,实际上,多任务的列表是每次启动多任务时从任务历史记录处获取列表,而后再作各类过滤动做得到真正适合展现给用户的列表。在下文3.1节具体讲解了多任务的列表是如何从AMS获取并过滤的。
[Recents官方文档][3]
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#getRecentTasks()
1 @Override 2 public ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, 3 int userId) { 4 final int callingUid = Binder.getCallingUid(); 5 userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, 6 false, ALLOW_FULL_ONLY, "getRecentTasks", null); 7 8 final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0; 9 final boolean withExcluded = (flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0; 10 synchronized (this) { 11 final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(), 12 callingUid); 13 final boolean detailed = checkCallingPermission( 14 android.Manifest.permission.GET_DETAILED_TASKS) 15 == PackageManager.PERMISSION_GRANTED; 16 17 if (!isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) { 18 Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents"); 19 return ParceledListSlice.emptyList(); 20 } 21 mRecentTasks.loadUserRecentsLocked(userId); 22 23 final int recentsCount = mRecentTasks.size(); 24 ArrayList<ActivityManager.RecentTaskInfo> res = 25 new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount); 26 27 final Set<Integer> includedUsers; 28 if (includeProfiles) { 29 includedUsers = mUserController.getProfileIds(userId); 30 } else { 31 includedUsers = new HashSet<>(); 32 } 33 includedUsers.add(Integer.valueOf(userId)); 34 35 for (int i = 0; i < recentsCount && maxNum > 0; i++) { 36 TaskRecord tr = mRecentTasks.get(i); 37 // Only add calling user or related users recent tasks 38 if (!includedUsers.contains(Integer.valueOf(tr.userId))) { 39 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr); 40 continue;//不属于该用户组的跳过 41 } 42 43 if (tr.realActivitySuspended) { 44 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr); 45 continue; 46 } 47 48 // Return the entry if desired by the caller. We always return 49 // the first entry, because callers always expect this to be the 50 // foreground app. We may filter others if the caller has 51 // not supplied RECENT_WITH_EXCLUDED and there is some reason 52 // we should exclude the entry. 53 54 if (i == 0 55 || withExcluded 56 || (tr.intent == null) 57 || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) 58 == 0)) { 59 if (!allowed) { 60 // If the caller doesn't have the GET_TASKS permission, then only 61 // allow them to see a small subset of tasks -- their own and home. 62 if (!tr.isHomeTask() && tr.effectiveUid != callingUid) { 63 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr); 64 continue;//没有GET_TASKS权限的不能获取其它应用的列表 65 } 66 } 67 68 if ((flags & ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS) != 0) { 69 if (tr.stack != null && tr.stack.isHomeStack()) { 70 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 71 "Skipping, home stack task: " + tr); 72 continue; 73 } 74 } 75 if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK) != 0) { 76 final ActivityStack stack = tr.stack; 77 if (stack != null && stack.isDockedStack() && stack.topTask() == tr) { 78 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 79 "Skipping, top task in docked stack: " + tr); 80 continue;//原生逻辑,在A/r状态下,下屏miniRecents中不会有上屏应用的快照 81 } 82 } 83 if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) { 84 if (tr.stack != null && tr.stack.isPinnedStack()) { 85 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 86 "Skipping, pinned stack task: " + tr); 87 continue; 88 } 89 } 90 if (tr.autoRemoveRecents && tr.getTopActivity() == null) { 91 // Don't include auto remove tasks that are finished or finishing. 92 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 93 "Skipping, auto-remove without activity: " + tr); 94 continue;//autoRemoveRecents的应用在销毁后会从mRecentsTasks列表中删除,这种状况只是还没来得及删除,但也要过滤掉 95 } 96 if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0 97 && !tr.isAvailable) { 98 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 99 "Skipping, unavail real act: " + tr); 100 continue; 101 } 102 103 if (!tr.mUserSetupComplete) { 104 // Don't include task launched while user is not done setting-up. 105 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, 106 "Skipping, user setup not complete: " + tr); 107 String record = tr.toString(); 108 if(record.contains(QQ_NAME) || record.contains(WEIBO_NAME) || record.contains(WECHAT_NAME)){ 109 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,"not skip for dualapp" + tr); 110 }else{ 111 continue; 112 } 113 } 114 115 ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr); 116 if (!detailed) { 117 rti.baseIntent.replaceExtras((Bundle)null); 118 } 119 120 res.add(rti); 121 maxNum--; 122 } 123 } 124 return new ParceledListSlice<>(res); 125 } 126 }
这个方法看起来比较长,但逻辑其实很简单。
首先从入参看,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的方法中筛掉黑名单里的应用。
SystemServiceProxy#getRecentTasks()
1 public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId, 2 boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) { 3 ...... 4 5 // Remove home/recents/excluded tasks 6 int minNumTasksToQuery = 10; 7 int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks); 8 int flags = ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS | 9 //ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK | 10 ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS | 11 ActivityManager.RECENT_IGNORE_UNAVAILABLE | 12 ActivityManager.RECENT_INCLUDE_PROFILES; 13 if(mIsInMultiWindowMode == true) { 14 flags |= ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK; 15 } 16 if (includeFrontMostExcludedTask) { 17 flags |= ActivityManager.RECENT_WITH_EXCLUDED; 18 } 19 List<ActivityManager.RecentTaskInfo> tasks = null; 20 try { 21 tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId); 22 } catch (Exception e) { 23 Log.e(TAG, "Failed to get recent tasks", e); 24 } 25 26 // Break early if we can't get a valid set of tasks 27 if (tasks == null) { 28 return new ArrayList<>(); 29 } 30 31 boolean isFirstValidTask = true; 32 Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator(); 33 while (iter.hasNext()) { 34 ActivityManager.RecentTaskInfo t = iter.next(); 35 36 // NOTE: The order of these checks happens in the expected order of the traversal of the 37 // tasks 38 39 // Remove the task if it or it's package are blacklsited 40 if (sRecentsBlacklist.contains(t.realActivity.getClassName()) || 41 sRecentsBlacklist.contains(t.realActivity.getPackageName())) { 42 iter.remove(); 43 continue; 44 } 45 ...... 46 } 47 48 return tasks.subList(0, Math.min(tasks.size(), numLatestTasks)); 49 }
8~12行是多任务从AMS获取列表的默认flag。
上述代码中的21行从AMS得到列表,而后在40~44行里,将黑名单中的去掉。
上文提到,某些特例下,设置了从多任务排除掉的应用仍会在多任务显示,像这种不管如何都不但愿在多任务显示的应用,能够在此处加入黑名单,就可以确保从列表中去掉了。
除了这些基本的过滤,Recents还有进行本身的一套过滤,好比说丢弃掉已经过久没有激活过的应用。
在RecentsTaskLoadPlan#preloadPlan中,上述的preloadRawTasks()执行完后,还会遍历获得的mRawTasks作深一步的预处理和过滤。
其中有个断定项isStackTask
将会在后面用做过滤。
1 boolean isStackTask = isFreeformTask || !isHistoricalTask(t) || 2 (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS)); 3 4 /** 5 * Returns whether this task is too old to be shown. 6 */ 7 private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) { 8 return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME /* 6h */); 9 }
如上代码,若是应用有过久没有使用,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)
1 public TaskStack() { 2 // Ensure that we only show non-docked tasks 3 mStackTaskList.setFilter(new TaskFilter() { 4 @Override 5 public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) { 6 if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) { 7 if (t.isAffiliatedTask()) { 8 // If this task is affiliated with another parent in the stack, then the 9 // historical state of this task depends on the state of the parent task 10 Task parentTask = taskIdMap.get(t.affiliationTaskId); 11 if (parentTask != null) { 12 t = parentTask; 13 } 14 } 15 } 16 return t.isStackTask; 17 } 18 }); 19 }
如上代码中16行,若是以前计算得出的isStackTask为false,那就会被过滤掉。mStackTaskList
才是最后在多任务中被拿来用的任务列表。
首先要增长EXCLUDE_FROM_RECENTS属性,具体来讲,在模块manifest中的里增长以下代码
<activity android:name="XYZ" android:excludeFromRecents="true">
但原生逻辑下,从应用直接进入多任务的时候,及时加了exclude属性,当前应用的快照也会保留,若是这种状况也不但愿显示。那么须要将本身加入多任务黑名单。
SystemServiceProxy.sRecentsBlacklist:
1 final static List<String> sRecentsBlacklist; 2 static { 3 sRecentsBlacklist = new ArrayList<>(); 4 sRecentsBlacklist.add("com.android.systemui.tv.pip.PipOnboardingActivity"); 5 sRecentsBlacklist.add("com.android.systemui.tv.pip.PipMenuActivity"); 6 }
上面是咱们ROM里当前的黑名单,头两个是原生就有的,后面是针对JUI系统界面需求所增长的,像全局搜索、bigbang这类的,对用户算是系统界面的一部分,但实际上倒是经过app实现的应用适合加入黑名单。
前面一直讲的getRecentTasks()获取的列表包含了已经处于destoryed状态的tasks,若是只想要后台运行应用的列表,可使用mAm.getRunningTasks(maxNum)方法,这个方法会调用到AMS#getTasks():
1 @Override 2 public List<RunningTaskInfo> getTasks(int maxNum, int flags) { 3 final int callingUid = Binder.getCallingUid(); 4 ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>(); 5 synchronized(this) { 6 final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(), 7 callingUid); 8 9 // TODO: Improve with MRU list from all ActivityStacks. 10 mStackSupervisor.getTasksLocked(maxNum, list, callingUid, allowed); 11 } 12 return list; 13 }
StackSuperVisor#getTasksLocked()方法会深搜遍历activity任务栈,而后截取所需数目的列表并返回。
不过mAm.getRunningTasks()这个方法已是@Deprecated的了。
咱们看到第9行有个TODO,但这个已经好几年没有变化了,大概是RecentTasks已经基本够用了。
从多任务点击快照与通常启动应用的方式不同。通常从Launcher启动或是应用间跳转都是借助Intent,在新建task以前,会遍历任务栈中的应用看是否有intent相同的task并复用之。
而从多任务启动应用,却与intent无关,是直接使用taskId的:
ASS#startActivityFromRecentsInner
1 final int startActivityFromRecentsInner(int taskId, Bundle bOptions) { 2 ...... 3 task = anyTaskForIdLocked(taskId, RESTORE_FROM_RECENTS, launchStackId); 4 if (task == null) { 5 continueUpdateBounds(HOME_STACK_ID); 6 mWindowManager.executeAppTransition(); 7 throw new IllegalArgumentException( 8 "startActivityFromRecentsInner: Task " + taskId + " not found."); 9 } 10 ...... 11 }
ASS#anyTaskForIdLocked
1 TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents, int stackId) { 2 int numDisplays = mActivityDisplays.size(); 3 for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { 4 ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; 5 for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { 6 ActivityStack stack = stacks.get(stackNdx); 7 TaskRecord task = stack.taskForIdLocked(id); 8 if (task != null) { 9 return task; 10 } 11 } 12 } 13 14 // Don't give up! Look in recents.//若是任务栈中没有,尝试在RecentTasks中搜索 15 if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents"); 16 TaskRecord task = mRecentTasks.taskForIdLocked(id); 17 if (task == null) { 18 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "\tDidn't find task id=" + id + " in recents"); 19 return null; 20 } 21 22 if (!restoreFromRecents) { 23 return task; 24 } 25 26 if (!restoreRecentTaskLocked(task, stackId)) { 27 if (DEBUG_RECENTS) Slog.w(TAG_RECENTS, 28 "Couldn't restore task id=" + id + " found in recents"); 29 return null; 30 } 31 if (DEBUG_RECENTS) Slog.w(TAG_RECENTS, "Restored task id=" + id + " from in recents"); 32 return task; 33 }
因为多任务
中显示的是最近任务列表,对用户来讲,更是所谓在后台运行的应用,正常状况经过taskid是必定能找到一个可重用的taskrecord的。在anyTaskForIdLocked()中,首先遍历任务栈寻找相同taskid应用,若是找不到则在RecentTasks中继续找,找到后经过restoreRecentTaskLocked将taskRecord从新加入合适的ActivityStack中去。这样,本已被销毁的应用从RecentTasks
中被加回任务栈
,taskId等信息都不变。
与上面相对的,直接经过intent方式启动activity时,虽然也会尽量寻找可重用的task,但却只是从任务栈
中遍历寻找intent相同的Task,不会从RecentTasks
中再寻找一边。能够说,一个taskRecord实例的惟一标识是taskId,而一个应用task的惟一标识是intent。