Android 7.0 Launcher3的啓動和加載流程分析

本文的分析基於MTK提供的Android 7.0源碼,並非Google官方提供的源碼,其中可能有一些小的差異,還望諒解。
Launcher的本質就是一個普通應用,它比普通應用多配置了Category的Android:name=”android.intent.category.HOME”屬性,之後ActivityManagerService的startHomeActivityLocked方法將啓動含有這個屬性的Activity。

boolean startHomeActivityLocked(int userId) {
        if(this.mHeadless) {
            this.ensureBootCompleted();
            return false;
        } else if(this.mFactoryTest == 1 && this.mTopAction == null) {
            return false;
        } else {
            Intent intent = new Intent(this.mTopAction, this.mTopData != null?Uri.parse(this.mTopData):null);
            intent.setComponent(this.mTopComponent);
            if(this.mFactoryTest != 1) {
                intent.addCategory("android.intent.category.HOME");
            }
            ActivityInfo aInfo = intent.resolveActivityInfo(this.mContext.getPackageManager(), 1024);
            if(aInfo != null) {
                intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
                aInfo = new ActivityInfo(aInfo);
                aInfo.applicationInfo = this.getAppInfoForUser(aInfo.applicationInfo, userId);
                ProcessRecord app = this.getProcessRecordLocked(aInfo.processName, aInfo.applicationInfo.uid);
                if(app == null || app.instrumentationClass == null) {
                    intent.setFlags(intent.getFlags() | 268435456);
                    this.mMainStack.startActivityLocked((IApplicationThread)null, intent, (String)null, aInfo, (IBinder)null, (String)null, 0, 0, 0, 0, (Bundle)null, false, (ActivityRecord[])null);
                }
            }  
   return true;
      }
 }

這裏貼出一篇關於ActivityManagerService啓動Activity的博文:
http://blog.csdn.net/gaugamela/article/details/53183216

接下來看看Launcher界面的劃分。Launcher3實質其實就是一個Activity包含N個自定義的View。
Launcher3界面
結合圖和佈局文件可能更好理解Launcher3的界面

<com.android.launcher3.LauncherRootView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res-auto"
    android:id="@+id/launcher"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <com.android.launcher3.DragLayer
        android:id="@+id/drag_layer"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.android.launcher3.FocusIndicatorView
            android:id="@+id/focus_indicator"
            android:layout_width="52dp"
            android:layout_height="52dp" />
        <!-- The workspace contains 5 screens of cells -->
        <!-- DO NOT CHANGE THE ID -->
        <com.android.launcher3.Workspace
            android:id="@+id/workspace"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            launcher:defaultScreen="@integer/config_workspaceDefaultScreen"
            launcher:pageIndicator="@+id/page_indicator">
        </com.android.launcher3.Workspace>

        <!-- DO NOT CHANGE THE ID -->
        <include layout="@layout/hotseat"
            android:id="@+id/hotseat"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <include layout="@layout/overview_panel"
            android:id="@+id/overview_panel"
            android:visibility="gone" />
        <!-- Keep these behind the workspace so that they are not visible when
             we go into AllApps -->
        <include
            android:id="@+id/page_indicator"
            layout="@layout/page_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
           android:layout_gravity="center_horizontal" />
        <include
            android:id="@+id/search_drop_target_bar"
            layout="@layout/search_drop_target_bar" />
        <include layout="@layout/widgets_view"
            android:id="@+id/widgets_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="invisible" />
        <include layout="@layout/all_apps"
            android:id="@+id/apps_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="invisible" />
    </com.android.launcher3.DragLayer>
</com.android.launcher3.LauncherRootView>

下面是Launcher3中一些類的大致含義:
Launcher:主界面Activity,最核心且唯一的Activity。
LauncherAppState:單例對象,構造方法中初始化對象、註冊應用安裝、卸載、更新,配置變化等廣播。這些廣播用來實時更新桌面圖標等,其receiver的實現在LauncherModel類中,LauncherModel也在這裏初始化。
LauncherModel:數據處理類,保存桌面狀態,提供讀寫數據庫的API,內部類LoaderTask用來初始化桌面。
InvariantDeviceProfile:一些不變的設備相關參數管理類,其內部包涵了橫豎屏模式的DeviceProfile。
WidgetPreviewLoader:存儲Widget信息的數據庫,內部創建了數據庫widgetpreviews.db。
LauncherAppsCompat:獲取已安裝App列表信息的兼容抽象基類,子類依據不同版本API進行兼容性處理。
AppWidgetManagerCompat:獲取AppWidget列表的兼容抽象基類,子類依據不同版本API進行兼容性處理。
LauncherStateTransitionAnimation:各類動畫總管處理執行類,負責各種情況下的各種動畫效果處理。
IconCache:圖標緩存類,應用程序icon和title的緩存,內部類創建了數據庫app_icons.db。
LauncherProvider:核心數據庫類,負責launcher.db的創建與維護。
LauncherAppWidgetHost:AppWidgetHost子類,是桌面插件宿主,爲了方便託拽等才繼承處理的。
LauncherAppWidgetHostView:AppWidgetHostView子類,配合LauncherAppWidgetHost得到HostView。
LauncherRootView:豎屏模式下根佈局,繼承了InsettableFrameLayout,控制是否顯示在狀態欄等下面。
DragLayer:一個用來負責分發事件的ViewGroup。
DragController:DragLayer只是一個ViewGroup,具體的拖拽的處理都放到了DragController中。
BubblTextView:圖標都基於他,繼承自TextView。
DragView:拖動圖標時跟隨手指移動的View。
Folder:打開文件夾展示的View。
FolderIcon:文件夾圖標。
DragSource/DropTarget:拖拽接口,DragSource表示圖標從哪開始拖,DropTarget表示圖標被拖到哪去。
ItemInfo:桌面上每個Item的信息數據結構,包括在第幾屏、第幾行、第幾列、寬高等信息;該對象與數據庫中記錄一一對應;該類有多個子類,譬如FolderIcon的FolderInfo、BubbleTextView的ShortcutInfo等。

瞭解上面這些類後,現在來看看Launcher3的啓動流程:(小提示:若看不清圖片可將網頁放大至200%)
Launcher3啓動流程
由於Launcher3也是一個Activity,其啓動後首先會執行onCreate()方法,從流程圖中可以看出在該方法裏會調用LauncherAppState.getInstance()方法,Launcher3的各類數據的初始化和廣播的註冊都在這裏被執行。隨後執行LauncherModel mModel = app.setLauncher(this),將當前Launcher對象的引用傳給LauncherProvider,在該方法裏調用了LauncherModel的initialize(Callbacks callbacks)方法,因爲Launcher也實現了LauncherModel.Callbacks接口,因此這裏將Launcher和LauncherModel建立了聯繫,LauncherModel中的所有操作都會通過Callbacks接口中的方法傳給Launcher。可以來看看LauncherModel.Callbacks接口。

public interface Callbacks {
        //如果Launcher在加載完成之前被強制暫停,那麼需要通過這個回調方法通知Launcher
        //在它再次顯示的時候重新執行加載過程
        public boolean setLoadOnResume();
        //獲取當前屏幕序號
        public int getCurrentWorkspaceScreen();
        //啓動桌面數據綁定
        public void startBinding();
        //批量綁定桌面組件:快捷方式列表,列表的開始位置,列表結束的位置,是否使用動畫
        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
                              boolean forceAnimateIcons);
        //批量綁定桌面頁,orderedScreenIds 序列化後的桌面頁列表
        public void bindScreens(ArrayList<Long> orderedScreenIds);
        public void bindAddScreens(ArrayList<Long> orderedScreenIds);
        //批量綁定文件夾,folders 文件夾映射列表
        public void bindFolders(LongArrayMap<FolderInfo> folders);
        //完成綁定
        public void finishBindingItems();
        //批量綁定小部件,info 需要綁定到桌面上的小部件信息
        public void bindAppWidget(LauncherAppWidgetInfo info);
        //綁定應用程序列表界面的應用程序信息,apps 需要綁定到應用程序列表中的應用程序列表
        public void bindAllApplications(ArrayList<AppInfo> apps);
        //批量添加組件
        public void bindAppsAdded(ArrayList<Long> newScreens,
                                  ArrayList<ItemInfo> addNotAnimated,
                                  ArrayList<ItemInfo> addAnimated,
                                  ArrayList<AppInfo> addedApps);
        //批量更新應用程序相關的快捷方式或者入口
        public void bindAppsUpdated(ArrayList<AppInfo> apps);
        public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
                ArrayList<ShortcutInfo> removed, UserHandleCompat user);
        //當Widget被重置的時候調用
        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
        public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
        public void bindWorkspaceComponentsRemoved(
                HashSet<String> packageNames, HashSet<ComponentName> components,
                UserHandleCompat user);
        public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
        public void notifyWidgetProvidersChanged();
        public void bindWidgetsModel(WidgetsModel model);
        public void bindSearchProviderChanged();
        public boolean isAllAppsButtonRank(int rank);
        //指示正在綁定的頁面
        public void onPageBoundSynchronously(int page);
        //輸出當前Launcher信息到本地文件中
        public void dumpLogsToLocalData();
}

前面說過Launcher也是一個Activity,所以它也需要執行setContentView()將佈局文件顯示出來。之後分別調用setupViews()、mDeviceProfile.layout(this)、restoreState(mSavedState)
我們先來看看setupViews()。

private void setupViews() {
    ......
    mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
    mWorkspace.setPageSwitchListener(this);
    mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
    ......
    // Setup the hotseat
    mHotseat = (Hotseat) findViewById(R.id.hotseat);
    if (mHotseat != null) {
        mHotseat.setOnLongClickListener(this);
    }
    // Setup the overview panel
    setupOverviewPanel();
    .....
    if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) {                      mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
    } else {
        mAppsView.setSearchBarController(new DefaultAppSearchController());
    }
}

可以看到setUpViews()的代碼就是執行一系列的findViewById操作,並對控件設置各種監聽和綁定。而mDeviceProfile.layout(this)所幹的事大概就可以猜測是將這些控件進行佈局。
看到layout裏的代碼也證實了我的猜想。

public void layout(Launcher launcher) {
    FrameLayout.LayoutParams lp;
    boolean hasVerticalBarLayout = isVerticalBarLayout();
    final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
    ......
    // Layout the page indicators
    View pageIndicator = launcher.findViewById(R.id.page_indicator);
    if (pageIndicator != null) {
        if (hasVerticalBarLayout) {
            // Hide the page indicators when we have vertical search/hotseat
            pageIndicator.setVisibility(View.GONE);
        } else {
            // Put the page indicators above the hotseat
            lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
            lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
            lp.width = LayoutParams.WRAP_CONTENT;
            lp.height = LayoutParams.WRAP_CONTENT;
            lp.bottomMargin = hotseatBarHeightPx;
            pageIndicator.setLayoutParams(lp);
        }
    }
    ......
}

執行上述方法之後,源碼中還執行了一個restoreState ()方法,當onCreate()方法中的參數savedInstanceState不爲空時纔會進行相應的操作,該方法的作用就是恢復以前的狀態。由於第一次啓動Launcher時不會執行該方法,因此暫不進行分析。
隨後就開始執行一個比較重要的方法,LauncherModel#startLoader()。

if (!mRestoring) {
    if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
        // If the user leaves launcher, then we should just load items asynchronously when
        // they return.
        mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
    } else {
        // We only load the page synchronously if the user rotates (or triggers a
        // configuration change) while launcher is in the foreground
        mModel.startLoader(mWorkspace.getRestorePage());
    }
}

我們進入到LauncherModel的startLoader(),發現這是個重載的方法,最後都會執行public void startLoader(int synchronousBindPage, int loadFlags) {},該方法裏最重要的就是創建了LoaderTask實例並執行其run()方法。

public void startLoader(int synchronousBindPage, int loadFlags) {
    synchronized (mLock) {
        ......
        if (mCallbacks != null && mCallbacks.get() != null) {
            // If there is already one running, tell it to stop.
            stopLoaderLocked();
            ......
            mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
            .....
            if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
                    && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
                mLoaderTask.runBindSynchronousPage(synchronousBindPage);
            } else {
                //第一次啓動會執行
                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                sWorker.post(mLoaderTask);
            }
        }
    }
}

在LoadTask的run()方法裏主要有以下幾步操作:
loadAndBindWorkspace()->waitForIdle()->loadAndBindAllApps()
我們先來看loadAndBindWorkspace()

private void loadAndBindWorkspace() {
    mIsLoadingAndBindingWorkspace = true;
    ......
    if (!mWorkspaceLoaded) {
        loadWorkspace();
        synchronized (LoaderTask.this) {
            if (mStopped) {
                LauncherLog.d(TAG, "loadAndBindWorkspace returned by stop flag.");
                return;
            }
            mWorkspaceLoaded = true;
        }
    }
    // Bind the workspace
    bindWorkspace(-1);
}

mWorkspaceLoaded這個標識主要用來判斷workspace是否已經加載過,如果沒有,則先加載再進行綁定。我們先看看loadWorkspace(),其主要功能就是負責從數據庫表中讀取數據並轉換爲Launcher的數據結構。

private void loadWorkspace() {
    if(){
        ......
    }else{
        //加載默認值 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
    }
    synchronized (sBgLock) {
        // 清空之前的內存數據(sBgWorkspaceItems,sBgAppWidgets等)  
        clearSBgDataStructures();
        // 存儲無效數據的id,在後面統一從數據庫中刪掉  
        final ArrayList<Long> itemsToRemove = new ArrayList<>();
        // 查詢ContentProvider,返回favorites表的結果集    
        final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
        final Cursor c = contentResolver.query(contentUri, null, null, null, null);
        try {
        // 獲取數據庫每一列的索引值
            final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
            final int intentIndex = c.getColumnIndexOrThrow
                    (LauncherSettings.Favorites.INTENT);
            ......
            //查詢ContentProvider
            while (!mStopped && c.moveToNext()) {
                try {
                    //根據不同的itemType類型,將結果存儲到相應的集合裏
                    ......
                    switch (itemType) {
                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                        ......
                        break;
                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                       ......
                        break;
                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                    case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
                       ......
                        break;
                    }
                } catch (Exception e) {
                    ......
                }
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
        // Break early if we've stopped loading
        if (mStopped) {
            clearSBgDataStructures();
            return;
        }
        // 對文件夾排序、contentResolver.update 、註冊廣播、移除空的屏幕
        ......
    }
}

而bindWorkspace()則是將loadWorkspace()方法裏獲取到的數據顯示在Launcher上。

/**
 * Binds all loaded data to actual views on the main thread.
 */
private void bindWorkspace(int synchronizeBindPage) {
    ......
    // Save a copy of all the bg-thread collections
    ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
    ......
    // Load all the items that are on the current page first (and in the process, unbind
    // all the existing workspace items before we call startBinding() below.
    unbindWorkspaceItemsOnMainThread();
    ......
    // Tell the workspace that we're about to start binding items
    r = new Runnable() {
        public void run() {
            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
            if (callbacks != null) {
                callbacks.startBinding();
            }
        }
    };
    runOnMainThread(r);

    bindWorkspaceScreens(oldCallbacks, orderedScreenIds);

    // Load items on the current page
    bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
            currentFolders, null);
    if (isLoadingSynchronously) {
        r = new Runnable() {
            public void run() {
                Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
                    callbacks.onPageBoundSynchronously(currentScreen);
                }
            }
        };
        runOnMainThread(r);
    }

    // Load all the remaining pages (if we are loading synchronously, we want to defer this
    // work until after the first render)
    synchronized (mDeferredBindRunnables) {
        mDeferredBindRunnables.clear();
    }
    bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
            (isLoadingSynchronously ? mDeferredBindRunnables : null));

    // Tell the workspace that we're done binding items
    r = new Runnable() {
        public void run() {
            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
            if (callbacks != null) {
                callbacks.finishBindingItems();
            }

            mIsLoadingAndBindingWorkspace = false;

            // Run all the bind complete runnables after workspace is bound.
            if (!mBindCompleteRunnables.isEmpty()) {
                synchronized (mBindCompleteRunnables) {
                    for (final Runnable r : mBindCompleteRunnables) {
                        runOnWorkerThread(r);
                    }
                    mBindCompleteRunnables.clear();
                }
            }
        }
    };
    if (isLoadingSynchronously) {
        synchronized (mDeferredBindRunnables) {
            mDeferredBindRunnables.add(r);
        }
    } else {
        runOnMainThread(r);
    }
}

bindWorkspace()的流程主要可以概括爲:unbindWorkspaceItemsOnMainThread()->callbacks.startBinding()->bindWorkspaceScreens()-> bindWorkspaceItems()->callbacks.onPageBoundSynchronously(currentScreen)-> mDeferredBindRunnables.clear()->bindWorkspaceItems()->mBindCompleteRunnables.clear();
由於篇幅的限制這裏就暫不做更深入的研究。
我們回到LoadTask的run()方法,執行完loadAndBindWorkspace()之後還執行了一個方法waitForIdle(),這個方法是用來幹什麼的呢?
這個方法裏的註釋告訴我們這個方法會等待其他線程執行完,直到workspace設置好了之後纔開始執行loadAndBindAllApps(),相當於在這裏阻塞線程。

private void waitForIdle() {
    // Wait until the either we're stopped or the other threads are done.
    // This way we don't start loading all apps until the workspace has settled down.
    synchronized (LoaderTask.this) {
    ......
        while (!mStopped && !mLoadAndBindStepFinished) {
            try {
                // wait no longer than 1sec at a time
                this.wait(1000);
            } catch (InterruptedException ex) {
                // Ignore
            }
        }
    }
}

至於loadAndBindAllApps(),就是加載主菜單的數據。現在很多國產的ROM在界面上已經看不到AllApps,猜測可能將這個方法屏蔽了。這個方法裏的執行過程也很簡單。首先會判斷AllApps是否加載,如果沒有加載則會先執行loadAllApps()、updateIconCache(),並賦值mAllAppsLoaded爲true。若已經加載了則直接執行onlyBindAllApps()方法。

private void loadAndBindAllApps() {
    ......
    if (!mAllAppsLoaded) {
        loadAllApps();
        synchronized (LoaderTask.this) {
            if (mStopped) {
                return;
            }
        }
        updateIconCache();
        synchronized (LoaderTask.this) {
            if (mStopped) {
                return;
            }
            mAllAppsLoaded = true;
        }
    } else {
        onlyBindAllApps();
    }
}

這整個過程和加載綁定workspace()類似。我們先看一下loadAllApps()方法。
loadAllApps流程圖

private void loadAllApps() {
    ......
    final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
    // Clear the list of apps
    mBgAllAppsList.clear();

    //遍歷賬戶列表
    for (UserHandleCompat user : profiles) {
        // Query for the set of apps
        final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
        final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
        ......              
        boolean quietMode = mUserManager.isQuietModeEnabled(user);
        // Create the ApplicationInfos,將應用加入到緩衝區
        for (int i = 0; i < apps.size(); i++) {
            LauncherActivityInfoCompat app = apps.get(i);                
            // This builds the icon bitmaps.
            mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
        }
        //創建與該用戶相關聯的篩選器實例
        final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);

        if (heuristic != null) {
            final Runnable r = new Runnable() {
                @Override
                public void run() {
                    //創建按賬戶分類應用程序的任務
                    heuristic.processUserApps(apps);
                }
            };
            runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    // Check isLoadingWorkspace on the UI thread, as it is updated on
                    // the UI thread.
                    if (mIsLoadingAndBindingWorkspace) {
                        synchronized (mBindCompleteRunnables) {
                            mBindCompleteRunnables.add(r);
                        }
                    } else {
                        runOnWorkerThread(r);
                    }
                }
            });
        }
    }
    // Huh? Shouldn't this be inside the Runnable below?
    final ArrayList<AppInfo> added = mBgAllAppsList.added;
    mBgAllAppsList.added = new ArrayList<AppInfo>();

    // Post callback on main thread
    mHandler.post(new Runnable() {
        public void run() {
           ......
            if (callbacks != null) {
            //綁定應用程序,因爲Launcher這個Activity實現了
            //LauncherModel.CallBacks接口,因此這句相當於調用LauncherModel
            //的bindAllApplications方法
                callbacks.bindAllApplications(added);          
                ......
            }
        }
    });
    // Cleanup any data stored for a deleted user.
    ......
}

onlyBindAllApps()所執行的和loadAllApps()方法大同小異,這裏就不做分析。

至此我已經把Launcher3的啓動和加載數據的流程大致走了一遍。其中還有很多細節沒有考慮到,希望以後再看的時候能夠及時查漏補缺。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章