Launcher3分析之數據加載與綁定

Launcher3的主界面是

packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

首先分析onCreate

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (DEBUG_STRICT_MODE) {
		    //StrictMode被稱爲嚴苛模式,google提供用來進行測試的類
            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());
        }
        if (LauncherAppState.PROFILE_STARTUP) {
            Trace.beginSection("Launcher-onCreate");
        }

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.preOnCreate();
        }

        WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this);
        wallpaperColorInfo.setOnThemeChangeListener(this);
        overrideTheme(wallpaperColorInfo.isDark(), wallpaperColorInfo.supportsDarkText());

        super.onCreate(savedInstanceState);
        //單例模式,初始化LauncherAppState
        LauncherAppState app = LauncherAppState.getInstance(this);

        // Load configuration-specific DeviceProfile
		//初始化手機固件信息對象DeviceProfile
        mDeviceProfile = app.getInvariantDeviceProfile().getDeviceProfile(this);
        if (isInMultiWindowModeCompat()) {
            Display display = getWindowManager().getDefaultDisplay();
            Point mwSize = new Point();
            display.getSize(mwSize);
            mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
        }
        //獲取屏幕方向
        mOrientation = getResources().getConfiguration().orientation;
        mSharedPrefs = Utilities.getPrefs(this); //獲取sharedPreferences
        mIsSafeModeEnabled = getPackageManager().isSafeMode();
        mModel = app.setLauncher(this); //獲取LauncherModel實例
        mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
        mIconCache = app.getIconCache();//獲取IconCache實例,此類主要保存圖標信息
        mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
        //拖拽
        mDragController = new DragController(this);
        mAllAppsController = new AllAppsTransitionController(this);
        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);

        mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); //獲取AppWidgetManager實例,用來管理widge

        mAppWidgetHost = new LauncherAppWidgetHost(this);
        if (Utilities.ATLEAST_MARSHMALLOW) {
            mAppWidgetHost.addProviderChangeListener(this);
        }
        mAppWidgetHost.startListening();

        // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
        // this also ensures that any synchronous binding below doesn't re-trigger another
        // LauncherModel load.
        mPaused = false;
        //設置佈局,此時佈局是不包含參數佈局
        mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
        //初始化View,進行各種View的初始化事件綁定
        setupViews();
        mDeviceProfile.layout(this, false /* notifyListeners */);//佈局的參數設置
        loadExtractedColorsAndColorItems();

        mPopupDataProvider = new PopupDataProvider(this);

        ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
                .addAccessibilityStateChangeListener(this);

        lockAllApps();

        restoreState(savedInstanceState);

        if (LauncherAppState.PROFILE_STARTUP) {
            Trace.endSection();
        }

        // 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 we are not binding synchronously, show a fade in animation when
            // the first page bind completes.
            mDragLayer.setAlpha(0);
        } else {
            // Pages bound synchronously.
            mWorkspace.setCurrentPage(currentScreen);

            setWorkspaceLoading(true);
        }

        // For handling default keys
        mDefaultKeySsb = new SpannableStringBuilder();
        Selection.setSelection(mDefaultKeySsb, 0);

        mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
        // In case we are on a device with locked rotation, we should look at preferences to check
        // if the user has specifically allowed rotation.
        if (!mRotationEnabled) {
            mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
            mRotationPrefChangeHandler = new RotationPrefChangeHandler();
            mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
        }

        if (PinItemDragListener.handleDragRequest(this, getIntent())) {
            // Temporarily enable the rotation
            mRotationEnabled = true;
        }

        // On large interfaces, or on devices that a user has specifically enabled screen rotation,
        // we want the screen to auto-rotate based on the current orientation
        setOrientation();

        setContentView(mLauncherView);

        // Listen for broadcasts
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
        registerReceiver(mReceiver, filter);
        mShouldFadeInScrim = true;

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

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onCreate(savedInstanceState);
        }
    }
	
	
	
	    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;

        if (TestingUtils.MEMORY_DUMP_ENABLED) {
            TestingUtils.startTrackingMemory(mContext);
        }
        //初始化固定的設備配置
        mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
		//初始化圖標管理工具
        mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
		//初始化Widget加載緩存工具
        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);
        // For extracting colors from the wallpaper
        if (Utilities.ATLEAST_NOUGAT) {
            // TODO: add a broadcast entry to the manifest for pre-N.
            filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
        }
        //註冊廣播
        mContext.registerReceiver(mModel, filter);
        UserManagerCompat.getInstance(mContext).enableAndResetCache();
        new ConfigMonitor(mContext).register();

        ExtractionUtils.startColorExtractionServiceIfNecessary(mContext);

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

從代碼中可以看出,首先調用了LauncherAppState.getInstance(this)來初始化一個單例對象。LauncherAppState裏面保存了一些比較常用的對象,方便其他地方通過單例來獲取,比如IconCache、LauncherModel等。

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

注意這裏初始化使用的application的Context,因爲單例作爲static對象,生命週期是與application生命週期一樣長的,如果這裏使用了Activity的Contxet,會導致activity退出後,該Context依然被單例持有而無法回收,於是出現內存泄漏。

    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;

        if (TestingUtils.MEMORY_DUMP_ENABLED) {
            TestingUtils.startTrackingMemory(mContext);
        }
        //初始化固定的設備配置
        mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);
		//初始化圖標管理工具
        mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
		//初始化Widget加載混存工具
        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);
        // For extracting colors from the wallpaper
        if (Utilities.ATLEAST_NOUGAT) {
            // TODO: add a broadcast entry to the manifest for pre-N.
            filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
        }

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

        ExtractionUtils.startColorExtractionServiceIfNecessary(mContext);

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

這裏面其實是各個對象的實例創建過程,並且註冊了一些系統事件的監聽。創建完成LauncherAppState後,會執行mModel = app.setLauncher(this); //獲取LauncherModel實例

    LauncherModel setLauncher(Launcher launcher) {
        getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
        mModel.initialize(launcher);
        return mModel;
    }


Launcher3/src/com/android/launcher3/LauncherModel.java
    public void initialize(Callbacks callbacks) {
        synchronized (mLock) {
            Preconditions.assertUIThread();
            mCallbacks = new WeakReference<>(callbacks);
        }
    }

這裏將傳過來的Callbacks對象(也就是Launcher,Launcher實現了Callbacks接口)保存爲了弱引用。同樣是基於避免內存泄漏的考慮。這裏的LauncherModel是LauncherAppState內部的一個成員變量,生命週期也是比Launcher這個Activity要長。

總結一下onCreate的工作:初始化對象、加載佈局、註冊一些事件監聽、以及開啓數據加載。

數據加載是通過mModel.startLoader。現在進入LauncherModel

Launcher3/src/com/android/launcher3/LauncherModel.java

    /**
     * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
     * @return true if the page could be bound synchronously.
    */
    public boolean startLoader(int synchronousBindPage) {
        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
        synchronized (mLock) {
            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {
                final Callbacks oldCallbacks = mCallbacks.get();
                // Clear any pending bind-runnables from the synchronized load process.
                mUiExecutor.execute(new Runnable() {
                            public void run() {
                                oldCallbacks.clearPendingBinds();
                            }
                        });

                // If there is already one running, tell it to stop.
                stopLoader();
                LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
                        mBgAllAppsList, synchronousBindPage, mCallbacks);
                if (mModelLoaded && !mIsLoaderTaskRunning) {
                    // Divide the set of loaded items into those that we are binding synchronously,
                    // and everything else that is to be bound normally (asynchronously).
                    loaderResults.bindWorkspace();
                    // For now, continue posting the binding of AllApps as there are other
                    // issues that arise from that.
                    loaderResults.bindAllApps();
                    loaderResults.bindDeepShortcuts();
                    loaderResults.bindWidgets();
                    return true;
                } else {
                    startLoaderForResults(loaderResults);
                }
            }
        }
        return false;
    }



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


    /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
     * posted on the worker thread handler. */
    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);
        }
    }

從上面代碼可以看到,數據加載調用的是工作線程,避免堵塞主線程。工作線程使用的是LoaderTask。

Launcher3/src/com/android/launcher3/model/LoaderTask.java

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

        try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
            long now = 0;
            if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace");
            loadWorkspace();

            verifyNotStopped();
            if (DEBUG_LOADERS) Log.d(TAG, "step 1.2: bind workspace workspace");
            mResults.bindWorkspace();

            // Take a break
            if (DEBUG_LOADERS) {
                Log.d(TAG, "step 1 completed, wait for idle");
                now = SystemClock.uptimeMillis();
            }
            waitForIdle();
            if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms");
            verifyNotStopped();

            // second step
            if (DEBUG_LOADERS) Log.d(TAG, "step 2.1: loading all apps");
            loadAllApps();

            if (DEBUG_LOADERS) Log.d(TAG, "step 2.2: Binding all apps");
            verifyNotStopped();
            mResults.bindAllApps();

            verifyNotStopped();
            if (DEBUG_LOADERS) Log.d(TAG, "step 2.3: Update icon cache");
            updateIconCache();

            // Take a break
            if (DEBUG_LOADERS) {
                Log.d(TAG, "step 2 completed, wait for idle");
                now = SystemClock.uptimeMillis();
            }
            waitForIdle();
            if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms");
            verifyNotStopped();

            // third step
            if (DEBUG_LOADERS) Log.d(TAG, "step 3.1: loading deep shortcuts");
            loadDeepShortcuts();

            verifyNotStopped();
            if (DEBUG_LOADERS) Log.d(TAG, "step 3.2: bind deep shortcuts");
            mResults.bindDeepShortcuts();

            // Take a break
            if (DEBUG_LOADERS) Log.d(TAG, "step 3 completed, wait for idle");
            waitForIdle();
            verifyNotStopped();

            // fourth step
            if (DEBUG_LOADERS) Log.d(TAG, "step 4.1: loading widgets");
            mBgDataModel.widgetsModel.update(mApp, null);

            verifyNotStopped();
            if (DEBUG_LOADERS) Log.d(TAG, "step 4.2: Binding widgets");
            mResults.bindWidgets();

            transaction.commit();
        } catch (CancellationException e) {
            // Loader stopped, ignore
            if (DEBUG_LOADERS) {
                Log.d(TAG, "Loader cancelled", e);
            }
        }
    }

在上面代碼中,主要有4步,由於Launcher裏面數據比較多,在這裏採用了分批加載、分批綁定的做法。

這裏以Workspace爲例,分析加載和綁定。加載桌面是loadWorkspace方法,需要先了解BgDataModel類(Launcher3/src/com/android/launcher3/model/BgDataModel.java)。

BgDataModel是android O版本新增的,對memory緩存進行了封裝,可以看到有workspaceItems(所有應用圖標數據對應的ItemInfo),appWidgets(所有AppWidgets數據對應的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();

loadWorkspace的代碼比較長,主要做兩件事情:1、加載默認配置xml文件中的items,並把默認數據寫入數據庫。2、加載數據庫中的數據到緩存。先看加載默認佈局

        if (clearDb) {
            Log.d(TAG, "loadWorkspace: resetting launcher database");
            LauncherSettings.Settings.call(contentResolver,
                    LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
        }

最終會其會調用LauncherProvider的loadDefaultFavoritesIfNecessary()方法。

再分析加載數據庫

final LoaderCursor c = new LoaderCursor(contentResolver.query(
                    LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);

1、通過LauncherSettings.Favorites.CONTENT_URI查詢Favorites表中所有內容,拿到cursor。

2、遍歷cursor,進行數據的整理。每一行數據都有一個對應的itemType,標誌着這一行數據對應得的是一個應用、還是一個Widget或文件夾等,不同類型會進行不同的處理。

3、對於圖標類型(itemType是ITEM_TYPE_SHORTCUT,ITEM_TYPE_APPLICATION,ITEM_TYPE_DEEP_SHORTCUT),首先經過一系列判斷,判斷其是否還可用(比如應用在Launcher未啓動時被卸載導致不可用),不可用的話就標記爲可刪除,繼續循環。如果可用的話,就根據當前cursor的內容,生成一個ShortInfo對象,保存到BgDataModel。

4、對於文件夾類型(itemType是ITEM_TYPE_FOLDER),直接生成一個對應的FolderInfo對象,保存到BgDataModel。

5、對於文件類型(itemType是ITEM_TYPE_FOLDER),直接生成一個對應的Folder對象,保存到BgDataModel。

6、對於AppWidget(itemType是ITEM_TYPE_APPWIDGETITEM_TYPE_CUSTOM_APPWIDGET),也需要經過是否可用的判斷,但是可用條件與圖標類型是有差異的。如果可用,生成一個LauncherAppWidgetInfo對象,保存到BgDataModel。

7、經過上述流程,現在所有數據庫裏讀出的內容已經分類完畢,並且保存到了內存(BgDataModel)中。然後開始處理之前標記爲可刪除的內容。顯示從數據庫中刪除對應的行,然後還要判斷此次刪除操作是否帶來了其他需要刪除的內容。比如某個文件夾或者某一頁只有一個圖標,這個圖標因爲某些原因被刪掉了,那麼此文件夾或頁面也需要被刪掉。

數據加載完畢後開始綁定數據使用mResults.bindWorkspace()

Launcher3/src/com/android/launcher3/model/LoaderResults.java

        // 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啓動後默認顯示出來的那一頁所擁有的數據篩選到

currentWorkspaceItems與currentAppWidgets,其他頁的數據篩選到otherWorkspaceItems與otherAppWidgets。然後對每個list,按照從上到下,從左到右的順序進行排序。然後可以開始綁定了。

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

上面的Callbacks就是Launcher activity實例,首先通知Launcher要開始綁定(callbacks.startBinding()),然後把空白頁添加到View tree中(callbacks.bindScreens(orderedScreenIds)),之後先綁定默認頁面的所有元素( bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor)),當然這些所有的操作都是通過mUiExecutor放到主線程執行的。

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

最後通過callbacks調用在Launcher中的bindItems方法

    @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);
            ...
    }

上面的代碼主要是根據不同的itemType來生產不同的View,然後通過addInScreenFromBind函數將View add到相應的ViewGroup中去。

默認頁的元素綁定完了,然後繼續綁定其他頁面的元素。

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

從上面代碼可以看出,Launcher爲了讓默認頁儘快顯示,自定義了一個ViewOnDrawExecutor,這裏面會讓綁定其他頁的操作在綁定完第一頁的元素並且第一次onDraw執行完之後才執行。讀者有興趣的話可以去看看這個Executor的實現。

桌面數據的加載與綁定完之後,我們看這裏執行了一個waitForIdle的函數,然後纔是繼續執行第二步,這裏面涉及到一個應用啓動優化的技術。我們知道應用的啓動優化可以有延遲加載、懶加載、異步加載等手段。而用一個名爲IdleHandler的類,就可以比較方便的實現延遲加載。

參考資料:

Android 9.0 Launcher源碼分析(二)——Launcher應用啓動

墨香帶你學Launcher之(二)-數據加載流程

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