Android的SystemUI應用,為使用者提供檢視最近使用應用的清單,當使用者點選Switch按鍵時,PhoneWindowManager會攔截此次key事件,攔截後調用StatusBarManagerService 接口,StatusBarManagerService通過調用mBar(SystemUI 注冊到StatusBarManagerService的Binder對象)通知SystemUI啟動RecentsActivity顯示最近使用應用的清單。
啟動的大概流程圖如下:
Recents的UI結構圖
接下來就按照流程圖簡單的分析一下RecentsActivity的啟動流程:
step1攔截key_Switch事件:
在key事件分發之前,會調用PhoneWindowManager的interceptKeyBeforeDispatching函數優先處理key事件.
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
} else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) {
if (!keyguardOn) {
if (down && repeatCount == ) {
preloadRecentApps();//預加載流程自行分析
} else if (!down) {
toggleRecentApps();
}
}
return -;
}
preloadRecentApps();主要是預加載流程,和啟動流程關鍵點基本一緻,感興趣自行分析。
當收到swith事件,不是down事件時調用toggleRecentApps函數:
step 2 toggleRecentApps函數
private void toggleRecentApps() {
mPreloadedRecentApps = false; // preloading no longer needs to be canceled
try {
IStatusBarService statusbar = getStatusBarService();
if (statusbar != null) {
statusbar.toggleRecentApps();
}
}
這裡直接調用StatusBarManagerService的toggleRecentApps函數。
step 3 StatusBarManagerService .toggleRecentApps()
public void toggleRecentApps() {
if (mBar != null) {
try {
mBar.toggleRecentApps();
} catch (RemoteException ex) {}
}
}
mBar 是SystemUI啟動的時候,在BaseStatusBar Start函數中通過調用StatusBarManagerService 的registerStatusBar函數注冊過來的如下:
mCommandQueue = new CommandQueue(this, iconList);
mBarService.registerStatusBar(mCommandQueue, iconList, switches, binders);
mBar 是一個(IStatusBar)binder代理對象,他的本地對象就是運作在SystemUI的mCommandQueue對象。
這樣StatusBarManagerService 就通過mBar程序間調用調用到mCommandQueue的toggleRecentApps函數。
step 4 mCommandQueue. toggleRecentApps
public void toggleRecentApps() {
synchronized (mList) {
mHandler.removeMessages(MSG_TOGGLE_RECENT_APPS);
mHandler.obtainMessage(MSG_TOGGLE_RECENT_APPS, , , null).sendToTarget();
}
}
這個函數隻是發送了一個消息到主線程消息隊列。最後處理該消息的代碼如下:
mCallbacks.toggleRecentApps();
mCallbacks 是BaseStatusBar 在step3中初始化CommandQueue時傳入this對象。
BaseStatusBar 又發送消息到消息隊列。最後處理該消息的是BaseStatusBar 的toggleRecents函數。
Step5 BaseStatusBar. toggleRecents
protected void toggleRecents() {
if (mRecents != null) {
sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
}
}
mRecents 是Recents類對象他繼承自SystemUI,在系統啟動的時候,SystemServer會調用startSystemUi函數啟動SystemUI應用的SystemUIService服務,
在SystemUIService 的的onCreate函數中會調用
SystemUIApplication 的startServicesIfNeeded,
在這個函數中會啟動會執行個體化多個繼承自SystemUI的類,并調用他們的Start函數,其中就包括Recents的start函數。
在Start函數中,調用putComponent(RecentsComponent.class, this);把Recents對象儲存到了SystemUI的mComponents中。
BaseStatusBar中的mRecents成員變量也就是從裡面取出來的。
Step6 Recents. toggleRecents
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
if (mUseAlternateRecents) {
mAlternateRecents.onToggleRecents(statusBarView);
return;
}
......
}
mUseAlternateRecents 預設為真,mAlternateRecents.onToggleRecents(statusBarView);該函數直接調用了toggleRecentsActivity
step7 AlternateRecentsComponent. toggleRecentsActivity
void toggleRecentsActivity() {
ActivityManager.RunningTaskInfo topTask = getTopMostTask();
AtomicBoolean isTopTaskHome = new AtomicBoolean();
if (isRecentsTopMost(topTask, isTopTaskHome)) {
Intent intent = new Intent(ACTION_TOGGLE_RECENTS_ACTIVITY);
intent.setPackage(mContext.getPackageName());
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
mLastToggleTime = System.currentTimeMillis();
return;
} else {
startRecentsActivity(topTask, isTopTaskHome.get());
}
}
這個函數主要是首先判斷目前界面是不是RecentsActivity
如果是就發送一個廣播,關閉RecentsActivity.廣播肯定是RecentsActivity接收了,接收後調用退出動畫,退出完成後調用RecentsActivity的finish函數。
如果不是則調用startRecentsActivity啟動RecentsActivity。
RecentsActivity啟動:界面的展示離不開資料,從界面上看,RecentsActivity至少需要應用名稱,圖檔,以及截圖,接下來看RecentsActivity資料的擷取流程:
Step8 在RecentsActivity的onStart函數中有調用updateRecentsTasks來更新擷取界面顯示的資料。
void updateRecentsTasks(Intent launchIntent) {
……
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SpaceNode root = loader.reload(this,
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount,
mConfig.launchedFromHome);
ArrayList<TaskStack> stacks = root.getStacks();
if (!stacks.isEmpty()) {
mRecentsView.setTaskStacks(root.getStacks());
}
……
if (mConfig.launchedWithNoRecentTasks) {
if (mEmptyView == null) {
mEmptyView = mEmptyViewStub.inflate();
}
mEmptyView.setVisibility(View.VISIBLE);
mRecentsView.setSearchBarVisibility(View.GONE);
} else {
if (mEmptyView != null) {
mEmptyView.setVisibility(View.GONE);
}
if (mRecentsView.hasSearchBar()) {
mRecentsView.setSearchBarVisibility(View.VISIBLE);
} else {
addSearchBarAppWidgetView();
}
}
mScrimViews.prepareEnterRecentsAnimation();
}
這個函數主要就是
1 調用RecentsTaskLoader的reload函數擷取相關最近使用應用的相關資訊。
2 調用mRecentsView.setTaskStacks,把獲得的TaskStack設定到RecentsView中建立UI結構圖中的TaskStackView
3 如果沒有最近使用的資訊則顯示空的提示資訊。這一步比較簡單隻是設定View的顯示隐藏。
Step9 RecentsTaskLoader.reload函數
先看一下RecentsTaskLoader構造函數中初始化的幾個重要的類和變量
1 mSystemServicesProxy = new SystemServicesProxy(context);
SystemServicesProxy 這個類主要承接了RecentsActivity和系統服務的互動,包含擷取最近任務清單,啟動制定任務所在的應用等等。
2 mLoadQueue = new TaskResourceLoadQueue();一個隊列主要用于緩存任務
3 mApplicationIconCache = new DrawableLruCache(iconCacheSize);
mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
mActivityLabelCache = new StringLruCache(100);
三個緩存,主要用于緩存應用圖示,名稱,截屏。
4 mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache, mDefaultThumbnail, mDefaultApplicationIcon);
TaskResourceLoader 繼承自Runnable 他的構造函數中啟動了一個HandlerThread,在他的Run函數中一直循環從mLoadQueue中讀取任務并擷取任務所對應的截圖,并緩存到mThumbnailCache中,然後通知TaskView加載截圖并顯示。
public SpaceNode reload(Context context, int preloadCount, boolean isTopTaskHome) {
ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
ArrayList<Task> tasksToLoad = new ArrayList<Task>();
TaskStack stack = getTaskStack(mSystemServicesProxy, context.getResources(),
-, preloadCount, true, isTopTaskHome, taskKeys, tasksToLoad);
SpaceNode root = new SpaceNode();
root.setStack(stack);
mLoader.start(context);
mLoadQueue.addTasks(tasksToLoad);
mPackageMonitor.setTasks(taskKeys);
return root;
}
這個函數首先通過調用getTaskStack擷取最近最近任務堆棧,在getTaskStack中是通過mSystemServicesProxy找到ActivityManagerService擷取最近使用的應用清單,然後擷取應用清單中的應用的啟動圖檔,以及名稱等資料儲存到資料類Task中,在吧所有的Task添加到TaskStack中傳回。然後啟動裝載應用截圖線程,并把所有的Task加入到mLoadQueue中。
Step10 mRecentsView.setTaskStacks
public void setTaskStacks(ArrayList<TaskStack> stacks) {
int childCount = getChildCount();
for (int i = childCount - ; i >= ; i--) {
View v = getChildAt(i);
if (v != mSearchBar) {
removeViewAt(i);
}
}
mStacks = stacks;
int numStacks = mStacks.size();
for (int i = ; i < numStacks; i++) {
TaskStack stack = mStacks.get(i);
TaskStackView stackView = new TaskStackView(getContext(), stack);
stackView.setCallbacks(this);
if (mConfig.debugModeEnabled) {
stackView.setDebugOverlay(mDebugOverlay);
}
addView(stackView);
}
mAlreadyLaunchingTask = false;
}
這個函數先移除RecentsView中的所有子View然後把擷取的Taskstack儲存到mStacks中,然後建立TaskStackView 視圖,并把視圖中要顯示的TaskStack儲存到TaskStackView中。
最後把建立的TaskStackView視圖作為子View添加到RecentsView中。
到此裝載過程完成。
當RecentsActivity中ViewRootImpl對所有的View程序一次Measure ,過程中。會調用TaskStackView的onMeasure函數,在onMeasure函數中會調用到TaskView的synchronizeStackViewsWithModel函數。
Step11 TaskView. synchronizeStackViewsWithModel
這個函數比較長就不貼代碼了,主要完成兩個工作,
1 建立UI結構圖中的TaskView并作為子View添加到TaskStackView中。
2 為TaskView計算在TaskStackView中的坐标并做一個TaskViewTransform平移動畫。(這部分代碼就不詳細分析了)
在這個函數中就是通過調用tv = mViewPool.pickUpViewFromPool(task, task);來建立TaskView
mViewPool = new ViewPool
V pickUpViewFromPool(T preferredData, T prepareData) {
V v = null;
boolean isNewView = false;
if (mPool.isEmpty()) {
v = mViewCreator.createView(mContext);
isNewView = true;
}
……
mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);
return v;
}
從ViewPool的構造函數可以知道mViewCreator 就是TaskStackView
TaskStackView的createView就是建立了一個TaskView
在調用TaskStackView的prepareViewToLeavePool函數
Step12 TaskStackView.prepareViewToLeavePool函數
public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
tv.onTaskBound(task);
……
RecentsTaskLoader.getInstance().loadTaskData(task);
tv.setClipViewInStack(true);
if (isNewView) {
addView(tv, insertIndex);
tv.setTouchEnabled(true);
tv.setCallbacks(this);
} else {
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
}
}
首先調用TaskView的onTaskBound函數,這個函數主要就是設定TaskView所關聯的Task,也就是說所要顯示的應用,然後設定TaskView為Task的成員變量mCb,而mCb主要用來通知重新整理的。然後把建立的TaskView添加到TaskStackView中。
然後調用RecentsTaskLoader.getInstance().loadTaskData(task)函數
Step13 RecentsTaskLoader. loadTaskData
public void loadTaskData(Task t) {
Drawable applicationIcon = mApplicationIconCache.getAndInvalidateIfModified(t.key);
Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(t.key);
boolean requiresLoad = (applicationIcon == null) || (thumbnail == null);
applicationIcon = applicationIcon != null ? applicationIcon : mDefaultApplicationIcon;
if (requiresLoad) {
mLoadQueue.addTask(t);
}
t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnail, applicationIcon);
}
這個函數主要是判斷目前Task的應用啟動圖示和截圖是否已經在mApplicationIconCache中緩存。
如果沒有緩存,則把Task加入到等待加載截圖的隊列mLoadQueue中,前面也介紹過這個隊列TaskResourceLoader的run函數會一直從mLoadQueue 讀取Task去擷取其對應的應用截圖和啟動圖示。
Step14 TaskResourceLoader.run
public void run() {
while (true) {
if (mCancelled) {
……
} else {
SystemServicesProxy ssp = mSystemServicesProxy;
final Task t = mLoadQueue.nextTask();
if (t != null) {
Drawable cachedIcon = mApplicationIconCache.get(t.key);
Bitmap cachedThumbnail = mThumbnailCache.get(t.key);
if (cachedIcon == null) {
cachedIcon = getTaskDescriptionIcon(t.key, t.icon, t.iconFilename, ssp,
mContext.getResources());
if (cachedIcon == null) {
ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(),
t.key.userId);
if (info != null) {
cachedIcon = ssp.getActivityIcon(info, t.key.userId);
}
}
if (cachedIcon == null) {
cachedIcon = mDefaultApplicationIcon;
}
mApplicationIconCache.put(t.key, cachedIcon);
}
if (cachedThumbnail == null) {
cachedThumbnail = ssp.getTaskThumbnail(t.key.id);
if (cachedThumbnail != null) {
cachedThumbnail.setHasAlpha(false);
} else {
cachedThumbnail = mDefaultThumbnail;
}
mThumbnailCache.put(t.key, cachedThumbnail);
}
if (!mCancelled) {
final Drawable newIcon = cachedIcon;
final Bitmap newThumbnail = cachedThumbnail == mDefaultThumbnail
? null : cachedThumbnail;
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
t.notifyTaskDataLoaded(newThumbnail, newIcon);
}
});
}
}
……
}
}
}
}
這個函數比較好分析,首先從緩存中檢視對應的Task的應用啟動圖示和截圖是否已經存在,如果不存在就去擷取,擷取過程都是調用的系統接口,自行檢視。
擷取完成後緩存,然後往主線程發送一個消息,去執行t.notifyTaskDataLoaded(newThumbnail, newIcon);函數。
Step15 Task. notifyTaskDataLoaded
public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
this.applicationIcon = applicationIcon;
this.thumbnail = thumbnail;
if (mCb != null) {
mCb.onTaskDataLoaded();
}
}
這個函數就是儲存擷取的應用啟動圖示和應用截圖,然後調用mCb. onTaskDataLoaded函數。
在step12中為TaskView關聯要顯示的Task後,還把TaskView設定為Task的mCb
TaskView的onTaskDataLoaded函數大家就知道了,把剛剛裝載的應用啟動圖示和截圖設定到TaskView對應的View中顯示出來。
到此RecentsActivity的啟動過程就分析完了。
歡迎關注個人微信公衆号,定期更新個人工作心得