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() {
                        @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 itemsIdMap = new LongArrayMap<>();

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

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

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

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

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

    public boolean hasShortcutHostPermission;

    public final MultiHashMap 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 currentWorkspaceItems = new ArrayList<>();
        ArrayList otherWorkspaceItems = new ArrayList<>();
        ArrayList currentAppWidgets = new ArrayList<>();
        ArrayList 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 workspaceItems,
            final ArrayList 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 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之(二)-数据加载流程

你可能感兴趣的:(源码分析,Android之路)