Launcher3桌面開(kāi)發(fā)(3)-Launcher3 桌面加載流程分析(下)

主目錄見(jiàn):Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)
Launcher3源碼地址:Launcher3-master
[This tutorial was written by Ticoo]

上文Launcher3 桌面加載流程分析(上)句携,我們看到
LauncherModel 創(chuàng)建LoaderTask加載數(shù)據(jù)固耘,我們繼續(xù)往下看

LoaderTask

創(chuàng)建LoaderTask,flags為 PagedView.INVALID_RESTORE_PAGE值-1001, 我們看它的run方法是如何執(zhí)行的。


private class LoaderTask implements Runnable {
   
    LoaderTask(Context context, int flags) {
        mContext = context;
        mFlags = flags;
    }

    public void run() {
        ...
        
        keep_running: {
            if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
            loadAndBindWorkspace();

            if (mStopped) {
                break keep_running;
            }

            waitForIdle();

            // second step
            if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
            loadAndBindAllApps();
        }
        ...
    }
}
        

根據(jù)mStopped的狀態(tài)做一些預(yù)先的判斷外,最先執(zhí)行的是 loadAndBindWorkspace()方法,加載和綁定Workspace的數(shù)據(jù),包括屏幕數(shù),應(yīng)用數(shù)據(jù)哗戈,widget組件信息等等,然后調(diào)用waitForIdle() 等待loadAndBindWorkspace()里創(chuàng)建的一些子線(xiàn)程執(zhí)行完荷科,修改mStopped和mLoadAndBindStepFinished的狀態(tài)后執(zhí)行l(wèi)oadAndBindAllApps()唯咬,加載所有應(yīng)用,完成整個(gè)加載應(yīng)用的流程畏浆。
整體流程就是上面的run方法胆胰,具體的細(xì)節(jié)我們一步步來(lái)看。

Workspace

Workspace是什么呢刻获?大家自己看下Launcher3的主布局文件launcher.xml布局就很明了蜀涨,workspace是Launcher的工作臺(tái),承載應(yīng)用數(shù)據(jù),widget組件數(shù)據(jù)勉盅,文件夾數(shù)據(jù)以及其他的功能。

加載workspace的流程分兩步顶掉,

  1. 加載數(shù)據(jù)草娜,loadWorkspace()
  2. 綁定workspace, bindWorkspace()

流程如下


    private void loadAndBindWorkspace() {
        mIsLoadingAndBindingWorkspace = true;        
        // Load the workspace
        if (DEBUG_LOADERS) {
            Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
        }

        if (!mWorkspaceLoaded) {
            loadWorkspace();
            synchronized (LoaderTask.this) {
                if (mStopped) {
                    return;
                }
                mWorkspaceLoaded = true;
            }
        }

        // Bind the workspace
        bindWorkspace(-1);
    }

加載WorkSpace數(shù)據(jù)

加載WorkSpace數(shù)據(jù)的方法都在loadWorkSpace()里, 這個(gè)步驟是整個(gè)流程最核心的痒筒,雖然只有l(wèi)oadWorkSpace()這個(gè)方法宰闰,但是我目前的這個(gè)版本該方法的源碼就達(dá)到600多行,所以我們截取核心的代碼來(lái)分析簿透,很多細(xì)節(jié)還是要大家自己去琢磨移袍。


    final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

    final Context context = mContext;
    final ContentResolver contentResolver = context.getContentResolver();
    final PackageManager manager = context.getPackageManager();
    final boolean isSafeMode = manager.isSafeMode();
    final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
    final boolean isSdCardReady = context.registerReceiver(null,
            new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;

    LauncherAppState app = LauncherAppState.getInstance();
    InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
    int countX = profile.numColumns;
    int countY = profile.numRows;

    if (GridSizeMigrationTask.ENABLED &&
            !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) {
        // Migration failed. Clear workspace.
        mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
    }

    if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
        Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
        LauncherAppState.getLauncherProvider().deleteDatabase();
    }

    if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
        // append the user's Launcher2 shortcuts
        Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
        LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
    } else {
        // Make sure the default workspace is loaded
        Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
        LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
    }

首先聲明了一些核心對(duì)象,ContentResolver老充,LauncherAppsCompat, LauncherAppState,InvariantDeviceProfile這些上一篇已經(jīng)有介紹過(guò)就不再贅述葡盗。
前幾個(gè)if條件,是關(guān)于數(shù)據(jù)庫(kù)移植的啡浊,比如Lacuncher2觅够,升級(jí)到Launcher3,桌面圖標(biāo)大小發(fā)生變化的特殊場(chǎng)合處理巷嚣,不是我們需要特別留意的喘先。

加載WorkSpace資源文件

關(guān)鍵的代碼是 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(); 在首次打開(kāi)Launcher時(shí),會(huì)加載默認(rèn)的數(shù)據(jù)廷粒,比如桌面首頁(yè)顯示什么內(nèi)容窘拯,hotseat配置等等“泳ィ看LauncherAppState代碼發(fā)現(xiàn)調(diào)用的是 LauncherProvider的loadDefaultFavoritesIfNecessary方法


    /**
        * Loads the default workspace based on the following priority scheme:
        *   1) From the app restrictions
        *   2) From a package provided by play store
        *   3) From a partner configuration APK, already in the system image
        *   4) The default configuration for the particular device
        */
    synchronized public void loadDefaultFavoritesIfNecessary() {
        SharedPreferences sp = Utilities.getPrefs(getContext());

        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
            Log.d(TAG, "loading default workspace");

            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction();
            if (loader == null) {
                loader = AutoInstallsLayout.get(getContext(),
                        mOpenHelper.mAppWidgetHost, mOpenHelper);
            }
            if (loader == null) {
                final Partner partner = Partner.get(getContext().getPackageManager());
                if (partner != null && partner.hasDefaultLayout()) {
                    final Resources partnerRes = partner.getResources();
                    int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                            "xml", partner.getPackageName());
                    if (workspaceResId != 0) {
                        loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
                                mOpenHelper, partnerRes, workspaceResId);
                    }
                }
            }

            final boolean usingExternallyProvidedLayout = loader != null;
            if (loader == null) {
                loader = getDefaultLayoutParser();
            }

            // There might be some partially restored DB items, due to buggy restore logic in
            // previous versions of launcher.
            createEmptyDB();
            // Populate favorites table with initial favorites
            if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                    && usingExternallyProvidedLayout) {
                // Unable to load external layout. Cleanup and load the internal layout.
                createEmptyDB();
                mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                        getDefaultLayoutParser());
            }
            clearFlagEmptyDbCreated();
        }
    }

正常流程不會(huì)都執(zhí)行涤姊,故簡(jiǎn)單介紹一下,該方法跟注釋一樣嗤放,會(huì)從以下幾種方式中的一種加載默認(rèn)布局

  1. 應(yīng)用約束砂轻,調(diào)用createWorkspaceLoaderFromAppRestriction,獲取用戶(hù)設(shè)置的一組用于限制應(yīng)用功能的Bundle串斤吐,獲取Bundle里workspace.configuration.package.name具體的應(yīng)用包名搔涝,獲取WorkSpace默認(rèn)配置資源。
  2. 從帶有 android.autoinstalls.config.action.PLAY_AUTO_INSTALL Action的應(yīng)用里獲取workspace默認(rèn)配置資源
  3. 從系統(tǒng)內(nèi)置的partner應(yīng)用里獲取workspace默認(rèn)配置
  4. 調(diào)用getDefaultLayoutParser() 獲取我們Launcher里的默認(rèn)資源

默認(rèn)流程和措,會(huì)執(zhí)行第四步, 然后創(chuàng)建數(shù)據(jù)庫(kù)庄呈,建表favorites和workspaceScreens,加載數(shù)據(jù)mOpenHelper.loadFavorites


    private DefaultLayoutParser getDefaultLayoutParser() {
        int defaultLayout = LauncherAppState.getInstance()
                .getInvariantDeviceProfile().defaultLayoutId;
        return new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost,
                mOpenHelper, getContext().getResources(), defaultLayout);
    }


而默認(rèn)的資源就是我們配置 InvariantDeviceProfile的資源如R.xml.default_workspace_5x6派阱,詳細(xì)見(jiàn)上一篇文章诬留。故我們可以在res/xml/里修改我們的默認(rèn)顯示應(yīng)用的配置。


    @Thunk 
    int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
        ArrayList<Long> screenIds = new ArrayList<Long>();
        // TODO: Use multiple loaders with fall-back and transaction.
        int count = loader.loadLayout(db, screenIds);

        // Add the screens specified by the items above
        Collections.sort(screenIds);
        int rank = 0;
        ContentValues values = new ContentValues();
        for (Long id : screenIds) {
            values.clear();
            values.put(LauncherSettings.WorkspaceScreens._ID, id);
            values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
            if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, 
                null, values) < 0) {
                throw new RuntimeException("Failed initialize screen table"
                        + "from default layout");
            }
            rank++;
        }

        // Ensure that the max ids are initialized
        mMaxItemId = initializeMaxItemId(db);
        mMaxScreenId = initializeMaxScreenId(db);

        return count;
    }

loadFavorites方法里調(diào)用DefaultLayoutParser.loadLayout(db, screenIds) 解析布局xml里的文件夾信息,應(yīng)用信息文兑,widget信息等等保存到數(shù)據(jù)庫(kù), 并獲取到屏幕id集合盒刚,保存到workspaceScreens表中。至于怎么解析的绿贞,我們直接看關(guān)鍵的代碼

AutoInstallsLayout.java

     /**
        * Parses the layout and returns the number of elements added on the homescreen.
        */
    protected int parseLayout(int layoutId, ArrayList<Long> screenIds)
            throws XmlPullParserException, IOException {
        XmlResourceParser parser = mSourceRes.getXml(layoutId);
        beginDocument(parser, mRootTag);
        final int depth = parser.getDepth();
        int type;
        HashMap<String, TagParser> tagParserMap = getLayoutElementsMap();
        int count = 0;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            count += parseAndAddNode(parser, tagParserMap, screenIds);
        }
        return count;
    }

通過(guò)XmlResourceParser 解析xml文件,獲取我們需要的配置因块。不同的標(biāo)簽通過(guò)不同的解析對(duì)象處理,我們使用的是AutoInstallsLayout的子類(lèi)DefaultLayoutParser籍铁,在getLayoutElementsMap()方法里涡上,我們可以看到使用的處理對(duì)象,如應(yīng)用解析器ResolveParser拒名,文件夾解析器MyFolderParser等等吩愧,解析到信息后會(huì)保存到對(duì)應(yīng)的數(shù)據(jù)庫(kù)中。這就是加載默認(rèn)workspace的原理增显,解析的細(xì)節(jié)就不一一介紹了雁佳,請(qǐng)大家自己找到需要的解析器琢磨代碼了哦

DefaultLayoutParser.java

    @Override
    protected HashMap<String, TagParser> getLayoutElementsMap() {
        HashMap<String, TagParser> parsers = new HashMap<String, TagParser>();
        parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
        parsers.put(TAG_APPWIDGET, new AppWidgetParser());
        parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
        parsers.put(TAG_RESOLVE, new ResolveParser());
        parsers.put(TAG_FOLDER, new MyFolderParser());
        parsers.put(TAG_PARTNER_FOLDER, new PartnerFolderParser());
        return parsers;
    }

我們簡(jiǎn)單的介紹一個(gè)默認(rèn)配置,

default_workspace_4x4.xml

<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto">

    <!-- Hotseat -->
    <include launcher:workspace="@xml/dw_phone_hotseat" />

    <!-- Bottom row -->
    <resolve
        launcher:screen="0"
        launcher:x="0"
        launcher:y="-1" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" />
        <favorite launcher:uri="mailto:" />
    </resolve>

    <resolve
        launcher:screen="0"
        launcher:x="1"
        launcher:y="-1" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_GALLERY;end" />
        <favorite launcher:uri="#Intent;type=images/*;end" />
    </resolve>

    <resolve
        launcher:screen="0"
        launcher:x="3"
        launcher:y="-1" >
        <favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MARKET;end" />
        <favorite launcher:uri="market://details?id=com.android.launcher" />
    </resolve>

</favorites>

  1. @xml/dw_phone_hotseat, hotseat配置文件同云,配置規(guī)則一樣
  2. resolve標(biāo)簽甘穿, 通過(guò)ResolveParser解析,包含內(nèi)嵌標(biāo)簽
  3. favorite, 一個(gè)app的信息梢杭,可以指定uri温兼,或具體的包名,類(lèi)名來(lái)識(shí)別app
  4. 可以有自定義的標(biāo)簽武契,自己實(shí)現(xiàn)解析即可

至此募判,loadDefaultFavoritesIfNecessary()就執(zhí)行完成,我們回到LauncherModel繼續(xù)看loadWorkspace()

讀取數(shù)據(jù)

接下來(lái)就是從數(shù)據(jù)庫(kù)讀取從配置文件讀到的信息咒唆,根據(jù)itemType, 走switch的不同case


    final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
    if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
    final Cursor c = contentResolver.query(contentUri, null, null, null, null);

    // +1 for the hotseat (it can be larger than the workspace)
    // Load workspace in reverse order to ensure that latest items are loaded first (and
    // before any earlier duplicates)
    final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
    HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
    ...
    while (!mStopped && c.moveToNext()) {
        try {
            int itemType = c.getInt(itemTypeIndex);
            boolean restored = 0 != c.getInt(restoredIndex);
            boolean allowMissingTarget = false;
            container = c.getInt(containerIndex);

            switch (itemType) {
            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                ...
                
                sBgItemsIdMap.put(info.id, info);
                ...
                break;

            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                ...
                sBgItemsIdMap.put(folderInfo.id, folderInfo);
                sBgFolders.put(folderInfo.id, folderInfo);
                ...
                break;
            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
        ...

比如 ITEM_TYPE_APPLICATION和ITEM_TYPE_SHORTCUT届垫,執(zhí)行到最后會(huì)保存到LongArrayMap sBgItemsIdMap里。而ITEM_TYPE_FOLDER全释,保存到LongArrayMap sBgFolders装处,同時(shí)也保存到sBgItemsIdMap,因?yàn)镕olderInfo和應(yīng)用的AppInfo浸船,ShortcutInfo都是繼承ItemInfo妄迁,后續(xù)可以轉(zhuǎn)型處理。ITEM_TYPE_APPWIDGET,ITEM_TYPE_CUSTOM_APPWIDGET同理李命。

這里的細(xì)節(jié)比較多登淘,通常我們也不用特地修正這里的查詢(xún)邏輯,故不做詳細(xì)闡述封字。

綁定WorkSpace數(shù)據(jù)

加載完workspace數(shù)據(jù)后黔州,往下就是講數(shù)據(jù)綁定到workspace耍鬓,調(diào)用bindWorkspace(-1)方法。上一步我流妻, 知道數(shù)據(jù)都保存在集合sBgWorkspaceItems牲蜀,sBgAppWidgets,sBgWorkspaceScreens里绅这,這里Google不是直接遍歷里面的數(shù)據(jù)涣达,綁定到View上。而是做了一個(gè)copy的操作君躺,避免后續(xù)的某個(gè)線(xiàn)程修改全局變量影響到其他的工作線(xiàn)程峭判。

        private void bindWorkspace(int synchronizeBindPage) {
            // Save a copy of all the bg-thread collections
            ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
            ArrayList<LauncherAppWidgetInfo> appWidgets =
                    new ArrayList<LauncherAppWidgetInfo>();
            ArrayList<Long> orderedScreenIds = new ArrayList<Long>();

            final LongArrayMap<FolderInfo> folders;
            final LongArrayMap<ItemInfo> itemsIdMap;

            synchronized (sBgLock) {
                workspaceItems.addAll(sBgWorkspaceItems);
                appWidgets.addAll(sBgAppWidgets);
                orderedScreenIds.addAll(sBgWorkspaceScreens);

                folders = sBgFolders.clone();
                itemsIdMap = sBgItemsIdMap.clone();
            }

            final boolean isLoadingSynchronously =
                    synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
            int currScreen = isLoadingSynchronously ? synchronizeBindPage :
                oldCallbacks.getCurrentWorkspaceScreen();
            if (currScreen >= orderedScreenIds.size()) {
                // There may be no workspace screens (just hotseat items and an empty page).
                currScreen = PagedView.INVALID_RESTORE_PAGE;
            }
            final int currentScreen = currScreen;
            final long currentScreenId = currentScreen < 0
                    ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);

            // 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();

            // Separate the items that are on the current screen, and all the other remaining items
            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
            ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
                    new ArrayList<LauncherAppWidgetInfo>();
            ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
                    new ArrayList<LauncherAppWidgetInfo>();
            LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
            LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();

            filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
                    otherWorkspaceItems);
            filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
                    otherAppWidgets);
            filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
                    otherFolders);
            sortWorkspaceItemsSpatially(currentWorkspaceItems);
            sortWorkspaceItemsSpatially(otherWorkspaceItems);
            // 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);
            Log.e(TAG, "orderedScreenIds:" + orderedScreenIds);

之后一系列filter和sort方法开缎,將當(dāng)前需要加載頁(yè)的數(shù)據(jù)棕叫,按screenId排序,并填充到新的集合里. 準(zhǔn)備工作也就完成了奕删,然后通過(guò) tryGetCallbacks 獲取到一個(gè)Callbacks, 這個(gè)Callbacks也就是LauncherModel的mCallback俺泣,初始化是在Launcher onCreate里調(diào)用LauncherAppState.setLauncher,在LauncherModel的initialize()里完成賦值完残。 故伏钠,Callbacks就是我們的Launcher。

調(diào)用Launcher里實(shí)現(xiàn)的 startBinding(), 改變workspace的狀態(tài)谨设,移除一些舊的View和數(shù)據(jù)

    /**
     * Refreshes the shortcuts shown on the workspace.
     * <p>
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void startBinding() {
        setWorkspaceLoading(true);

        // If we're starting binding all over again, clear any bind calls we'd postponed in
        // the past (see waitUntilResume) -- we don't need them since we're starting binding
        // from scratch again
        mBindOnResumeCallbacks.clear();

        // Clear the workspace because it's going to be rebound
        mWorkspace.clearDropTargets();
        mWorkspace.removeAllWorkspaceScreens();

        mWidgetsToAdvance.clear();
        if (mHotseat != null) {
            mHotseat.resetLayout();
        }
    }

之后就依次開(kāi)始綁定

  1. 綁定WorkSpace的Screen熟掂,bindWorkspaceScreens
  2. 綁定Workspace當(dāng)前頁(yè)的Items,包括應(yīng)用信息扎拣,組件信息赴肚,bindWorkspaceItems
  3. 綁定Workspace其他頁(yè)的Items
bindWorkspace(int synchronizeBindPage)方法片段:

            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));
        

我們看到bindWorkspaceScreens,bindWorkspaceItems里最終調(diào)用的是
Launcher里的回調(diào)方法二蓝,bindScreens誉券,bindItems,bindFolders等等.
以 綁定Screen為例

    @Override
    public void bindScreens(ArrayList<Long> orderedScreenIds) {
        bindAddScreens(orderedScreenIds);

        // If there are no screens, we need to have an empty screen
        if (orderedScreenIds.size() == 0) {
            mWorkspace.addExtraEmptyScreen();
        }

        // Create the custom content page (this call updates mDefaultScreen which calls
        // setCurrentPage() so ensure that all pages are added before calling this).
        if (hasCustomContentToLeft()) {
            mWorkspace.createCustomContentContainer();
            populateCustomContentContainer();
        }
    }

    @Override
    public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
        int count = orderedScreenIds.size();
        for (int i = 0; i < count; i++) {
     mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
        }
    }

根據(jù)獲取到Screen Id集合刊愚,調(diào)用Workspace的insertNewWorkspaceScreenBeforeEmptyScreen踊跟,
創(chuàng)建相對(duì)應(yīng)的CellLayout,并添加到我們的Workspace這個(gè)容器里


    public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
        // Find the index to insert this view into.  If the empty screen exists, then
        // insert it before that.
        int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
        if (insertIndex < 0) {
            insertIndex = mScreenOrder.size();
        }
        return insertNewWorkspaceScreen(screenId, insertIndex);
    }


    public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
        if (mWorkspaceScreens.containsKey(screenId)) {
            throw new RuntimeException("Screen id " + screenId + " already exists!");
        }

        // Inflate the cell layout, but do not add it automatically so that we can get the newly
        // created CellLayout.
        CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(
                        R.layout.workspace_screen, this, false /* attachToRoot */);

        newScreen.setOnLongClickListener(mLongClickListener);
        newScreen.setOnClickListener(mLauncher);
        newScreen.setSoundEffectsEnabled(false);
        mWorkspaceScreens.put(screenId, newScreen);
        mScreenOrder.add(insertIndex, screenId);
        addView(newScreen, insertIndex);

        LauncherAccessibilityDelegate delegate =
                LauncherAppState.getInstance().getAccessibilityDelegate();
        if (delegate != null && delegate.isInAccessibleDrag()) {
            newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
        }
        return screenId;
    }

圖標(biāo)鸥诽,文件夾商玫,組件的創(chuàng)建流程都是類(lèi)似的,類(lèi)似的東西就不重復(fù)牡借,具體細(xì)節(jié)得各位慢慢琢磨决帖。

加載所有應(yīng)用

加載完workspace后,會(huì)加載所有應(yīng)用蓖捶,更新應(yīng)用圖標(biāo)地回。

    private void loadAndBindAllApps() {
        if (DEBUG_LOADERS) {
            Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
        }
        if (!mAllAppsLoaded) {
            loadAllApps();
            synchronized (LoaderTask.this) {
                if (mStopped) {
                    return;
                }
            }
            updateIconCache();
            synchronized (LoaderTask.this) {
                if (mStopped) {
                    return;
                }
                mAllAppsLoaded = true;
            }
        } else {
            onlyBindAllApps();
        }
    }

加載所有應(yīng)用,保存到mBgAllAppsList AllAppsList對(duì)象里。


    private void loadAllApps() {
        final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

        final Callbacks oldCallbacks = mCallbacks.get();
        if (oldCallbacks == null) {
            // This launcher has exited and nobody bothered to tell us.  Just bail.
            Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
            return;
        }

        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);
            if (DEBUG_LOADERS) {
                Log.d(TAG, "getActivityList took "
                        + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
                Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
            }
            // Fail if we don't have any apps
            // TODO: Fix this. Only fail for the current user.
            if (apps == null || apps.isEmpty()) {
                return;
            }
            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() {

                final long bindTime = SystemClock.uptimeMillis();
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                if (callbacks != null) {
                    callbacks.bindAllApplications(added);
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound " + added.size() + " apps in "
                                + (SystemClock.uptimeMillis() - bindTime) + "ms");
                    }
                } else {
                    Log.i(TAG, "not binding apps: no Launcher activity");
                }
            }
        });
        // Cleanup any data stored for a deleted user.
        ManagedProfileHeuristic.processAllUsers(profiles, mContext);
        if (DEBUG_LOADERS) {
            Log.d(TAG, "Icons processed in "
                    + (SystemClock.uptimeMillis() - loadTime) + "ms");
        }
    }

通過(guò)LauncherAppsCompat的對(duì)象刻像,拿到所有安裝的應(yīng)用畅买,遍歷添加到AllAppsList里。

    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
    

同綁定workspace數(shù)據(jù)一樣细睡,會(huì)調(diào)用Launcher里實(shí)現(xiàn)的回調(diào)方法 bindAllApplications谷羞,將數(shù)據(jù)填充到抽屜View容器里。

    /**
     * Add the icons for all apps.
     * <p>
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindAllApplications(final ArrayList<AppInfo> apps) {
        if (waitUntilResume(mBindAllApplicationsRunnable, true)) {
            mTmpAppsList = apps;
            return;
        }

        if (mAppsView != null) {
            mAppsView.setApps(apps);
        }
        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.bindAllApplications(apps);
        }
    }
    

這樣加載流程基本就結(jié)束了溜徙。
給大家梳理一個(gè)大致的流程湃缎,很多細(xì)節(jié)都沒(méi)有介紹,不懂的地方可以留言蠢壹,謝謝嗓违。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市图贸,隨后出現(xiàn)的幾起案子蹂季,更是在濱河造成了極大的恐慌,老刑警劉巖疏日,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偿洁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡沟优,警方通過(guò)查閱死者的電腦和手機(jī)涕滋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挠阁,“玉大人宾肺,你說(shuō)我怎么就攤上這事【槲ǎ” “怎么了爱榕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)坡慌。 經(jīng)常有香客問(wèn)我黔酥,道長(zhǎng),這世上最難降的妖魔是什么洪橘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任跪者,我火速辦了婚禮,結(jié)果婚禮上熄求,老公的妹妹穿的比我還像新娘渣玲。我一直安慰自己,他們只是感情好弟晚,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布忘衍。 她就那樣靜靜地躺著逾苫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枚钓。 梳的紋絲不亂的頭發(fā)上铅搓,一...
    開(kāi)封第一講書(shū)人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音搀捷,去河邊找鬼星掰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嫩舟,可吹牛的內(nèi)容都是我干的氢烘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼家厌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼播玖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起像街,我...
    開(kāi)封第一講書(shū)人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤黎棠,失蹤者是張志新(化名)和其女友劉穎晋渺,沒(méi)想到半個(gè)月后镰绎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡木西,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年畴栖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片八千。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吗讶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恋捆,到底是詐尸還是另有隱情照皆,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布沸停,位于F島的核電站膜毁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏愤钾。R本人自食惡果不足惜瘟滨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望能颁。 院中可真熱鬧杂瘸,春花似錦、人聲如沸伙菊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至运翼,卻和暖如春纯赎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背南蹂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工犬金, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人六剥。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓晚顷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親疗疟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子该默,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)策彤,斷路器栓袖,智...
    卡卡羅2017閱讀 134,702評(píng)論 18 139
  • 上文Launcher3 桌面加載流程分析(上),我們看到LauncherModel 創(chuàng)建LoaderTask加載數(shù)...
    憤怒皮卡閱讀 2,145評(píng)論 1 3
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,301評(píng)論 25 707
  • 上一篇墨香帶你學(xué)Launcher之-概述,我已經(jīng)介紹了Launcher的布局以及相關(guān)的界面跳轉(zhuǎn),今天我們繼續(xù)學(xué)習(xí),...
    翰墨飄香閱讀 13,225評(píng)論 26 29
  • 碧藍(lán)錫箔紙外殼下 它是薄荷清香辣嗓的口嚼糖 一場(chǎng)與口嚼糖的激吻在即 牙齒將它不斷剪開(kāi) 任由唾液滲入 他不滿(mǎn)地抓住牙...
    紅杉97閱讀 457評(píng)論 0 0