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。