Android 9.0 Launcher源碼分析(二)——Launcher應(yīng)用啟動(dòng)流程舞肆,數(shù)據(jù)加載與綁定

轉(zhuǎn)載請(qǐng)注明原地址:http://www.reibang.com/p/725bdb3d08aa

上一篇文章中分析了系統(tǒng)是如何把桌面應(yīng)用拉起的。(見(jiàn)Android 9.0 Launcher源碼分析(一)——系統(tǒng)啟動(dòng)Launcher流程

現(xiàn)在接上文朦乏,分析一下Launcher應(yīng)用的啟動(dòng)流程岳颇。
首先把Launcher的onCreate貼出來(lái)。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (DEBUG_STRICT_MODE) {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()   // or .detectAll() for all detectable problems
                    .penaltyLog()
                    .build());
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectLeakedSqlLiteObjects()
                    .detectLeakedClosableObjects()
                    .penaltyLog()
                    .penaltyDeath()
                    .build());
        }
        TraceHelper.beginSection("Launcher-onCreate");

        super.onCreate(savedInstanceState);
        TraceHelper.partitionSection("Launcher-onCreate", "super call");

        LauncherAppState app = LauncherAppState.getInstance(this);
        mOldConfig = new Configuration(getResources().getConfiguration());
        mModel = app.setLauncher(this);
        initDeviceProfile(app.getInvariantDeviceProfile());

        mSharedPrefs = Utilities.getPrefs(this);
        mIconCache = app.getIconCache();
        mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);

        mDragController = new DragController(this);
        mAllAppsController = new AllAppsTransitionController(this);
        mStateManager = new LauncherStateManager(this);
        UiFactory.onCreate(this);

        mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);

        mAppWidgetHost = new LauncherAppWidgetHost(this);
        mAppWidgetHost.startListening();

        mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);

        setupViews();
        mPopupDataProvider = new PopupDataProvider(this);

        mRotationHelper = new RotationHelper(this);
        mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);

        boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
        if (internalStateHandled) {
            if (savedInstanceState != null) {
                // InternalStateHandler has already set the appropriate state.
                // We dont need to do anything.
                savedInstanceState.remove(RUNTIME_STATE);
            }
        }
        restoreState(savedInstanceState);

        // We only load the page synchronously if the user rotates (or triggers a
        // configuration change) while launcher is in the foreground
        int currentScreen = PagedView.INVALID_RESTORE_PAGE;
        if (savedInstanceState != null) {
            currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
        }

        if (!mModel.startLoader(currentScreen)) {
            if (!internalStateHandled) {
                // If we are not binding synchronously, show a fade in animation when
                // the first page bind completes.
                mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
            }
        } else {
            // Pages bound synchronously.
            mWorkspace.setCurrentPage(currentScreen);

            setWorkspaceLoading(true);
        }

        // For handling default keys
        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);

        setContentView(mLauncherView);
        getRootView().dispatchInsets();

        // Listen for broadcasts
        registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));

        getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
                Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onCreate(savedInstanceState);
        }
        mRotationHelper.initialize();

        TraceHelper.endSection("Launcher-onCreate");
    }

我們從頭開(kāi)始看叮贩,在super.onCreate過(guò)后击狮,首先調(diào)用了LauncherAppState.getInstance(this)來(lái)初始化一個(gè)單例對(duì)象。LauncherAppState里面保存了一些比較常用的對(duì)象妇汗,方便其他地方通過(guò)單例來(lái)獲取帘不,比如IconCache(圖標(biāo)緩存)说莫、LauncherModel(負(fù)責(zé)數(shù)據(jù)加載和處理各種回調(diào))等杨箭。getInstance函數(shù)如下,注意這里初始化使用的application的Context储狭,因?yàn)閱卫鳛閟tatic對(duì)象互婿,生命周期是與application生命周期一樣長(zhǎng)的,如果這里使用了Activity的Context辽狈,會(huì)導(dǎo)致activity退出后慈参,該Context依然被單例持有而無(wú)法回收,于是出現(xiàn)內(nèi)存泄露刮萌。

public static LauncherAppState getInstance(final Context context) {
        if (INSTANCE == null) {
            if (Looper.myLooper() == Looper.getMainLooper()) {
                INSTANCE = new LauncherAppState(context.getApplicationContext());
            } else {
                try {
                    return new MainThreadExecutor().submit(new Callable<LauncherAppState>() {
                        @Override
                        public LauncherAppState call() throws Exception {
                            return LauncherAppState.getInstance(context);
                        }
                    }).get();
                } catch (InterruptedException|ExecutionException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return INSTANCE;
    }

繼續(xù)看LauncherAppState的初始化過(guò)程驮配。這里面其實(shí)就是各個(gè)對(duì)象的實(shí)例創(chuàng)建過(guò)程,并且注冊(cè)了一些系統(tǒng)事件的監(jiān)聽(tīng)。

    private LauncherAppState(Context context) {
        if (getLocalProvider(context) == null) {
            throw new RuntimeException(
                    "Initializing LauncherAppState in the absence of LauncherProvider");
        }
        Log.v(Launcher.TAG, "LauncherAppState initiated");
        Preconditions.assertUIThread();
        mContext = context;

        mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
        mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
        mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
        mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));

        LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel);

        // Register intent receivers
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
        // For handling managed profiles
        filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
        filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
        filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
        filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
        filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);

        if (FeatureFlags.IS_DOGFOOD_BUILD) {
            filter.addAction(ACTION_FORCE_ROLOAD);
        }

        mContext.registerReceiver(mModel, filter);
        UserManagerCompat.getInstance(mContext).enableAndResetCache();
        new ConfigMonitor(mContext).register();

        if (!mContext.getResources().getBoolean(R.bool.notification_badging_enabled)) {
            mNotificationBadgingObserver = null;
        } else {
            // Register an observer to rebind the notification listener when badging is re-enabled.
            mNotificationBadgingObserver = new SettingsObserver.Secure(
                    mContext.getContentResolver()) {
                @Override
                public void onSettingChanged(boolean isNotificationBadgingEnabled) {
                    if (isNotificationBadgingEnabled) {
                        NotificationListener.requestRebind(new ComponentName(
                                mContext, NotificationListener.class));
                    }
                }
            };
            mNotificationBadgingObserver.register(NOTIFICATION_BADGING);
        }
    }

回到剛才的Launcher創(chuàng)建流程壮锻,LauncherAppState初始化完成后琐旁,有這樣一句mModel = app.setLauncher(this),這里調(diào)用了mModel.initialize(launcher)猜绣,這里將傳過(guò)來(lái)的Callbacks對(duì)象(也就是Launcher灰殴,Launcher實(shí)現(xiàn)了Callbacks接口)保存為了弱引用。同樣是基于避免內(nèi)存泄露的考慮掰邢。還記得上文提到的LauncherAppState牺陶,LauncherModel是其內(nèi)部的一個(gè)成員變量,生命周期也是比Launcher這個(gè)Activity要長(zhǎng)的辣之。

public void initialize(Callbacks callbacks) {
        synchronized (mLock) {
            Preconditions.assertUIThread();
            mCallbacks = new WeakReference<>(callbacks);
        }
    }

繼續(xù)Launcher的create掰伸,之后是initDeviceProfile(app.getInvariantDeviceProfile()),DeviceProfile是與Launcher布局相關(guān)的一個(gè)重要類怀估,這里面保存了所有布局相關(guān)數(shù)據(jù)比如圖標(biāo)大小碱工、頁(yè)面寬高、各種padding等等奏夫。然后創(chuàng)建一些其他對(duì)象后怕篷,終于inflate了R.layout.launcher。繼續(xù)往下就執(zhí)行到一個(gè)關(guān)鍵函數(shù)mModel.startLoader(currentScreen)酗昼,前面執(zhí)行的都是諸如對(duì)象創(chuàng)建廊谓、View的inflate等邏輯,并沒(méi)有涉及到數(shù)據(jù)相關(guān)的內(nèi)容麻削,此函數(shù)就是開(kāi)啟Launcher數(shù)據(jù)加載的一個(gè)調(diào)用蒸痹。

之后又是一些初始化的邏輯。所以我們前面啰嗦一大堆呛哟,其實(shí)onCreate干的事情簡(jiǎn)單說(shuō)來(lái)就是初始化對(duì)象叠荠、加載布局、注冊(cè)一些事件監(jiān)聽(tīng)扫责、以及開(kāi)啟數(shù)據(jù)加載榛鼎。

接著看數(shù)據(jù)加載與綁定流程。數(shù)據(jù)加載的調(diào)用實(shí)際是這樣的startLoader()→startLoaderForResults()鳖孤。從如下代碼中可知者娱,數(shù)據(jù)加載時(shí)在一個(gè)工作線程去做的,這是很正常的一個(gè)選擇苏揣,避免阻塞主線程黄鳍。

public void startLoaderForResults(LoaderResults results) {
        synchronized (mLock) {
            stopLoader();
            mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
            runOnWorkerThread(mLoaderTask);
        }
    }

private static void runOnWorkerThread(Runnable r) {
        if (sWorkerThread.getThreadId() == Process.myTid()) {
            r.run();
        } else {
            // If we are not on the worker thread, then post to the worker handler
            sWorker.post(r);
        }
    }

在工作線程跑的是一個(gè)LoaderTask類,實(shí)現(xiàn)了Runnable接口平匈。我們直接來(lái)看其run函數(shù)的定義框沟。

    public void run() {
        synchronized (this) {
            // Skip fast if we are already stopped.
            if (mStopped) {
                return;
            }
        }

        TraceHelper.beginSection(TAG);
        try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
            TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
            loadWorkspace();

            verifyNotStopped();
            TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace");
            mResults.bindWorkspace();

            // Notify the installer packages of packages with active installs on the first screen.
            TraceHelper.partitionSection(TAG, "step 1.3: send first screen broadcast");
            sendFirstScreenActiveInstallsBroadcast();

            // Take a break
            TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle");
            waitForIdle();
            verifyNotStopped();

            // second step
            TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
            loadAllApps();

            TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
            verifyNotStopped();
            mResults.bindAllApps();

            verifyNotStopped();
            TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
            updateIconCache();

            // Take a break
            TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
            waitForIdle();
            verifyNotStopped();

            // third step
            TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts");
            loadDeepShortcuts();

            verifyNotStopped();
            TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts");
            mResults.bindDeepShortcuts();

            // Take a break
            TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle");
            waitForIdle();
            verifyNotStopped();

            // fourth step
            TraceHelper.partitionSection(TAG, "step 4.1: loading widgets");
            mBgDataModel.widgetsModel.update(mApp, null);

            verifyNotStopped();
            TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets");
            mResults.bindWidgets();

            transaction.commit();
        } catch (CancellationException e) {
            // Loader stopped, ignore
            TraceHelper.partitionSection(TAG, "Cancelled");
        }
        TraceHelper.endSection(TAG);
    }

非常清晰明了藏古,一步一步通過(guò)注釋和Log都標(biāo)出來(lái)了。Launcher里面數(shù)據(jù)比較多忍燥,包括所有應(yīng)用的圖標(biāo)和應(yīng)用數(shù)據(jù)校翔,所有應(yīng)用的Widget數(shù)據(jù),桌面已添加的用戶數(shù)據(jù)等灾前,隨著Android大版本演進(jìn)防症,還有DeepShortcuts等新的數(shù)據(jù)類型。如果按照常規(guī)的加載做法哎甲,等加載數(shù)據(jù)完成后再顯示到View蔫敲,耗時(shí)就太長(zhǎng)了。為了優(yōu)化體驗(yàn)炭玫,Launcher于是采用了分批加載奈嘿、分批綁定的做法。這是大家在應(yīng)用開(kāi)發(fā)時(shí)可以借鑒的一種優(yōu)化方案吞加。整體的加載綁定流程如下裙犹。

我們以其中的加載與綁定桌面內(nèi)容為例來(lái)進(jìn)行說(shuō)明,后面的三步在弄明白第一步如何做之后也就是業(yè)務(wù)邏輯上的差異衔憨,不再贅述叶圃。

加載桌面內(nèi)容,調(diào)用函數(shù)為loadWorkspace()践图。這個(gè)函數(shù)很長(zhǎng)掺冠,這里就不貼代碼了。簡(jiǎn)述一下其流程码党。

首先我們要知道一個(gè)BgDataModel類德崭,這個(gè)類用于把所有數(shù)據(jù)對(duì)應(yīng)的實(shí)例管理起來(lái)。如下面代碼揖盘,可以看到有workspaceItems(所有應(yīng)用圖標(biāo)數(shù)據(jù)對(duì)應(yīng)的ItemInfo)眉厨,appWidgets(所有AppWidgets數(shù)據(jù)對(duì)應(yīng)的LauncherAppWidgetInfo)等等。

    public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>();

    public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();

    public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();

    public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>();

    public final ArrayList<Long> workspaceScreens = new ArrayList<>();

    public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();

    public boolean hasShortcutHostPermission;

    public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>();

    public final WidgetsModel widgetsModel = new WidgetsModel();

然后正式來(lái)看loadWorkspace兽狭。

  • 通過(guò)LauncherSettings.Favorites.CONTENT_URI查詢Favorites表的所有內(nèi)容憾股,拿到cursor。
  • 遍歷cursor椭符,進(jìn)行數(shù)據(jù)的整理荔燎。每一行數(shù)據(jù)都有一個(gè)對(duì)應(yīng)的itemType耻姥,標(biāo)志著這一行的數(shù)據(jù)對(duì)應(yīng)的是一個(gè)應(yīng)用销钝、還是一個(gè)Widget或文件夾等。不同的類型會(huì)進(jìn)行不同的處理琐簇。
  • 對(duì)于圖標(biāo)類型(itemType是ITEM_TYPE_SHORTCUT蒸健,ITEM_TYPE_APPLICATION座享,ITEM_TYPE_DEEP_SHORTCUT),首先經(jīng)過(guò)一系列判斷似忧,判斷其是否還可用(比如應(yīng)用在Launcher未啟動(dòng)時(shí)被卸載導(dǎo)致不可用)渣叛,不可用的話就標(biāo)記為可刪除,繼續(xù)循環(huán)盯捌。如果可用的話淳衙,就根據(jù)當(dāng)前cursor的內(nèi)容,生成一個(gè)ShortcutInfo對(duì)象饺著,保存到BgDataModel箫攀。
  • 對(duì)于文件夾類型(itemType是ITEM_TYPE_FOLDER),直接生成一個(gè)對(duì)應(yīng)的FolderInfo對(duì)象幼衰,保存到BgDataModel靴跛。
  • 對(duì)于AppWidget(itemType是ITEM_TYPE_APPWIDGETITEM_TYPE_CUSTOM_APPWIDGET)渡嚣,也需要經(jīng)過(guò)是否可用的判斷梢睛,但是可用條件與圖標(biāo)類型是有差異的。如果可用识椰,生成一個(gè)LauncherAppWidgetInfo對(duì)象绝葡,保存到BgDataModel。
  • 經(jīng)過(guò)上述流程腹鹉,現(xiàn)在所有數(shù)據(jù)庫(kù)里讀出的內(nèi)容已經(jīng)分類完畢挤牛,并且保存到了內(nèi)存(BgDataModel)中。然后開(kāi)始處理之前標(biāo)記為可刪除的內(nèi)容种蘸。顯示從數(shù)據(jù)庫(kù)中刪除對(duì)應(yīng)的行墓赴,然后還要判斷此次刪除操作是否帶來(lái)了其他需要?jiǎng)h除的內(nèi)容。比如某個(gè)文件夾或者某一頁(yè)只有一個(gè)圖標(biāo)航瞭,這個(gè)圖標(biāo)因?yàn)槟承┰虮粍h掉了诫硕,那么此文件夾或頁(yè)面也需要被刪掉。

至此數(shù)據(jù)加載完畢刊侯,開(kāi)始要進(jìn)行綁定了章办,也就是mResults.bindWorkspace()

此函數(shù)在LoaderResults類中。函數(shù)在執(zhí)行數(shù)據(jù)綁定之前滨彻,會(huì)執(zhí)行這樣一段代碼藕届。

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

        filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
                otherWorkspaceItems);
        filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets,
                otherAppWidgets);
        sortWorkspaceItemsSpatially(currentWorkspaceItems);
        sortWorkspaceItemsSpatially(otherWorkspaceItems);

這段代碼做的事情是,把Launcher啟動(dòng)后默認(rèn)顯示出來(lái)那一頁(yè)所擁有的數(shù)據(jù)篩選到currentWorkspaceItems與currentAppWidgets亭饵,其他頁(yè)的數(shù)據(jù)篩選到otherWorkspaceItems與otherAppWidgets休偶。然后對(duì)每個(gè)list,按照從上到下辜羊,從左到右的順序進(jìn)行排序踏兜。然后可以開(kāi)始綁定了词顾。下面代碼的Callbacks就是Launcher activity實(shí)例,首先通知Launcher要開(kāi)始綁定了(callbacks.startBinding())碱妆,然后先把空頁(yè)面添加到View tree中(callbacks.bindScreens(orderedScreenIds))肉盹,之后先綁定默認(rèn)頁(yè)的所有元素(下段代碼的最后一句)。當(dāng)然這些所有的操作都是通過(guò)mUiExecutor放到主線程執(zhí)行的疹尾。

        // Tell the workspace that we're about to start binding items
        r = new Runnable() {
            public void run() {
                Callbacks callbacks = mCallbacks.get();
                if (callbacks != null) {
                    callbacks.clearPendingBinds();
                    callbacks.startBinding();
                }
            }
        };
        mUiExecutor.execute(r);

        // Bind workspace screens
        mUiExecutor.execute(new Runnable() {
            @Override
            public void run() {
                Callbacks callbacks = mCallbacks.get();
                if (callbacks != null) {
                    callbacks.bindScreens(orderedScreenIds);
                }
            }
        });

                Executor mainExecutor = mUiExecutor;
        // Load items on the current page.
        bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);

最后一句的內(nèi)容如下上忍,也是通過(guò)callbacks調(diào)用在Launcher中的bindItems函數(shù)。

private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
            final ArrayList<LauncherAppWidgetInfo> appWidgets,
            final Executor executor) {

        // Bind the workspace items
        int N = workspaceItems.size();
        for (int i = 0; i < N; i += ITEMS_CHUNK) {
            final int start = i;
            final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
            final Runnable r = new Runnable() {
                @Override
                public void run() {
                    Callbacks callbacks = mCallbacks.get();
                    if (callbacks != null) {
                        callbacks.bindItems(workspaceItems.subList(start, start+chunkSize), false);
                    }
                }
            };
            executor.execute(r);
        }

        // Bind the widgets, one at a time
        N = appWidgets.size();
        for (int i = 0; i < N; i++) {
            final ItemInfo widget = appWidgets.get(i);
            final Runnable r = new Runnable() {
                public void run() {
                    Callbacks callbacks = mCallbacks.get();
                    if (callbacks != null) {
                        callbacks.bindItems(Collections.singletonList(widget), false);
                    }
                }
            };
            executor.execute(r);
        }
    }

bindItems函數(shù)我們看一下其中的關(guān)鍵代碼纳本。根據(jù)不同的itemType來(lái)生產(chǎn)不同的View睡雇,然后通過(guò)addInScreenFromBind函數(shù)將View add到相應(yīng)的ViewGroup去。

    @Override
    public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
        ...
            switch (item.itemType) {
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
                    ShortcutInfo info = (ShortcutInfo) item;
                    view = createShortcut(info);
                    break;
                }
                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
                    view = FolderIcon.fromXml(R.layout.folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                            (FolderInfo) item);
                    break;
                }
                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
                    view = inflateAppWidget((LauncherAppWidgetInfo) item);
                    if (view == null) {
                        continue;
                    }
                    break;
                }
                default:
                    throw new RuntimeException("Invalid Item Type");
            }

            ...
            workspace.addInScreenFromBind(view, item);
            ...
    }

默認(rèn)頁(yè)的元素綁定完了饮醇,然后繼續(xù)綁定其他頁(yè)的元素它抱。這里我們從注釋也可以看出,Launcher為了讓默認(rèn)頁(yè)盡快顯示朴艰,自定義了一個(gè)ViewOnDrawExecutor观蓄,這里面會(huì)讓綁定其他頁(yè)的操作在綁定完第一頁(yè)的元素并且第一次onDraw執(zhí)行完之后才執(zhí)行。讀者有興趣的話可以去看看這個(gè)Executor的實(shí)現(xiàn)祠墅。

        // In case of validFirstPage, only bind the first screen, and defer binding the
        // remaining screens after first onDraw (and an optional the fade animation whichever
        // happens later).
        // This ensures that the first screen is immediately visible (eg. during rotation)
        // In case of !validFirstPage, bind all pages one after other.
        final Executor deferredExecutor =
                validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;

        mainExecutor.execute(new Runnable() {
            @Override
            public void run() {
                Callbacks callbacks = mCallbacks.get();
                if (callbacks != null) {
                    callbacks.finishFirstPageBind(
                            validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
                }
            }
        });

        bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);

經(jīng)過(guò)其他頁(yè)的綁定之后侮穿,桌面數(shù)據(jù)的加載與綁定也就到此為止。接下來(lái)就是之前提到的另外三步后續(xù)加載與綁定內(nèi)容了毁嗦,不再贅述亲茅。但是在本文結(jié)束前,還想說(shuō)一個(gè)值得一提的地方狗准。

桌面數(shù)據(jù)的加載與綁定完之后克锣,我們看這里執(zhí)行了一個(gè)waitForIdle的函數(shù),然后才是繼續(xù)執(zhí)行第二步腔长。這個(gè)函數(shù)是做什么的呢袭祟?

            // Take a break
            TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle");
            waitForIdle();
            verifyNotStopped();

            // second step
            TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
            loadAllApps();

我們看下它的實(shí)現(xiàn)。

    protected synchronized 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.
        LooperIdleLock idleLock = mResults.newIdleLock(this);
        // Just in case mFlushingWorkerThread changes but we aren't woken up,
        // wait no longer than 1sec at a time
        while (!mStopped && idleLock.awaitLocked(1000));
    }

    public class LooperIdleLock implements MessageQueue.IdleHandler, Runnable {

    private final Object mLock;

    private boolean mIsLocked;

    public LooperIdleLock(Object lock, Looper looper) {
        mLock = lock;
        mIsLocked = true;
        if (Utilities.ATLEAST_MARSHMALLOW) {
            looper.getQueue().addIdleHandler(this);
        } else {
            // Looper.myQueue() only gives the current queue. Move the execution to the UI thread
            // so that the IdleHandler is attached to the correct message queue.
            new LooperExecutor(looper).execute(this);
        }
    }

    @Override
    public void run() {
        Looper.myQueue().addIdleHandler(this);
    }

    @Override
    public boolean queueIdle() {
        synchronized (mLock) {
            mIsLocked = false;
            mLock.notify();
        }
        return false;
    }

    public boolean awaitLocked(long ms) {
        if (mIsLocked) {
            try {
                // Just in case mFlushingWorkerThread changes but we aren't woken up,
                // wait no longer than 1sec at a time
                mLock.wait(ms);
            } catch (InterruptedException ex) {
                // Ignore
            }
        }
        return mIsLocked;
    }
}

這里面涉及到一個(gè)應(yīng)用啟動(dòng)優(yōu)化的技術(shù)捞附。我們知道應(yīng)用的啟動(dòng)優(yōu)化可以有延遲加載巾乳、懶加載、異步加載等手段鸟召。而用一個(gè)名為IdleHandler的類胆绊,就可以比較方便的實(shí)現(xiàn)延遲加載。這個(gè)后面有空的話再來(lái)細(xì)說(shuō)吧欧募,本文就先到這里压状。

下一篇將分析Launcher布局相關(guān)內(nèi)容。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末槽片,一起剝皮案震驚了整個(gè)濱河市何缓,隨后出現(xiàn)的幾起案子肢础,更是在濱河造成了極大的恐慌还栓,老刑警劉巖碌廓,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異剩盒,居然都是意外死亡谷婆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門辽聊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纪挎,“玉大人,你說(shuō)我怎么就攤上這事跟匆∫彀溃” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵玛臂,是天一觀的道長(zhǎng)烤蜕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)迹冤,這世上最難降的妖魔是什么讽营? 我笑而不...
    開(kāi)封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮泡徙,結(jié)果婚禮上橱鹏,老公的妹妹穿的比我還像新娘。我一直安慰自己堪藐,他們只是感情好莉兰,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著礁竞,像睡著了一般贮勃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苏章,一...
    開(kāi)封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天寂嘉,我揣著相機(jī)與錄音,去河邊找鬼枫绅。 笑死泉孩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的并淋。 我是一名探鬼主播寓搬,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼县耽!你這毒婦竟也來(lái)了句喷?” 一聲冷哼從身側(cè)響起镣典,我...
    開(kāi)封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唾琼,沒(méi)想到半個(gè)月后兄春,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锡溯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年赶舆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祭饭。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芜茵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倡蝙,到底是詐尸還是另有隱情九串,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布寺鸥,位于F島的核電站猪钮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏析既。R本人自食惡果不足惜躬贡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眼坏。 院中可真熱鬧拂玻,春花似錦、人聲如沸宰译。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)沿侈。三九已至闯第,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缀拭,已是汗流浹背咳短。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛛淋,地道東北人咙好。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像褐荷,于是被迫代替她去往敵國(guó)和親勾效。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359