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命令打印其列表。ecentTasks
應該被看作任務的歷史記錄而不是實例,雖然保留了TaskRecord對象,但並不一定有對應的activity。RecentTasks
列表是始終有序的,最近使用的task在列表中的位置最靠前。之所以有序,是因為框架里,每次resume后都會把當前應用重新添加到RecentTasks中,典型代碼如下:
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。
與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途徑,就是用戶在多任務上滑快照回收一個應用或在多任務點清理內存按鈕批量回收應用。
應用按back鍵退出做finish,雖然activity都destroy掉了,在整個的應用棧里被刪掉,但是taskrecord其實還是保留下來的,保存在mRecentTasks中直到歷史記錄過多。
####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的方法是在調用前者后再根據多任務另外設置的黑名單再過濾一遍。
3.1.1 第一層過濾
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的方法中篩掉黑名單里的應用。
3.1.2 第二層過濾
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還有進行自己的一套過濾,比如說丟棄掉已經太久沒有激活過的應用。
3.1.3 第三層過濾
在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
才是最后在多任務中被拿來用的任務列表。
3.2 應用如何設定自己不在多任務中顯示
首先要增加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實現的應用適合加入黑名單。
3.3 如何獲取后台應用列表而不是歷史記錄
前面一直講的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已經基本夠用了。
3.2 mRecentTasks中的taskRecord重回任務棧舞台
從多任務點擊快照與一般啟動應用的方式不一樣。一般從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。