天天看點

RecentsTask分析Android5.0

Android的SystemUI應用,為使用者提供檢視最近使用應用的清單,當使用者點選Switch按鍵時,PhoneWindowManager會攔截此次key事件,攔截後調用StatusBarManagerService 接口,StatusBarManagerService通過調用mBar(SystemUI 注冊到StatusBarManagerService的Binder對象)通知SystemUI啟動RecentsActivity顯示最近使用應用的清單。

啟動的大概流程圖如下:

RecentsTask分析Android5.0

Recents的UI結構圖

RecentsTask分析Android5.0

接下來就按照流程圖簡單的分析一下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的啟動過程就分析完了。

歡迎關注個人微信公衆号,定期更新個人工作心得

RecentsTask分析Android5.0