SystemUI RecentsActivity 分析
功能描述
Android在finish結束應用後,之前佔用內存不會立即被釋放出來。在內存不足的時候,我們可以recent按鈕清理後臺的應用。點擊recent按鈕,界面上會顯示所有有界面後臺的task的棧頂縮微圖(Launch不會顯示)
預先了解
ActivityStack,ActivityRecord,TaskRecord關係
- ActivityStack則是用來管理TaskRecord的,包含了多個TaskRecord。
- 一個TaskRecord由一個或者多個ActivityRecord組成,這就是我們常說的任務棧,具有後進先出的特點
- ActivityRecord對應一個Activity實例
下面是包含關係
->:包含
- 問題1:什麼情況纔會有多個TaskRecord
當跳轉的下一個界面的啓動模式是 android:launchMode=“singleInstance”,應用會產生新的TaskRecord
可以通過 adb shell dumpsys activity 抓取後臺task
當我們設置MainActivity爲singleInstance時:
TaskRecord{1175cb4 #59 A=com.can.keycodeddetected U=0 StackId=1 sz=1}
Run #1: ActivityRecord{f3ddef9 u0 com.can.keycodeddetected/.MainActivity t59}
TaskRecord{c6b59bc #58 A=com.can.keycodeddetected U=0 StackId=1 sz=1}
Run #0: ActivityRecord{3c56e71 u0 com.can.keycodeddetected/.autofilltest.AutoFillActivity t58}
當我們設置MainActivity爲standard時:
TaskRecord{d0f6fec #60 A=com.can.keycodeddetected U=0 StackId=1 sz=2}
Run #1: ActivityRecord{1ca3948 u0 com.can.keycodeddetected/.MainActivity t60}
Run #0: ActivityRecord{b07d861 u0 com.can.keycodeddetected/.autofilltest.AutoFillActivity t60}
mResumedActivity: ActivityRecord{1ca3948 u0 com.can.keycodeddetected/.MainActivity t60}
從上面可以看出singleInstance模式下,會有兩個TaskRecord,分別是#58,#59,在standard就只有一個#60。
- 問題2:(一個應用內)點擊recent按鈕顯示的是哪個TaskRecord,如果清除後臺,是清除的TaskRecord還是ActivityStack
通過應用,我們可以發現recent按鈕,只會顯示最後這個應用的界面(runningtask),當我們清除這個應用後臺,整個應用都清除了,所以:點擊recent按鈕顯示的是最後運行的TaskRecord,清除後臺,是清除的ActivityStack,因爲只有一個taskrecord顯示在歷史記錄,如果是清除的TaskRecord,那麼必須顯示所以存在的TaskRecord,這樣顯然不合理,對於用戶而言,用戶不關心TaskRecord,用戶只想把整個應用都幹掉。
ActivityManager
獲取後臺任務,清理後臺,以及獲取內存數據,都是在AMS以及相關服務裏面完成的。
如獲取內存:
獲取AMS代理對象:
ActivityManager mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
獲取內存:
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
mAm.getMemoryInfo(memoryInfo);
其他api後臺分析
調用流程
從響應按鍵到顯示歷史任務活動
PhoneWindowManager響應按鍵事件(如何是虛擬按鍵直接看後面)
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
....
....
showRecentApps(true, false);
....
}
private void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) {
mPreloadedRecentApps = false; // preloading no longer needs to be canceled
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
statusbar.showRecentApps(triggeredFromAltTab, fromHome);
}
}
StatusBarManagerInternal是StatusbarManagerService的內部對象,位於系統進程。
frameworks/base//services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@Override
public void showRecentApps(boolean triggeredFromAltTab, boolean fromHome) {
if (mBar != null) {
try {
mBar.showRecentApps(triggeredFromAltTab, fromHome);
} catch (RemoteException ex) {}
}
}
這裏的mBar對象是SystemUI調用StatusBarManagerService服務的registerStatusBar傳遞進來的,使得SystemUI與StatusBarManagerService能夠雙向通訊,使得StatusBarManagerService不僅僅能被請求,還能主動請求SystemUI這個客戶端。
(注意:Binder是Android一種跨進程的通訊方式,屬於客戶服務端方式,client主動請求,service被動接受,然後返回,如果需要雙方都能夠主動請求,必須傳遞一個binder過去,讓兩個對象互相作爲對方的客戶端)
上面的mBar對象對應的是SystemUI裏面的CommandQueue
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
從CommandQueue大概走如下流程
歷史任務活動加載View和stack
到達RecentsActivity,就是顯示歷史任務
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
這裏面有3個操作
- 清理後臺stack,清除指定stack或全部stack
- 獲取手機運存,並顯示出來
- 獲取後他的stack,並顯示處理
清理後臺stack,清除指定stack或全部stack
在處理RecentsActivity.java處理(省略清除按鈕到這個處理方法的步驟)
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
public final void onBusEvent(DeleteTaskDataEvent event) {
// Remove any stored data from the loader
RecentsTaskLoader loader = Recents.getTaskLoader();
loader.deleteTaskData(event.task, false);
// Remove the task from activity manager
SystemServicesProxy ssp = Recents.getSystemServices();
// SPRD: Bug 692452 new feature of lock recent apps
if(!event.task.isTaskLocked){
ssp.removeTask(event.task.key.id);
}
}
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
/** Removes the task */
public void removeTask(final int taskId) {
if (mAm == null) return;
if (RecentsDebugFlags.Static.EnableMockTasks) return;
// Remove the task.
mUiOffloadThread.submit(() -> {
mAm.removeTask(taskId);
});
}
可以看到會調用AMS的removeTask的方法,這個是系統api,不再進行分析。
獲取手機運存,並顯示出來
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
/* SPRD: new feature of showing memory tip @{ */
public ActivityManager.MemoryInfo getMemoryInfo() {
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
mAm.getMemoryInfo(memoryInfo);
return memoryInfo;
}
調用mAm.getMemoryInfo(memoryInfo);獲取手機當前內存。
獲取後他的stack,並顯示處理
frameworks/base/packages/SystemUI//src/com/android/systemui/recents/RecentsActivity.java
private void reloadStackView() {
// If the Recents component has preloaded a load plan, then use that to prevent
// reconstructing the task stack
//看做RecentsTaskLoader loader = new RecentsTaskLoader();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan();
if (loadPlan == null) {
//看做RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan();
loadPlan = loader.createLoadPlan(this);
}
// Start loading tasks according to the load plan
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!loadPlan.hasTasks()) {
loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
!launchState.launchedFromHome && !launchState.launchedViaDockGesture);//1
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.runningTaskId = launchState.launchedToTaskId;
loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
loader.loadTasks(this, loadPlan, loadOpts);//2
TaskStack stack = loadPlan.getTaskStack();
mRecentsView.onReload(stack, mIsVisible);
....
}
上面主要通過RecentsTaskLoader和RecentsTaskLoadPlan獲取歷史任務。在1,2處,
//加載最終調AMS加載task。Rencenttaskinfo 轉task操作
loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
!launchState.launchedFromHome && !launchState.launchedViaDockGesture);
//獲取loader保存的task,並配置圖標等等
loader.loadTasks(this, loadPlan, loadOpts);
這兩個類互相調用,流程如下
在preloadTasks和loadTasks方法裏面做了許多判斷,沒有再具體分析。比如androd Go存在修改時間無法顯示歷史任務的問題,就是這兩個方法中某一個條件導致的,
參考:https://www.jianshu.com/p/5133f012f21b
擴展
我們要在SystemUi裏面自己寫一個清理後臺的功能,比如在quick Setting中清理,可以把代碼剝離出來使用
封裝兩個方法直接使用:
- 獲取所有task
返回所有後臺能清理的task
private ArrayList<Task> reloadStack() {
// If the Recents component has preloaded a load plan, then use that to prevent
// reconstructing the task stack
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan();
if (loadPlan == null) {
loadPlan = loader.createLoadPlan(mContext);
}
// Start loading tasks according to the load plan
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!loadPlan.hasTasks()) {
loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
!launchState.launchedFromHome && !launchState.launchedViaDockGesture);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.runningTaskId = launchState.launchedToTaskId;
loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
loader.loadTasks(mContext, loadPlan, loadOpts);
TaskStack stack = loadPlan.getTaskStack();
// Keep track of the total stack task count
return stack.getStackTasks();
}
- 清理指定的task
private void removeTask(Task task) {
// Remove any stored data from the loader
RecentsTaskLoader loader = Recents.getTaskLoader();
loader.deleteTaskData(task, false);
// Remove the task from activity manager
SystemServicesProxy ssp = Recents.getSystemServices();
Log.i("wangcan",task+">remove #"+task.key.id);
// SPRD: Bug 692452 new feature of lock recent apps
if(!task.isTaskLocked){
ssp.removeTask(task.key.id);
}
}
使用方法:
protected void startClearAllTask() {
mHost.collapsePanels();
ArrayList<Task> allTasks = reloadStack();
int count = allTasks.size();
for(int i = 0;i < count;i++) {
Task oneTask = allTasks.get(i);
removeTask(oneTask);
}
}