Launcher 背后的魔法:揭开 LoaderTask 的数据加载奥秘

在这里插入图片描述

Android 14.0文章
Launcher 背后的魔法📑 Gradle 构建与编译全攻略(超详细)
Launcher 背后的魔法📑 揭开 LoaderTask 的数据加载奥秘

# 核心代码类

核心类路径
Launcher.javapackage/app/Launcher3/src/com/android/launcher3/Launcher.java
LoaderTaskpackage/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java
LauncherSettings.javapackage/app/Launcher3/src/com/android/launcher3/LauncherSettings.java
LauncherProvider.javapackage/app/Launcher3/src/com/android/launcher3/LauncherProvider.java
ModelDbController.javapackage/app/Launcher3/src/com/android/launcher3/model/ModelDbController.java
InvariantDeviceProfile.javapackage/app/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java
LauncherAppState.javapackage/app/Launcher3/src/com/android/launcher3/LauncherAppState.java

一、Launcher.onCreate()

Launcher 被 AMS 拉起后,就进入了自己的生命流程,Launcher.java 中的 onCreate() 函数被调用,准备开始加载桌面。

# 源码路径:package/app/Launcher3/src/com/android/launcher3/Launcher.java

public class Launcher extends StatefulActivity<LauncherState>
        implements LauncherExterns, Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
        PluginListener<LauncherOverlayPlugin> {

    private LauncherModel mModel;

    protected void onCreate(Bundle savedInstanceState) {
        ...
        
        LauncherAppState app = LauncherAppState.getInstance(this);
        mModel = app.getModel();
        
        // 🗒️ 执行 LauncherModel 的 addCallbacksAndLoad() 方法
        if (!mModel.addCallbacksAndLoad(this)) {
            if (!internalStateHandled) {
                mOnInitialBindListener = Boolean.FALSE::booleanValue;
            }
        }
        
        ...
    }
        
}

我们来看看 addCallbacksAndLoad() 在 LauncherModel.java 中实现。

# 源码路径:package/app/Launcher3/src/com/android/launcher3/LauncherModel.java

public class LauncherModel extends LauncherApps.Callback implements InstallSessionTracker.Callback {

    public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
        synchronized (mLock) {
            addCallbacks(callbacks);
            // 🗒️ 执行 startLoader() 方法
            return startLoader(new Callbacks[] { callbacks });
        }
    }

}

又调用了 startLoader() 方法:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/LauncherModel.java

public class LauncherModel extends LauncherApps.Callback implements InstallSessionTracker.Callback {

    private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
        ItemInstallQueue.INSTANCE.get(mApp.getContext())
                .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);

        synchronized (mLock) {
            ...
    
            if (callbacksList.length > 0) {
                # Clear any pending bind-runnables from the synchronized load process.
                for (Callbacks cb : callbacksList) {
                    MAIN_EXECUTOR.execute(cb::clearPendingBinds);
                }
    
                LauncherBinder launcherBinder = new LauncherBinder(
                        mApp, mBgDataModel, mBgAllAppsList, callbacksList);

                // 非首次启动,Launcher 直接从数据库中同步加载
                if (bindDirectly) {
                    launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true);
                    launcherBinder.bindAllApps();
                    launcherBinder.bindDeepShortcuts();
                    launcherBinder.bindWidgets();
                    launcherBinder.updateIconTheme();
                    if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                        mModelDelegate.bindAllModelExtras(callbacksList);
                    }
                    return true;
                } else {
                    stopLoader();
                    // 🗒️ 首次启动走这里
                    mLoaderTask = new LoaderTask(
                            mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder);
                    MODEL_EXECUTOR.post(mLoaderTask);
                }
            }
        }
        return false;
    }

}

在 startLoader() 方法中会创建 LauncherBinder 对象。如果是首次启动情况下,会创建 LoaderTask 对象,然后会执行 LoaderTask.run() 方法,利用之前创建的 LauncherBinder 开始加载桌面。


二、LoaderTask.run()

查看 LoaderTask 的 run() 方法:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java

/**
 * Runnable for the thread that loads the contents of the launcher:
 *   - workspace icons
 *   - widgets
 *   - all apps icons
 *   - deep shortcuts within apps
 */
public class LoaderTask implements Runnable {

    public void run() {
        synchronized (this) {
            // 如果已经停止,则快速跳过
            if (mStopped) {
                return;
            }
        }
    
        Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
        LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
        // 这里与 LauncherModel.LoaderTransaction 关联上
        try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
            List<ShortcutInfo> allShortcuts = new ArrayList<>();
        ---------------------------------------------------------------------------
            // 🗒️ 第 1 步:加载工作区
            loadWorkspace(allShortcuts, "", memoryLogger);
    
            // 根据从数据库加载的工作空间,清理数据重新同步小部件快捷方式
            if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
                verifyNotStopped();
                sanitizeFolders(mItemsDeleted);
                sanitizeWidgetsShortcutsAndPackages();
                logASplit("sanitizeData");
            }
    
            verifyNotStopped();
            // 绑定 Workspace 数据(绑定图标之类的数据,桌面及其上内容开始呈现)
            mLauncherBinder.bindWorkspace(true /* incrementBindId */, /* isBindSync= */ false);
            logASplit("bindWorkspace");
    
            mModelDelegate.workspaceLoadComplete();
            // 发送首屏广播(开机后默认显示的第一个 Screen,如果图标多的话,
            // 会被分成多个 Screen,通过左右滑动显示其他的)
            sendFirstScreenActiveInstallsBroadcast();
            logASplit("sendFirstScreenActiveInstallsBroadcast");
    
            // 等待桌面 (workspace) 加载完成再加载抽屉 (Allapps)
            waitForIdle();
            logASplit("step 1 complete");
            verifyNotStopped();
        ---------------------------------------------------------------------------
            // 🗒️ 第 2 步:加载全部应用
            Trace.beginSection("LoadAllApps");
            List<LauncherActivityInfo> allActivityList;
            try {
               allActivityList = loadAllApps();
            } finally {
                Trace.endSection();
            }
            logASplit("loadAllApps");
    
            if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
                logASplit("allAppsDelegateItems");
            }
            verifyNotStopped();
            // 绑定 All Apps
            mLauncherBinder.bindAllApps();
            logASplit("bindAllApps");
    
            verifyNotStopped();
            IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
            setIgnorePackages(updateHandler);
            updateHandler.updateIcons(allActivityList,
                    LauncherActivityCachingLogic.newInstance(mApp.getContext()),
                    mApp.getModel()::onPackageIconsUpdated);
            logASplit("update icon cache");
    
            verifyNotStopped();
            logASplit("save shortcuts in icon cache");
            updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
                    mApp.getModel()::onPackageIconsUpdated);
    
            // 等待上一操作加载完成,再进行加载应用程序快捷方式
            waitForIdle();
            logASplit("step 2 complete");
            verifyNotStopped();
        ---------------------------------------------------------------------------
            // 🗒️ 第 3 步:加载应用程序快捷方式
            List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
            logASplit("loadDeepShortcuts");
    
            verifyNotStopped();
            // 数据同步绑定应用程序快捷方式
            mLauncherBinder.bindDeepShortcuts();
            logASplit("bindDeepShortcuts");
    
            verifyNotStopped();
            logASplit("save deep shortcuts in icon cache");
            // 缓存对应图标
            updateHandler.updateIcons(allDeepShortcuts,
                    new ShortcutCachingLogic(), (pkgs, user) -> { });
    
            // 等待上一步操作完成,再进行加载小部件等
            waitForIdle();
            logASplit("step 3 complete");
            verifyNotStopped();
        ---------------------------------------------------------------------------    
            // 🗒️ 第 4 步:加载桌面小部件
            List<ComponentWithLabelAndIcon> allWidgetsList =
                    mBgDataModel.widgetsModel.update(mApp, null);
            logASplit("load widgets");
    
            verifyNotStopped();
            // 绑定 Widgets
            mLauncherBinder.bindWidgets();
            logASplit("bindWidgets");
            verifyNotStopped();
    
            if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
                logASplit("otherDelegateItems");
                verifyNotStopped();
            }
    
            // 将小部件进行缓存
            updateHandler.updateIcons(allWidgetsList,
                    new ComponentWithIconCachingLogic(mApp.getContext(), true),
                    mApp.getModel()::onWidgetLabelsUpdated);
            logASplit("save widgets in icon cache");
        ---------------------------------------------------------------------------
            // 🗒️ 第 5 步:加载文件夹名称
            loadFolderNames();
    
            verifyNotStopped();
            updateHandler.finish();
            logASplit("finish icon update");
    
            mModelDelegate.modelLoadComplete();
            // 提交完成
            transaction.commit();
            memoryLogger.clearLogs();
        } catch (CancellationException e) {
            // Loader stopped, ignore
            logASplit("Cancelled");
        } catch (Exception e) {
            memoryLogger.printLogs();
            throw e;
        }
        TraceHelper.INSTANCE.endSection(traceToken);
    }

}

所以我们可以很清晰的发现 LoaderTask 做了以下五件事:

  • Step 1: Workspace:加载工作区+ 绑定 Workspace
    • step 1.1: loading workspace
    • step 1.2: bind workspace
    • step 1.3: send first screen broadcast
    • step 1 completed, wait for idle
  • Step 2: AllApps:加载全部应用 + 绑定全部应用
    • step 2.1: loading all apps
    • step 2.2: bind all apps
    • step 2.3: update icon cache
    • step 2 completed, wait for idle
  • Step 3: DeepShortcuts:加载应用程序快捷方式 + 绑定应用程序快捷方式
    • step 3.1: loading deep shortcuts
    • step 3.2: bind deep shortcuts
    • step 3 completed, wait for idle
  • Step 4: Widgets:加载桌面小部件 + 绑定桌面小部件
    • step 4.1: loading widgets
    • step 4.2: bind widgets
    • step 4.3: Update icon cache
  • Step 5: FolderNames:加载文件夹名称
    • step 5: loading folder names

三、Step1: Workspace

Workspace 的工作主要分为:加载和绑定,我们一个个来分析。

3.1 加载 Workspace

跟踪 loadWorkspace() 方法:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java

public class LoaderTask implements Runnable {
    
    /**
     * run() -> loadWorkspace(allShortcuts, "", memoryLogger);
     *
     *     1. allDeepShortcuts: 存储应用快捷方式的列表,默认是空的
     *     2. selection: 可以指定选择条件,用于过滤,默认是没有传入任何条件的
     *     3. memoryLogger 是一个 LoaderMemoryLogger,是一个辅助记录器,用于捕获记录
     *        LoaderTask run() 方法执行过程中可能产生的异常及内存使用情况
     */
    protected void loadWorkspace(
            List<ShortcutInfo> allDeepShortcuts,
            String selection,
            LoaderMemoryLogger memoryLogger) {
        // 开始性能追踪,用于性能调试和监控
        Trace.beginSection("LoadWorkspace");
        try {
            // 🗒️ 负责加载工作区内容的主要逻辑
            loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger);
        } finally {
            // 结束性能跟踪
            Trace.endSection();
        }
        // 记录工作区加载过程的日志,用于性能分析或调试
        logASplit("loadWorkspace");
    
        if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
            // 确保 Loader 加载过程没有被中止
            verifyNotStopped();
            // 使用 ModelDelegate 加载并绑定工作区项(Workspace Items)
            mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
                    mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
            // 标记 ModelDelegate 为活动状态,表示它已成功加载并绑定工作区项。
            mModelDelegate.markActive();
            logASplit("workspaceDelegateItems");
        }
    }

}

3.1.1 loadWorkspaceImpl()

现在我们来深度解读核心方法:loadWorkspaceImpl()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java

public class LoaderTask implements Runnable {

    protected final LauncherAppState mApp;
    
    /**
     * run() -> loadWorkspace(allShortcuts, "", memoryLogger);
     *       -> loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger);
     *
     *     1. allDeepShortcuts: 存储应用快捷方式的列表,默认是空的
     *     2. selection: 可以指定选择条件,用于过滤,默认是没有传入任何条件的
     *     3. memoryLogger 是一个 LoaderMemoryLogger,是一个辅助记录器,用于捕获记录
     *        LoaderTask run() 方法执行过程中可能产生的异常及内存使用情况
     */
    private void loadWorkspaceImpl(
            List<ShortcutInfo> allDeepShortcuts,
            String selection,
            @Nullable LoaderMemoryLogger memoryLogger) {

        /**
         * LauncherAppState 会持有应用的 Context 对象,使得应用的各个部分
         * 能够访问全局的 Context,从而进行资源访问、启动服务、广播等操作。
         */
        final Context context = mApp.getContext();
        // 后续用于查询、插入、删除或更新数据
        final ContentResolver contentResolver = context.getContentResolver();
        // 自定义的包管理的辅助类
        final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
        // 检查设备是否处于安全模式
        final boolean isSafeMode = pmHelper.isSafeMode();
        // 检查设备的启动是否已完成,通常这意味着 SD 卡已经挂载并可以访问
        final boolean isSdCardReady = Utilities.isBootCompleted();
        // 用于管理桌面小部件的帮助类
        final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
    
        // 设定了一个清除数据库的标志位
        boolean clearDb = false;

        /** 检查是否需要进行数据库迁移。如果迁移失败,则清理整个数据库,这通常发生在数据模型发生较大变更时。
         *
         *  mApp.getModel()                        -> LauncherModel
         *  mApp.getModel().getModelDbController() -> ModelDbController
         */
        if (!mApp.getModel().getModelDbController().migrateGridIfNeeded()) {
            # Migration failed. Clear workspace.
            # 迁移失败
            clearDb = true;
        }
        
        // 重置数据库
        if (clearDb) {
            Log.d(TAG, "loadWorkspace: resetting launcher database");
            Settings.call(contentResolver, Settings.METHOD_CREATE_EMPTY_DB);
        }
    
        // 加载默认桌面布局
        Log.d(TAG, "loadWorkspace: loading default favorites");
        ######################################################################
        #  核心关注点 1
        #
        #  会调用到 LauncherProviderloadDefaultFavoritesIfNecessary() 方法,
        #  这个方法来判断是否需要加载默认的布局,如果清除了桌面数据
        #  或者手机恢复了出厂设置,就会加载默认的布局。
        ######################################################################
        Settings.call(contentResolver, Settings.METHOD_LOAD_DEFAULT_FAVORITES);
    
        // 加载数据库(我们放在后面分析)
        ...
    }
    
}

3.1.2 加载默认桌面布局

我们来看下 Settings.Call() 的流程:

// 加载默认桌面布局
Log.d(TAG, "loadWorkspace: loading default favorites");
######################################################################
#  核心关注点 1
#
#  会调用到 LauncherProviderloadDefaultFavoritesIfNecessary() 方法,
#  这个方法来判断是否需要加载默认的布局,如果清除了桌面数据
#  或者手机恢复了出厂设置,就会加载默认的布局。
######################################################################
Settings.call(contentResolver, Settings.METHOD_LOAD_DEFAULT_FAVORITES);

Settings 是 LauncherSettings 内部的静态方法:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/LauncherSettings.java

public class LauncherSettings {

    /**
     * Launcher settings
     */
    public static final class Settings {
        // CONTENT_URI 是一个 Uri 对象,表示内容提供者的 URI 地址。
        // 这是与 LauncherProvider 交互的入口点
        public static final Uri CONTENT_URI = Uri.parse("content://" +
                LauncherProvider.AUTHORITY + "/settings");
        
        public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";

        public static Bundle call(ContentResolver cr, String method) {
            return call(cr, method, null /* arg */);
        }
        
        public static Bundle call(ContentResolver cr, String method, String arg) {
            return cr.call(CONTENT_URI, method, arg, null);
        }
    }
}

3.1.3 LauncherProvider.call()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/LauncherProvider.java

public class LauncherProvider extends ContentProvider {

    /**
     * method: "load_default_favorites"
     * arg.  : null
     * extras: null
     */
    @Override
    public Bundle call(String method, final String arg, final Bundle extras) {
    
        switch (method) {
            ...
            case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
                getModelDbController().loadDefaultFavoritesIfNecessary();
                return null;
            }
            ...
        }
    
    }

}

3.1.4 ModelDbController.loadDefaultFavoritesIfNecessary()

继续跟踪 loadDefaultFavoritesIfNecessary() 代码:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/ModelDbController.java

public class ModelDbController {

    /**
     * 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
     *
     *   1) 来自应用限制
     *   2) 来自 Play 商店提供的软件包
     *   3) 来自已集成在 image 中的合作伙伴配置的 APK
     *   4) 特定设备的默认配置
     */
    @WorkerThread
    public synchronized void loadDefaultFavoritesIfNecessary() {
        // 确保数据库已创建。如果数据库不存在,则会创建一个新的数据库文件
        createDbIfNotExists();

        // 检查是否创建了空数据库,如果清除了 Launcher 数据,则会为 true
        if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()))) {
            // LauncherWidgetHolder 是一个用于管理小部件的容器,在加载布局时用来保存和处理小部件信息
            LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
            try {
                // 1. 尝试从系统配置的 launcher3.layout.provider 提供的 provider 里面获取布局资源
                AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder);
                if (loader == null) {
                    // 2. 尝试从带有 android.autoinstalls.config.action.PLAY_AUTO_INSTALL 的 apk 里面获取布局资源
                    loader = AutoInstallsLayout.get(mContext, widgetHolder, mOpenHelper);
                }

                if (loader == null) {
                    // 3. 尝试从系统内置的 Partner 应用里获取 workspace 默认配置
                    // 这个功能是提供给提供布局的第三方应用
                    final Partner partner = Partner.get(mContext.getPackageManager());
                    if (partner != null) {
                        int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT);
                        if (workspaceResId != 0) {
                            loader = new DefaultLayoutParser(mContext, widgetHolder,
                                    mOpenHelper, partner.getResources(), workspaceResId);
                        }
                    }
                }

                // 确定是否使用了外部提供的布局
                final boolean usingExternallyProvidedLayout = loader != null;
                if (loader == null) {
                    // 4. 尝试获取 Launcher 里的默认资源,比如 @xml/default_workspace_4x6
                    loader = getDefaultLayoutParser(widgetHolder);
                }

                // There might be some partially restored DB items, due to buggy restore logic in
                // previous versions of launcher.
                // 清空数据库
                mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                // Populate favorites table with initial favorites
                // 解析布局文件并写入数据库
                // 首先使用上面得到的 loader 获取,如果该 loader 提供的方式里面为空的布局,
                // 那么还是使用上面第 4 步的 loader
                if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                        && usingExternallyProvidedLayout) {
                    # Unable to load external layout. Cleanup and load the internal layout.
                    mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                    mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                            getDefaultLayoutParser(widgetHolder));
                }
                // 清理空数据库创建的标志,表示数据库已经准备好了。
                clearFlagEmptyDbCreated();
            } finally {
                // 无论方法是否成功执行,都销毁 widgetHolder 实例,释放相关资源
                widgetHolder.destroy();
            }
        }
    }

}

3.1.5 ModelDbController.getDefaultLayoutParser()

我们来看下加载默认的布局资源相关逻辑:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/ModelDbController.java

public class ModelDbController {

    private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) {
        /**
         * 获取 InvariantDeviceProfile 实例,它代表了设备的配置文件,包含布局相关的设置
         */
        InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
        
        /**
         * 获取默认布局资源id,比如:@xml/default_workspace_5x5
         * demoModeLayoutId:是否为演示模式
         *     1. 如果是则调用演示模式的布局 id -> idp.demoModeLayoutId
         *     2. 如不是则调用内部默认的布局 id -> idp.defaultLayoutId -> 我们只需要关心这个
         */
        int defaultLayout = idp.demoModeLayoutId != 0
                && mContext.getSystemService(UserManager.class).isDemoUser()
                ? idp.demoModeLayoutId : idp.defaultLayoutId;
                
        // 创建 DefaultLayoutParser 实例
        return new DefaultLayoutParser(mContext, widgetHolder,
                mOpenHelper, mContext.getResources(), defaultLayout);
    }

}

要想弄清楚 idp.defaultLayoutId 是哪个布局,我们得先来看 LauncherAppState.getIDP() 方法:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/LauncherAppState.java

public class LauncherAppState implements SafeCloseable {

    public static InvariantDeviceProfile getIDP(Context context) {
        // 获取 InvariantDeviceProfile 单例
        return InvariantDeviceProfile.INSTANCE.get(context);
    }

}

继续跟踪源码:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java

public class InvariantDeviceProfile {

    private InvariantDeviceProfile(Context context) {
        // 获取当前的网格名称
        String gridName = getCurrentGridName(context);
        // 初始化网格,可能会返回新的网格名称
        String newGridName = initGrid(context, gridName);
        if (!newGridName.equals(gridName)) {
            // 如果新的网格名称与当前网格名称不同,则更新 LauncherPrefs 中的网格名称
            LauncherPrefs.get(context).put(GRID_NAME, newGridName);
        }
        // 当用户解锁时执行指定的操作
        LockedUserState.get(context).runOnUserUnlocked(() -> {
            // 创建一个 DeviceGridState 实例并调用 writeToPrefs(context),
            // 将设备网格状态写入偏好设置
            new DeviceGridState(this).writeToPrefs(context);
        });
    
        // 设置一个监听器来处理设备显示配置的更改
        DisplayController.INSTANCE.get(context).setPriorityListener(
                (displayContext, info, flags) -> {
                    /**
                     * 监听器的回调方法检查标志 flags 是否包含配置更改的
                     * 相关标志(如密度、支持的边界、导航模式等)
                     */
                    if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
                            | CHANGE_NAVIGATION_MODE)) != 0) {
                        // 如果有变化,调用 onConfigChanged() 方法处理这些变化
                        onConfigChanged(displayContext);
                    }
                });
    }
    
}

我们关注 initGrid() 方法:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java

public class InvariantDeviceProfile {
    
    private String initGrid(Context context, String gridName) {
        // 获取当前设备的显示信息
        Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
        // 根据显示信息确定设备类型(如手机、平板等)
        @DeviceType int deviceType = getDeviceType(displayInfo);
    
        // 根据上下文、网格名称、设备类型和数据库恢复任务的状态,获取所有预定义的显示选项
        ArrayList<DisplayOption> allOptions =
                getPredefinedDeviceProfiles(context, gridName, deviceType,
                        RestoreDbTask.isPending(context));
        // 使用加权插值方法从所有预定义的显示选项中选择一个合适的选项,
        // 此方法考虑设备的显示信息和设备类型来做出选择。
        DisplayOption displayOption =
                invDistWeightedInterpolate(displayInfo, allOptions, deviceType);
        // 使用选定的显示选项初始化网格,这个方法可能会设置设备的显示网格以适应所选的显示选项
        initGrid(context, displayInfo, displayOption, deviceType);
        // 返回所选显示选项的网格名称
        return displayOption.grid.name;
    }

}

现在重点就来到了 getPredefinedDeviceProfiles() 方法:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java

public class InvariantDeviceProfile {
    
    private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context,
            String gridName, @DeviceType int deviceType, boolean allowDisabledGrid) {
        // 初始化一个空的 DisplayOption 列表,profiles 用于存储从 XML 解析出的所有显示配置文件
        ArrayList<DisplayOption> profiles = new ArrayList<>();
    
        /**
         * 开始解析 XML       -> 解析哪个 XML ?
         * 熟悉的 XML 文件来了 -> device_profiles.xml
         *
         * 使用 XmlResourceParser 解析 R.xml.device_profiles 资源文件,
         * 该文件包含所有预定义的设备配置文件
         */
        try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
            // 获取当前 XML 标签的深度,用于确定解析结束的条件
            final int depth = parser.getDepth();
            int type;
            // 循环解析 XML 文件,直到遇到 END_TAG(标签结束)或文档结束
            while (((type = parser.next()) != XmlPullParser.END_TAG ||
                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
                /**
                 * 看 if 判断里面的 GridOption.TAG_NAME -> "grid-option" -> 是不是很熟悉 ?
                 *
                 * 插播一点 device_profiles.xml 的内容
                 * <grid-option
                 *     launcher:name="4_by_6"
                 *     launcher:numRows="6"
                 *     launcher:defaultLayoutId="@xml/default_workspace_4x6"
                 *     launcher:deviceCategory="phone|multi_display"
                 *     ... />
                 *
                 *     <display-option
                 *         launcher:name="Large Phone"
                 *         ... />
                 * </grid-option>
                 */
                if ((type == XmlPullParser.START_TAG)
                        && GridOption.TAG_NAME.equals(parser.getName())) {
                    /**
                     * 当遇到 "grid-option" 对应标签,创建一个 GridOption 实例
                     *
                     * GridOption 的源码我们一会就要跟,这里先注意一下!!!
                     */
                    GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser));
                    // 检查 GridOption 是否启用,或根据 allowDisabledGrid 参数决定是否处理禁用的网格选项
                    if (gridOption.isEnabled(deviceType) || allowDisabledGrid) {
                        final int displayDepth = parser.getDepth();
                        while (((type = parser.next()) != XmlPullParser.END_TAG
                                || parser.getDepth() > displayDepth)
                                && type != XmlPullParser.END_DOCUMENT) {
                            // 如果满足条件,继续解析这个网格选项下的 display-option 标签
                            if ((type == XmlPullParser.START_TAG) && "display-option".equals(
                                    parser.getName())) {
                                // 将其添加到 profiles 列表中
                                profiles.add(new DisplayOption(gridOption, context,
                                        Xml.asAttributeSet(parser)));
                            }
                        }
                    }
                }
            }
        } catch (IOException | XmlPullParserException e) {
            throw new RuntimeException(e);
        }
    
    	// 创建一个空的 filteredProfiles 列表,用于存储符合条件的 DisplayOption
        ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
        
        /**
         * 如果提供了 gridName,则遍历 profiles 列表,找到与 gridName 匹配且启用的选项,
         * 将其添加到 filteredProfiles 列表中
         *
         * 我的设备测试 Log:
         * [ @@@ Pepsimarco ] 日志输出:===> gridName = 5_by_5
         */
        if (!TextUtils.isEmpty(gridName)) {
            for (DisplayOption option : profiles) {
                if (gridName.equals(option.grid.name)
                        && (option.grid.isEnabled(deviceType) || allowDisabledGrid)) {
                    filteredProfiles.add(option);
                }
            }
        }
        
        // 如果 filteredProfiles 为空(即没有匹配到合适的显示选项)
        if (filteredProfiles.isEmpty()) {
            // No grid found, use the default options
            // 则从 profiles 列表中选择可以作为默认选项 (canBeDefault) 的显示选项
            for (DisplayOption option : profiles) {
                if (option.canBeDefault) {
                    filteredProfiles.add(option);
                }
            }
        }
        
        if (filteredProfiles.isEmpty()) {
            throw new RuntimeException("No display option with canBeDefault=true");
        }
        
        // 返回 filteredProfiles,即符合条件的显示选项列表
        return filteredProfiles;
    }

}

至此,对于 device_profiles.xml 的解析,我们已经完全掌握了,还记得我们前面还遗漏的 GridOption 吗?

我们来跟源码:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java

public class InvariantDeviceProfile {

    public static final class GridOption {

        /**
         * GridOption 主要功能:
         *   1. 从传入的 AttributeSet 中获取并解析与网格布局相关的属性;
         *   2. 初始化 GridOption 类的成员变量,以便后续使用这些配置参数来设置或调整应用程序的显示布局。
         */
        public GridOption(Context context, AttributeSet attrs) {
            TypedArray a = context.obtainStyledAttributes(
                    attrs, R.styleable.GridDisplayOption);
            // 网格布局的名称
            name = a.getString(R.styleable.GridDisplayOption_name);
            // 网格的行数
            numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
            // 网格的列数
            numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
            // 搜索容器的列数,如果未指定,则默认与 numColumns 一致
            numSearchContainerColumns = a.getInt(
                    R.styleable.GridDisplayOption_numSearchContainerColumns, numColumns);
            // 数据库文件的名称
            dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
            // 默认布局资源 ID
            defaultLayoutId = a.getResourceId(
                    R.styleable.GridDisplayOption_defaultLayoutId, 0);
            // 演示模式下的布局资源 ID,如果未指定,则使用 defaultLayoutId
            demoModeLayoutId = a.getResourceId(
                    R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
            // 抽屉的样式资源 ID
            allAppsStyle = a.getResourceId(R.styleable.GridDisplayOption_allAppsStyle,
                    R.style.AllAppsStyleDefault);
            // 抽屉中的列数
            numAllAppsColumns = a.getInt(
                    R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
            // 数据库中应用程序抽屉的扩展列数,默认为 numAllAppsColumns 的两倍
            numDatabaseAllAppsColumns = a.getInt(
                    R.styleable.GridDisplayOption_numExtendedAllAppsColumns, 2 * numAllAppsColumns);
            // HotSeat 图标数量
            numHotseatIcons = a.getInt(
                    R.styleable.GridDisplayOption_numHotseatIcons, numColumns);
            // 数据库中 Hotseat 的扩展图标数量,默认为 numHotseatIcons 的两倍
            numDatabaseHotseatIcons = a.getInt(
                    R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons);
            // Hotseat 在不同屏幕方向和模式下的列跨度
            hotseatColumnSpan[INDEX_DEFAULT] = a.getInt(
                    R.styleable.GridDisplayOption_hotseatColumnSpan, numColumns);
            hotseatColumnSpan[INDEX_LANDSCAPE] = a.getInt(
                    R.styleable.GridDisplayOption_hotseatColumnSpanLandscape, numColumns);
            hotseatColumnSpan[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt(
                    R.styleable.GridDisplayOption_hotseatColumnSpanTwoPanelLandscape,
                    numColumns);
            hotseatColumnSpan[INDEX_TWO_PANEL_PORTRAIT] = a.getInt(
                    R.styleable.GridDisplayOption_hotseatColumnSpanTwoPanelPortrait,
                    numColumns);
            // 导航按钮 End 间距资源 ID
            inlineNavButtonsEndSpacing =
                    a.getResourceId(R.styleable.GridDisplayOption_inlineNavButtonsEndSpacing,
                            R.dimen.taskbar_button_margin_default);
            // 文件夹内的行数
            numFolderRows = a.getInt(
                    R.styleable.GridDisplayOption_numFolderRows, numRows);
            // 文件夹内的列数
            numFolderColumns = a.getInt(
                    R.styleable.GridDisplayOption_numFolderColumns, numColumns);
            // 文件夹的样式资源 ID
            folderStyle = a.getResourceId(R.styleable.GridDisplayOption_folderStyle,
                    INVALID_RESOURCE_HANDLE);
            // 单元格的样式资源 ID
            cellStyle = a.getResourceId(R.styleable.GridDisplayOption_cellStyle,
                    R.style.CellStyleDefault);
            // 网格是否可以缩放
            isScalable = a.getBoolean(
                    R.styleable.GridDisplayOption_isScalable, false);
            // 设备填充的资源 ID
            devicePaddingId = a.getResourceId(
                    R.styleable.GridDisplayOption_devicePaddingId, INVALID_RESOURCE_HANDLE);
            // 设备类别
            deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
                    DEVICE_CATEGORY_ALL);
        
            // 工作区规格的资源 ID
            if (FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE.get()) {
                mWorkspaceSpecsId = a.getResourceId(
                        R.styleable.GridDisplayOption_workspaceSpecsId, INVALID_RESOURCE_HANDLE);
            } else {
                mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
            }
        
            // 搜索栏在不同屏幕方向和模式下是否内联
            int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
                    DONT_INLINE_QSB);
            inlineQsb[INDEX_DEFAULT] =
                    (inlineForRotation & INLINE_QSB_FOR_PORTRAIT) == INLINE_QSB_FOR_PORTRAIT;
            inlineQsb[INDEX_LANDSCAPE] =
                    (inlineForRotation & INLINE_QSB_FOR_LANDSCAPE) == INLINE_QSB_FOR_LANDSCAPE;
            inlineQsb[INDEX_TWO_PANEL_PORTRAIT] =
                    (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_PORTRAIT)
                            == INLINE_QSB_FOR_TWO_PANEL_PORTRAIT;
            inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] =
                    (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE)
                            == INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE;
            // 释放 TypedArray,以避免内存泄漏
            a.recycle();
        }

    }

}

至此,我们再来看 idp.defaultLayoutId

# 源码路径:package/app/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java

public class InvariantDeviceProfile {

    public int defaultLayoutId;    // 这个默认布局是哪个,你清楚了吗?
    
    ==> defaultLayoutId = a.getResourceId(
                R.styleable.GridDisplayOption_defaultLayoutId, 0);

    ==> 就看你的 gridName 是哪个,比如我的设备是:5_by_5
    
    ==> 
	    <grid-option
	        launcher:name="5_by_5"
	        launcher:numRows="5"
	        launcher:numColumns="5"
	        launcher:numFolderRows="4"
	        launcher:numFolderColumns="4"
	        launcher:numHotseatIcons="5"
	        launcher:numExtendedHotseatIcons="6"
	        launcher:dbFile="launcher.db"
	        launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split"
	        launcher:defaultLayoutId="@xml/default_workspace_5x5"
	        launcher:deviceCategory="phone|multi_display" >
}

串起来了吧?讲到这,我们就完全讲通了你的 Launcher 为什么会解析 device_profiles.xml,并且去找对应的某一个 default_workspace_xxx 了。


3.2 加载数据库

分析完默认布局的流程外,我们继续第 2 部分遗留的数据库的加载逻辑:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java

public class LoaderTask implements Runnable {

    protected final LauncherAppState mApp;
    
    /##
     # run() -> loadWorkspace(allShortcuts, "", memoryLogger);
     #       -> loadWorkspaceImpl(allDeepShortcuts, selection, memoryLogger);
     #
     #     1. allDeepShortcuts: 存储应用快捷方式的列表,默认是空的
     #     2. selection: 可以指定选择条件,用于过滤,默认是没有传入任何条件的
     #     3. memoryLogger 是一个 LoaderMemoryLogger,是一个辅助记录器,用于捕获记录
     #        LoaderTask run() 方法执行过程中可能产生的异常及内存使用情况
     #/
    private void loadWorkspaceImpl(
            List<ShortcutInfo> allDeepShortcuts,
            String selection,
            @Nullable LoaderMemoryLogger memoryLogger) {
        ######################################################################
        #  核心 1: 加载默认桌面布局
        #
        #  会调用到 LauncherProviderloadDefaultFavoritesIfNecessary() 方法,
        #  这个方法来判断是否需要加载默认的布局,如果清除了桌面数据
        #  或者手机恢复了出厂设置,就会加载默认的布局。
        ######################################################################
        Settings.call(contentResolver, Settings.METHOD_LOAD_DEFAULT_FAVORITES);
    
        ######################################################################
        #  核心关注点 2
        #
        #  加载数据库
        ######################################################################
        synchronized (mBgDataModel) {
            mBgDataModel.clear();       # 清除数据模型(内存中的工作区数据)
            mPendingPackages.clear();   # 清除待处理的应用包信息
    
            # 获取当前正在进行的安装会话的列表
            final HashMap<PackageUserKey, SessionInfo> installingPkgs =
                    mSessionHelper.getActiveSessions();
            # 将每个安装会话的信息更新到图标缓存中 (IconCache),确保 Launcher 能够正确显示应用安装状态
            installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
    
            # 用于暂存包名和用户键值的临时对象
            final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
            # 初始化一个广播对象,用于在 Launcher 加载时广播状态信息
            mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
    
            # 存储固定在 Launcher 上的快捷方式信息的映射
            mShortcutKeyToPinnedShortcuts = new HashMap<>();
            ModelDbController dbController = mApp.getModel().getModelDbController();
            # 通过 LoaderCursor 查询数据库,获取工作空间的数据项
            final LoaderCursor c = new LoaderCursor(
                    dbController.query(TABLE_NAME, null, selection, null, null),
                    mApp, mUserManagerState);
            final Bundle extras = c.getExtras();
            # 从数据库查询结果的额外信息 (extras) 中获取数据库名称
            mDbName = extras == null ? null : extras.getString(Settings.EXTRA_DB_NAME);
            try {
                # 一个稀疏数组 (LongSparseArray),用于存储用户的解锁状态
                final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
    
                mUserManagerState.init(mUserCache, mUserManager);
                # 遍历用户配置文件,对每个用户:
                for (UserHandle user : mUserCache.getUserProfiles()) {
                    long serialNo = mUserCache.getSerialNumberForUser(user);
                    # 检查用户是否解锁
                    boolean userUnlocked = mUserManager.isUserUnlocked(user);
    
                    # 向 LauncherAppsService 获取 Pinned Shortcuts
                    if (userUnlocked) {
                        QueryResult pinnedShortcuts = new ShortcutRequest(context, user)
                                .query(ShortcutRequest.PINNED);
                        if (pinnedShortcuts.wasSuccess()) {
                            for (ShortcutInfo shortcut : pinnedShortcuts) {
                                # 如果用户解锁,查询固定的快捷方式,并将其存储在 mShortcutKeyToPinnedShortcuts 中
                                mShortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
                                        shortcut);
                            }
                        } else {
                            # Shortcut manager can fail due to some race condition when the
                            # lock state changes too frequently. For the purpose of the loading
                            # shortcuts, consider the user is still locked.
                            userUnlocked = false;
                        }
                    }
                    # 将用户的解锁状态存储在 unlockedUsers 中
                    unlockedUsers.put(serialNo, userUnlocked);
                }
    
                # 一个列表,存储了需要批量加载的图标请求信息
                List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();
    
                # 开始遍历数据库,一次读取数据库内容
                # 遍历 LoaderCursor 中的每一行,使用 processWorkspaceItem 方法
                # 处理每个工作空间项目,包括应用图标、小部件和快捷方式
                while (!mStopped && c.moveToNext()) {
                    processWorkspaceItem(c, memoryLogger, installingPkgs, isSdCardReady,
                            tempPackageKey, widgetHelper, pmHelper,
                            iconRequestInfos, unlockedUsers, isSafeMode, allDeepShortcuts);
                }
                # 批量加载工作空间图标,以提高加载性能
                tryLoadWorkspaceIconsInBulk(iconRequestInfos);
            } finally {
                IOUtils.closeSilently(c);
            }
    
            if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                # mModelDelegate: 负责将数据绑定到 UI 上的委托对象
                # 加载并绑定 WorkspaceItems
                mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
                # 加载并绑定 AllAppsItems
                mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
                # 加载并绑定 OtherItems
                mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
                # 标记加载过程完成,Launcher 可以开始响应用户操作
                mModelDelegate.markActive();
            }
    
            # Break early if we have stopped loading
            if (mStopped) {
                mBgDataModel.clear();
                return;
            }
    
            # Remove dead items,删除 processWorkspaceItem 中标记 delete 的数据
            mItemsDeleted = c.commitDeleted();
    
            # Sort the folder items, update ranks, and make sure all preview items are high res.
            # 文件夹内容的排序和更新
            # FolderGridOrganizer: 用于组织和验证文件夹网格布局
            FolderGridOrganizer verifier =
                    new FolderGridOrganizer(mApp.getInvariantDeviceProfile());
            # 遍历每一个文件夹
            for (FolderInfo folder : mBgDataModel.folders) {
                # 对文件夹内容进行排序
                Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                verifier.setFolderInfo(folder);
                int size = folder.contents.size();
    
                # Update ranks here to ensure there are no gaps caused by removed folder items.
                # Ranks are the source of truth for folder items, so cellX and cellY can be ignored
                # for now. Database will be updated once user manually modifies folder.
                # 更新项目的 rank,确保显示顺序正确
                for (int rank = 0; rank < size; ++rank) {
                    WorkspaceItemInfo info = folder.contents.get(rank);
                    info.rank = rank;
                    # 对于使用低分辨率图标且显示在文件夹预览中的项目,加载高分辨率图标
                    if (info.usingLowResIcon()
                            && info.itemType == Favorites.ITEM_TYPE_APPLICATION
                            && verifier.isItemInPreview(info.rank)) {
                        mIconCache.getTitleAndIcon(info, false);
                    }
                }
            }
    
            # 最后,将所有处理后的工作区项提交到数据库中
            c.commitRestoredItems();
        }
    }
    
}

3.2.1 processWorkspaceItem()

接下来我们就要开始重点分析 processWorkspaceItem() 的源码了:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java

public class LoaderTask implements Runnable {

    /##
     # 处理从数据库加载的桌面项目(如快捷方式、文件夹、应用小部件等)
     #
     #   c               : 用于读取数据库中存储的桌面项目数据的游标
     #   memoryLogger    : 用于记录内存消耗的日志工具
     #   installingPkgs  : 一个哈希表,用于跟踪正在安装的应用包
     #   isSdCardReady   : 标识 SD 卡是否已准备就绪的标志
     #   tempPackageKey  : 一个临时的 PackageUserKey 对象,用于处理包名和用户信息
     #   widgetHelper    : 用于管理小部件的辅助类
     #   pmHelper        : 用于处理包管理的辅助类
     #   iconRequestInfos: 用于存储要加载图标的请求信息的列表
     #   unlockedUsers   : 用于存储已解锁用户的标识符的稀疏数组
     #   isSafeMode      : 是否处于安全模式的标志
     #   allDeepShortcuts: 用于存储所有深层快捷方式的信息列表
     #/
    private void processWorkspaceItem(LoaderCursor c,
            LoaderMemoryLogger memoryLogger,
            HashMap<PackageUserKey, SessionInfo> installingPkgs,
            boolean isSdCardReady,
            PackageUserKey tempPackageKey,
            WidgetManagerHelper widgetHelper,
            PackageManagerHelper pmHelper,
            List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos,
            LongSparseArray<Boolean> unlockedUsers,
            boolean isSafeMode,
            List<ShortcutInfo> allDeepShortcuts) {
    
        try {
            if (c.user == null) {
                # User has been deleted, remove the item.
                # 首先检查用户是否存在。如果不存在,则标记为删除
                c.markDeleted("User has been deleted");
                return;
            }
    
            # 用于标记是否允许丢失目标应用的情况
            boolean allowMissingTarget = false;
            /##
             # 接下来就是实际的处理逻辑了,开始处理不同类型的桌面项目:
             #
             #   Favorites.ITEM_TYPE_SHORTCUT     : 处理普通快捷方式
             #   Favorites.ITEM_TYPE_APPLICATION  : 处理应用快捷方式
             #   Favorites.ITEM_TYPE_DEEP_SHORTCUT: 处理深层快捷方式
             #   Favorites.ITEM_TYPE_FOLDER       : 处理文件夹
             #   Favorites.ITEM_TYPE_APPWIDGET    : 处理应用小部件
             #/
            switch (c.itemType) {
                case Favorites.ITEM_TYPE_SHORTCUT:        # 如果是快捷方式
                case Favorites.ITEM_TYPE_APPLICATION:     # 如果是应用程序
                case Favorites.ITEM_TYPE_DEEP_SHORTCUT:   # 如果是深层快捷方式
                ...
                # 代码太多,我们分类分析
            }
        } catch (Exception e) {
            Log.e(TAG, "Desktop items loading interrupted", e);
        }
    }

}

3.2.2 应用/快捷方式

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java

public class LoaderTask implements Runnable {

    /##
     # 处理从数据库加载的桌面项目(如快捷方式、文件夹、应用小部件等)
     #
     #   c               : 用于读取数据库中存储的桌面项目数据的游标
     #   memoryLogger    : 用于记录内存消耗的日志工具
     #   installingPkgs  : 一个哈希表,用于跟踪正在安装的应用包
     #   isSdCardReady   : 标识 SD 卡是否已准备就绪的标志
     #   tempPackageKey  : 一个临时的 PackageUserKey 对象,用于处理包名和用户信息
     #   widgetHelper    : 用于管理小部件的辅助类
     #   pmHelper        : 用于处理包管理的辅助类
     #   iconRequestInfos: 用于存储要加载图标的请求信息的列表
     #   unlockedUsers   : 用于存储已解锁用户的标识符的稀疏数组
     #   isSafeMode      : 是否处于安全模式的标志
     #   allDeepShortcuts: 用于存储所有深层快捷方式的信息列表
     #/
    private void processWorkspaceItem(LoaderCursor c,
            LoaderMemoryLogger memoryLogger,
            HashMap<PackageUserKey, SessionInfo> installingPkgs,
            boolean isSdCardReady,
            PackageUserKey tempPackageKey,
            WidgetManagerHelper widgetHelper,
            PackageManagerHelper pmHelper,
            List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos,
            LongSparseArray<Boolean> unlockedUsers,
            boolean isSafeMode,
            List<ShortcutInfo> allDeepShortcuts) {
    
        try {
            if (c.user == null) {
                # User has been deleted, remove the item.
                # 首先检查用户是否存在。如果不存在,则标记为删除
                c.markDeleted("User has been deleted");
                return;
            }
    
            # 用于标记是否允许丢失目标应用的情况
            boolean allowMissingTarget = false;
            /##
             # 接下来就是实际的处理逻辑了,开始处理不同类型的桌面项目:
             #
             #   Favorites.ITEM_TYPE_SHORTCUT     : 处理普通快捷方式
             #   Favorites.ITEM_TYPE_APPLICATION  : 处理应用快捷方式
             #   Favorites.ITEM_TYPE_DEEP_SHORTCUT: 处理深层快捷方式
             #   Favorites.ITEM_TYPE_FOLDER       : 处理文件夹
             #   Favorites.ITEM_TYPE_APPWIDGET    : 处理应用小部件
             #/
            switch (c.itemType) {
                case Favorites.ITEM_TYPE_SHORTCUT:        # 如果是快捷方式
                case Favorites.ITEM_TYPE_APPLICATION:     # 如果是应用程序
                case Favorites.ITEM_TYPE_DEEP_SHORTCUT:   # 如果是深层快捷方式
                    # 从 LoaderCursor 中解析 Intent 对象
                    Intent intent = c.parseIntent();
                    # 验证 intent 有效性
                    if (intent == null) {
                        # 如果 Intent 为空或无效,则标记为已删除
                        c.markDeleted("Invalid or null intent");
                        return;
                    }
    
                    # 根据用户管理器状态判断该用户是否为静默用户
                    int disabledState = mUserManagerState.isUserQuiet(c.serialNumber)
                            ? WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER : 0;
                    # 从 Intent 中获取组件名称
                    ComponentName cn = intent.getComponent();
                    String targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
    
                    # 如果目标包名为空且项目类型不是快捷方式,则标记为已删除
                    if (TextUtils.isEmpty(targetPkg)
                            && c.itemType != Favorites.ITEM_TYPE_SHORTCUT) {
                        c.markDeleted("Only legacy shortcuts can have null package");
                        return;
                    }
    
                    # 检查目标包是否有效(如果没有目标包名,则表示这是一个隐式 Intent,始终有效)
                    boolean validTarget = TextUtils.isEmpty(targetPkg)
                            || mLauncherApps.isPackageEnabled(targetPkg, c.user);
    
                    # 如果组件名不为空且目标有效且项目不是深度快捷方式
                    if (cn != null && validTarget && c.itemType
                            != Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                        # If the apk is present and the shortcut points to a specific component.
    
                        # 如果目标活动存在,标记项目为已恢复
                        if (mLauncherApps.isActivityEnabled(cn, c.user)) {
                            # no special handling necessary for this item
                            c.markRestored();  # 标记为已恢复
                        } else {
                            # 否则,尝试找到一个备用的启动活动
                            intent = pmHelper.getAppLaunchIntent(targetPkg, c.user);
                            # 如果找到了启动活动,更新 Intent 并重置恢复标志
                            if (intent != null) {
                                c.restoreFlag = 0;
                                c.updater().put(
                                        Favorites.INTENT,
                                        intent.toUri(0)).commit();
                                # 更新组件名称
                                cn = intent.getComponent();
                            } else {
                                # 如果找不到启动活动,则标记项目为已删除并返回
                                c.markDeleted("Unable to find a launch target");
                                return;
                            }
                        }
                    }
                    
                    # 如果目标包无效,则处理恢复状态
                    if (!TextUtils.isEmpty(targetPkg) && !validTarget) {
                        if (c.restoreFlag != 0) {
                            # 如果应用程序还未恢复
                            FileLog.d(TAG, "package not yet restored: " + targetPkg);
    
                            tempPackageKey.update(targetPkg, c.user);
                            if (c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED)) {
                                # 恢复已经开始,不做任何操作
                            } else if (installingPkgs.containsKey(tempPackageKey)) {
                                # 恢复正在进行,更新恢复标记
                                c.restoreFlag |= WorkspaceItemInfo.FLAG_RESTORE_STARTED;
                                c.updater().put(Favorites.RESTORED,
                                        c.restoreFlag).commit();
                            } else {
                                # 否则标记项目为已删除并返回
                                c.markDeleted("Unrestored app removed: " + targetPkg);
                                return;
                            }
                        } else if (pmHelper.isAppOnSdcard(targetPkg, c.user)) {
                            # 如果包在 SD 卡上但不可用,标记应用不可用
                            disabledState |= WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE;
                            # 允许丢失目标,仍然将图标添加到工作区
                            allowMissingTarget = true;
                        } else if (!isSdCardReady) {
                            # 如果 SD 卡未准备好,将包名添加到待处理列表中
                            Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
                            mPendingPackages.add(new PackageUserKey(targetPkg, c.user));
                            # 允许丢失目标,仍然将图标添加到工作区
                            allowMissingTarget = true;
                        } else {
                            # 如果包无效且 SD 卡已准备好,标记项目为已删除并返回
                            c.markDeleted("Invalid package removed: " + targetPkg);
                            return;
                        }
                    }
    
                    # 如果恢复标志包含 Web UI 支持标志,设置目标无效
                    if ((c.restoreFlag & WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI) != 0) {
                        validTarget = false;
                    }
    
                    if (validTarget) {
                        # 如果快捷方式指向一个有效的目标,标记为已恢复
                        c.markRestored();
                    }
    
                    # 决定是否使用低分辨率的图标
                    boolean useLowResIcon = !c.isOnWorkspaceOrHotseat();
    
                    WorkspaceItemInfo info;
                    if (c.restoreFlag != 0) {
                        # 如果恢复标记不为0,获取恢复的 item 信息
                        info = c.getRestoredItemInfo(intent);
                    } else if (c.itemType == Favorites.ITEM_TYPE_APPLICATION) {
                        # 创建应用程序快捷方式信息
                        info = c.getAppShortcutInfo(
                                intent, allowMissingTarget, useLowResIcon, true);
                    } else if (c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                        # 处理深层快捷方式
                        ShortcutKey key = ShortcutKey.fromIntent(intent, c.user);
                        if (unlockedUsers.get(c.serialNumber)) {
                            # 如果用户已解锁,获取深度快捷方式信息
                            ShortcutInfo pinnedShortcut = mShortcutKeyToPinnedShortcuts.get(key);
                            if (pinnedShortcut == null) {
                                # 如果未找到固定快捷方式,标记项目为已删除并返回
                                c.markDeleted("Pinned shortcut not found");
                                return;
                            }
                            # 创建深度快捷方式的信息对象
                            info = new WorkspaceItemInfo(pinnedShortcut, mApp.getContext());
                            # If the pinned deep shortcut is no longer published,
                            # use the last saved icon instead of the default.
                            mIconCache.getShortcutIcon(info, pinnedShortcut, c::loadIcon);
    
                            # 检查应用是否被挂起
                            if (pmHelper.isAppSuspended(
                                    pinnedShortcut.getPackage(), info.user)) {
                                info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
                            }
                            # 更新 Intent
                            intent = info.getIntent();
                            allDeepShortcuts.add(pinnedShortcut);
                        } else {
                            # 如果用户被锁定,创建一个简单的工作区项目信息
                            info = c.loadSimpleWorkspaceItem();
                            info.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                        }
                    } else {
                        # 处理普通快捷方式
                        info = c.loadSimpleWorkspaceItem();
    
                        # Shortcuts are only available on the primary profile
                        if (!TextUtils.isEmpty(targetPkg)
                                && pmHelper.isAppSuspended(targetPkg, c.user)) {
                            disabledState |= FLAG_DISABLED_SUSPENDED;
                        }
                        info.options = c.getOptions();
    
                        # App shortcuts that used to be automatically added to Launcher
                        # did not always have the correct intent flags set, so do that here
                        # 修复某些旧快捷方式的 Intent 标志
                        if (intent.getAction() != null
                                && intent.getCategories() != null
                                && intent.getAction().equals(Intent.ACTION_MAIN)
                                && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                        }
                    }
    
                    # 检查 info 是否为空
                    if (info != null) {
                        if (info.itemType != Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                            # Skip deep shortcuts; their title and icons have already been
                            # loaded above.
                            # 将图标请求信息添加到列表
                            iconRequestInfos.add(c.createIconRequestInfo(info, useLowResIcon));
                        }
                        # 应用通用属性
                        c.applyCommonProperties(info);
                        # 更新信息对象的 Intent 和其他属性
                        info.intent = intent;
                        info.rank = c.getRank();
                        info.spanX = 1;
                        info.spanY = 1;
                        info.runtimeStatusFlags |= disabledState;
                        # 如果处于安全模式且目标应用不是系统应用,标记其为禁用
                        if (isSafeMode && !isSystemApp(mApp.getContext(), intent)) {
                            info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE;
                        }
                        # 获取启动活动信息
                        LauncherActivityInfo activityInfo = c.getLauncherActivityInfo();
                        if (activityInfo != null) {
                            # 设置下载进度
                            info.setProgressLevel(
                                    PackageManagerHelper.getLoadingProgress(activityInfo),
                                    PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
                        }
    
                        if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
                            tempPackageKey.update(targetPkg, c.user);
                            SessionInfo si = installingPkgs.get(tempPackageKey);
                            if (si == null) {
                                info.runtimeStatusFlags
                                        &= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
                            } else if (activityInfo == null) {
                                int installProgress = (int) (si.getProgress() * 100);
    
                                info.setProgressLevel(installProgress,
                                        PackageInstallInfo.STATUS_INSTALLING);
                            }
                        }
                        # 检查并添加 item 信息到数据模型
                        c.checkAndAddItem(info, mBgDataModel, memoryLogger);
                    } else {
                        throw new RuntimeException("Unexpected null WorkspaceItemInfo");
                    }
                    break;
                ===>>> 应用/快捷方式处理完毕
                ...
            }
        } catch (Exception e) {
            Log.e(TAG, "Desktop items loading interrupted", e);
        }
    }

}

3.2.3 文件夹类型

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java

public class LoaderTask implements Runnable {

    /##
     # 处理从数据库加载的桌面项目(如快捷方式、文件夹、应用小部件等)
     #
     #   c               : 用于读取数据库中存储的桌面项目数据的游标
     #   memoryLogger    : 用于记录内存消耗的日志工具
     #   installingPkgs  : 一个哈希表,用于跟踪正在安装的应用包
     #   isSdCardReady   : 标识 SD 卡是否已准备就绪的标志
     #   tempPackageKey  : 一个临时的 PackageUserKey 对象,用于处理包名和用户信息
     #   widgetHelper    : 用于管理小部件的辅助类
     #   pmHelper        : 用于处理包管理的辅助类
     #   iconRequestInfos: 用于存储要加载图标的请求信息的列表
     #   unlockedUsers   : 用于存储已解锁用户的标识符的稀疏数组
     #   isSafeMode      : 是否处于安全模式的标志
     #   allDeepShortcuts: 用于存储所有深层快捷方式的信息列表
     #/
    private void processWorkspaceItem(LoaderCursor c,
            LoaderMemoryLogger memoryLogger,
            HashMap<PackageUserKey, SessionInfo> installingPkgs,
            boolean isSdCardReady,
            PackageUserKey tempPackageKey,
            WidgetManagerHelper widgetHelper,
            PackageManagerHelper pmHelper,
            List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos,
            LongSparseArray<Boolean> unlockedUsers,
            boolean isSafeMode,
            List<ShortcutInfo> allDeepShortcuts) {
    
        try {
            if (c.user == null) {
                # User has been deleted, remove the item.
                # 首先检查用户是否存在。如果不存在,则标记为删除
                c.markDeleted("User has been deleted");
                return;
            }
    
            # 用于标记是否允许丢失目标应用的情况
            boolean allowMissingTarget = false;
            /##
             # 接下来就是实际的处理逻辑了,开始处理不同类型的桌面项目:
             #
             #   Favorites.ITEM_TYPE_SHORTCUT     : 处理普通快捷方式
             #   Favorites.ITEM_TYPE_APPLICATION  : 处理应用快捷方式
             #   Favorites.ITEM_TYPE_DEEP_SHORTCUT: 处理深层快捷方式
             #   Favorites.ITEM_TYPE_FOLDER       : 处理文件夹
             #   Favorites.ITEM_TYPE_APPWIDGET    : 处理应用小部件
             #/
            switch (c.itemType) {
                case Favorites.ITEM_TYPE_SHORTCUT:        # 如果是快捷方式
                case Favorites.ITEM_TYPE_APPLICATION:     # 如果是应用程序
                case Favorites.ITEM_TYPE_DEEP_SHORTCUT:   # 如果是深层快捷方式
                    # 应用/快捷方式处理逻辑
                ----------------------------------------------------------------------
                case Favorites.ITEM_TYPE_FOLDER:          # 如果是文件夹类型
                    # 从后台数据模型中查找或创建文件夹信息对象
                    FolderInfo folderInfo = mBgDataModel.findOrMakeFolder(c.id);
                    # 应用通用属性到文件夹信息对象
                    c.applyCommonProperties(folderInfo);
    
                    # 不要使用 c.getTitle(),因为此方法已将其缓存在 FolderInfo 中
                    folderInfo.title = c.getString(c.mTitleIndex);
                    # 设置文件夹在工作区的占用宽度和高度(单位是格子)
                    folderInfo.spanX = 1;
                    folderInfo.spanY = 1;
                    # 应用文件夹的其他选项
                    folderInfo.options = c.getOptions();
    
                    # 恢复标记为已恢复,无需特殊处理恢复的文件夹
                    c.markRestored();
                    # 检查并添加文件夹信息到数据模型,并记录内存使用情况
                    c.checkAndAddItem(folderInfo, mBgDataModel, memoryLogger);
                    break;
                ===>>> 文件夹类型处理完毕
                ...
            }
        } catch (Exception e) {
            Log.e(TAG, "Desktop items loading interrupted", e);
        }
    }

}

3.2.4 Widget 类型

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java

public class LoaderTask implements Runnable {

    /##
     # 处理从数据库加载的桌面项目(如快捷方式、文件夹、应用小部件等)
     #
     #   c               : 用于读取数据库中存储的桌面项目数据的游标
     #   memoryLogger    : 用于记录内存消耗的日志工具
     #   installingPkgs  : 一个哈希表,用于跟踪正在安装的应用包
     #   isSdCardReady   : 标识 SD 卡是否已准备就绪的标志
     #   tempPackageKey  : 一个临时的 PackageUserKey 对象,用于处理包名和用户信息
     #   widgetHelper    : 用于管理小部件的辅助类
     #   pmHelper        : 用于处理包管理的辅助类
     #   iconRequestInfos: 用于存储要加载图标的请求信息的列表
     #   unlockedUsers   : 用于存储已解锁用户的标识符的稀疏数组
     #   isSafeMode      : 是否处于安全模式的标志
     #   allDeepShortcuts: 用于存储所有深层快捷方式的信息列表
     #/
    private void processWorkspaceItem(LoaderCursor c,
            LoaderMemoryLogger memoryLogger,
            HashMap<PackageUserKey, SessionInfo> installingPkgs,
            boolean isSdCardReady,
            PackageUserKey tempPackageKey,
            WidgetManagerHelper widgetHelper,
            PackageManagerHelper pmHelper,
            List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos,
            LongSparseArray<Boolean> unlockedUsers,
            boolean isSafeMode,
            List<ShortcutInfo> allDeepShortcuts) {
    
        try {
            if (c.user == null) {
                # User has been deleted, remove the item.
                # 首先检查用户是否存在。如果不存在,则标记为删除
                c.markDeleted("User has been deleted");
                return;
            }
    
            # 用于标记是否允许丢失目标应用的情况
            boolean allowMissingTarget = false;
            /##
             # 接下来就是实际的处理逻辑了,开始处理不同类型的桌面项目:
             #
             #   Favorites.ITEM_TYPE_SHORTCUT     : 处理普通快捷方式
             #   Favorites.ITEM_TYPE_APPLICATION  : 处理应用快捷方式
             #   Favorites.ITEM_TYPE_DEEP_SHORTCUT: 处理深层快捷方式
             #   Favorites.ITEM_TYPE_FOLDER       : 处理文件夹
             #   Favorites.ITEM_TYPE_APPWIDGET    : 处理应用小部件
             #/
            switch (c.itemType) {
                case Favorites.ITEM_TYPE_SHORTCUT:        # 如果是快捷方式
                case Favorites.ITEM_TYPE_APPLICATION:     # 如果是应用程序
                case Favorites.ITEM_TYPE_DEEP_SHORTCUT:   # 如果是深层快捷方式
                    # 应用/快捷方式处理逻辑
                ----------------------------------------------------------------------
                case Favorites.ITEM_TYPE_FOLDER:          # 如果是文件夹类型
                    # 文件夹类型处理逻辑
                ----------------------------------------------------------------------
                case Favorites.ITEM_TYPE_APPWIDGET:         # 如果类型是应用小部件
                    # 检查是否禁用所有小部件,如果是,则标记为已删除并返回
                    if (WidgetsModel.GO_DISABLE_WIDGETS) {
                        c.markDeleted("Only legacy shortcuts can have null package");
                        return;
                    }
                    # Follow through,继续处理其他小部件类型
                case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:  # 如果类型是自定义的应用小部件
                    # 读取所有 Launcher 特定的小部件详细信息
                    boolean customWidget = c.itemType
                            == Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
                    # 获取小部件的 ID
                    int appWidgetId = c.getAppWidgetId();
                    # 为应用小部件恢复获取 Provider 的字符串
                    String savedProvider = c.getAppWidgetProvider();
                    final ComponentName component;
    
                    # 如果选项中包含搜索小部件标志
                    if ((c.getOptions() & LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET) != 0) {
                        # 获取搜索小部件的组件名称
                        component  = QsbContainerView.getSearchComponentName(mApp.getContext());
                        if (component == null) {
                            # 如果组件为空,则标记为已删除并返回
                            c.markDeleted("Discarding SearchWidget without packagename ");
                            return;
                        }
                    } else {
                        # 从保存的字符串中解析组件名称
                        component = ComponentName.unflattenFromString(savedProvider);
                    }
                    
                    # 检查小部件的 ID 是否有效
                    final boolean isIdValid =
                            !c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
                    # 检查小部件的 Provider 是否已准备好
                    final boolean wasProviderReady =
                            !c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
    
                    # 创建一个包含组件名称和用户的 ComponentKey
                    ComponentKey providerKey = new ComponentKey(component, c.user);
                    # 如果 mWidgetProvidersMap 不包含该键,则查找并添加小部件 Provider
                    if (!mWidgetProvidersMap.containsKey(providerKey)) {
                        mWidgetProvidersMap.put(providerKey,
                                widgetHelper.findProvider(component, c.user));
                    }
                    # 从 mWidgetProvidersMap 中获取小部件 Provider 信息
                    final AppWidgetProviderInfo provider = mWidgetProvidersMap.get(providerKey);
    
                    # 检查小部件 Provider 是否有效
                    final boolean isProviderReady = isValidProvider(provider);
                    # 如果不是安全模式、不是自定义小部件、小部件 Provider 曾经准备好但现在不再准备好
                    if (!isSafeMode && !customWidget && wasProviderReady && !isProviderReady) {
                        # 标记为已删除并返回
                        c.markDeleted("Deleting widget that isn't installed anymore: " + provider);
                    } else {
                        # 处理小部件恢复状态
                        LauncherAppWidgetInfo appWidgetInfo;
                        if (isProviderReady) {
                            # 如果小部件 Provider 准备好,则创建小部件信息
                            appWidgetInfo =
                                    new LauncherAppWidgetInfo(appWidgetId, provider.provider);
    
                            # Provider 可用,小部件要么可用,要么不可用,不需要跟踪未来的恢复更新
                            int status = c.restoreFlag
                                    & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
                                    & ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
                            if (!wasProviderReady) {
                                # 如果 Provider 之前未准备好,则更新状态和 UI 标志
                                if (isIdValid) {
                                    status |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
                                }
                            }
                            appWidgetInfo.restoreStatus = status;
                        } else {
                            # 如果 Provider 不可用,记录恢复状态并创建小部件信息
                            Log.v(TAG, "Widget restore pending id=" + c.id
                                    + " appWidgetId=" + appWidgetId
                                    + " status =" + c.restoreFlag);
                            appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, component);
                            appWidgetInfo.restoreStatus = c.restoreFlag;
    
                            # 更新临时包键并获取安装进度
                            tempPackageKey.update(component.getPackageName(), c.user);
                            SessionInfo si = installingPkgs.get(tempPackageKey);
                            Integer installProgress = si == null
                                    ? null
                                    : (int) (si.getProgress() * 100);
    
                            if (c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED)) {
                                # 恢复已经开始一次
                            } else if (installProgress != null) {
                                # 应用恢复已经开始,更新标志
                                appWidgetInfo.restoreStatus
                                        |= LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
                            } else if (!isSafeMode) {
                                # 如果不是安全模式,则标记为已删除并返回
                                c.markDeleted("Unrestored widget removed: " + component);
                                return;
                            }
    
                            # 设置安装进度
                            appWidgetInfo.installProgress =
                                    installProgress == null ? 0 : installProgress;
                        }
                        
                        # 如果恢复标志包含直接配置标志,则设置绑定选项
                        if (appWidgetInfo.hasRestoreFlag(
                                LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
                            appWidgetInfo.bindOptions = c.parseIntent();
                        }
    
                        # 应用通用属性到小部件信息
                        c.applyCommonProperties(appWidgetInfo);
                        # 设置小部件的占用格子数量
                        appWidgetInfo.spanX = c.getSpanX();
                        appWidgetInfo.spanY = c.getSpanY();
                        # 设置小部件的选项
                        appWidgetInfo.options = c.getOptions();
                        # 设置用户信息
                        appWidgetInfo.user = c.user;
                        # 设置小部件的来源容器
                        appWidgetInfo.sourceContainer = c.getAppWidgetSource();
    
                        # 如果小部件的大小无效,则标记为已删除并返回
                        if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) {
                            c.markDeleted("Widget has invalid size: "
                                    + appWidgetInfo.spanX + "x" + appWidgetInfo.spanY);
                            return;
                        }
                        
                        # 获取小部件 Provider 的信息并检查其最小尺寸要求
                        LauncherAppWidgetProviderInfo widgetProviderInfo =
                                widgetHelper.getLauncherAppWidgetInfo(appWidgetId);
                        if (widgetProviderInfo != null
                                && (appWidgetInfo.spanX < widgetProviderInfo.minSpanX
                                || appWidgetInfo.spanY < widgetProviderInfo.minSpanY)) {
                            FileLog.d(TAG, "Widget " + widgetProviderInfo.getComponent()
                                    + " minSizes not meet: span=" + appWidgetInfo.spanX
                                    + "x" + appWidgetInfo.spanY + " minSpan="
                                    + widgetProviderInfo.minSpanX + "x"
                                    + widgetProviderInfo.minSpanY);
                            logWidgetInfo(mApp.getInvariantDeviceProfile(),
                                    widgetProviderInfo);
                        }
                        
                        # 如果小部件不在工作区或 HotSeat 中,则标记为已删除并返回
                        if (!c.isOnWorkspaceOrHotseat()) {
                            c.markDeleted("Widget found where container != CONTAINER_DESKTOP"
                                    + "nor CONTAINER_HOTSEAT - ignoring!");
                            return;
                        }
    
                        # 如果不是自定义小部件,检查 Provider 名称是否匹配,更新状态
                        if (!customWidget) {
                            String providerName = appWidgetInfo.providerName.flattenToString();
                            if (!providerName.equals(savedProvider)
                                    || (appWidgetInfo.restoreStatus != c.restoreFlag)) {
                                c.updater()
                                        .put(Favorites.APPWIDGET_PROVIDER,
                                                providerName)
                                        .put(Favorites.RESTORED,
                                                appWidgetInfo.restoreStatus)
                                        .commit();
                            }
                        }
    
                        # 如果恢复状态不是完成,则创建待处理的小部件信息并获取标题和图标
                        if (appWidgetInfo.restoreStatus
                                != LauncherAppWidgetInfo.RESTORE_COMPLETED) {
                            appWidgetInfo.pendingItemInfo = WidgetsModel.newPendingItemInfo(
                                    mApp.getContext(),
                                    appWidgetInfo.providerName,
                                    appWidgetInfo.user);
                            mIconCache.getTitleAndIconForApp(
                                    appWidgetInfo.pendingItemInfo, false);
                        }
    
                        # 检查并添加小部件信息到数据模型中
                        c.checkAndAddItem(appWidgetInfo, mBgDataModel);
                    }
                    break;
            }
        } catch (Exception e) {
            Log.e(TAG, "Desktop items loading interrupted", e);
        }
    }

}

至此,loadWorkspace() 的工作总算是分析完了!


3.3 绑定 Workspace

LauncherBinder 的 bindWorkspace() 函数,在其父类 BaseLauncherBinder 中定义。

1. BaseLauncherBinder.bindWorkspace()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/BaseLauncherBinder.java

public abstract class BaseLauncherBinder {
    
    /##
     # Binds all loaded data to actual views on the main thread.
     #/
    public void bindWorkspace(boolean incrementBindId, boolean isBindSync) {
        /##
         # ENABLE_WORKSPACE_LOADING_OPTIMIZATION 标志主要用于:
         # 控制一系列与工作区加载过程相关的优化措施。这些优化可能涵盖了加载速度、
         # 内存使用、I/O 操作等方面的改进,以提升用户体验。
         # Log 实测为 false,所以我们可以略过这个条件判断。
         #
        if (FeatureFlags.ENABLE_WORKSPACE_LOADING_OPTIMIZATION.get()) {
            DisjointWorkspaceBinder workspaceBinder =
                    initWorkspaceBinder(incrementBindId, mBgDataModel.collectWorkspaceScreens());
            workspaceBinder.bindCurrentWorkspacePages(isBindSync);
            workspaceBinder.bindOtherWorkspacePages();
        } else {
            bindWorkspaceAllAtOnce(incrementBindId, isBindSync);
        }
    }
    
}

BaseLauncherBinder.bindWorkspaceAllAtOnce()

所以,现在我们继续跟踪 bindWorkspaceAllAtOnce() 方法:

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/BaseLauncherBinder.java

public abstract class BaseLauncherBinder {

    private void bindWorkspaceAllAtOnce(boolean incrementBindId, boolean isBindSync) {
        # Save a copy of all the bg-thread collections
        ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
        ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
        final IntArray orderedScreenIds = new IntArray();
        ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
        final int workspaceItemCount;
        synchronized (mBgDataModel) {
            workspaceItems.addAll(mBgDataModel.workspaceItems);
            appWidgets.addAll(mBgDataModel.appWidgets);
            # 可能有多个屏幕(比如图标很多,一个屏幕放不下的情况)
            orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
            mBgDataModel.extraItems.forEach(extraItems::add);
            if (incrementBindId) {
                mBgDataModel.lastBindId++;
            }
            mMyBindingId = mBgDataModel.lastBindId;
            workspaceItemCount = mBgDataModel.itemsIdMap.size();
        }
    
        # callback 对象是 Launcher 这个类
        for (Callbacks cb : mCallbacksList) {
            new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
                    workspaceItems, appWidgets, extraItems, orderedScreenIds)
                    .bind(isBindSync, workspaceItemCount);
        }
    }

}

该函数中,创建了 workspaceItems、appWidgets、orderedScreenIds(屏幕数)等信息的数组。然后创建 UnifiedWorkspaceBinder,调用其 bind() 函数开始绑定。

UnifiedWorkspaceBinder.bind()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/BaseLauncherBinder.java

public abstract class BaseLauncherBinder {
    
    private class UnifiedWorkspaceBinder {
    
        private void bind(boolean isBindSync, int workspaceItemCount) {
            # 拿到当前的屏幕ID
            final IntSet currentScreenIds =
                    mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
            Objects.requireNonNull(currentScreenIds, "Null screen ids provided by " + mCallbacks);
        
            # 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(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
                    otherWorkspaceItems);
            filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
                    otherAppWidgets);
            final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
            sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
            sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
        
            # Tell the workspace that we are about to start binding items
            executeCallbacksTask(c -> {
                c.clearPendingBinds();
                c.startBinding();
            }, mUiExecutor);
        
            # Bind workspace screens
            # 1. 先绑定屏幕
            executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
        
            # Load items on the current page.
            # 2. 往当前的屏幕上绑定数据内容
            bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);
            bindAppWidgets(currentAppWidgets, mUiExecutor);
            if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
                mExtraItems.forEach(item ->
                        executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
            }
        
            RunnableList pendingTasks = new RunnableList();
            Executor pendingExecutor = pendingTasks::add;
            # 3. 绑定非当前屏幕上的内容
            bindWorkspaceItems(otherWorkspaceItems, pendingExecutor);
            bindAppWidgets(otherAppWidgets, pendingExecutor);
            executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
            pendingExecutor.execute(
                    () -> {
                        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                        ItemInstallQueue.INSTANCE.get(mApp.getContext())
                                .resumeModelPush(FLAG_LOADER_RUNNING);
                    });
        
            executeCallbacksTask(
                    c -> {
                        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                        c.onInitialBindComplete(
                                currentScreenIds, pendingTasks, workspaceItemCount, isBindSync);
                    }, mUiExecutor);
        
            mCallbacks.bindStringCache(mBgDataModel.stringCache.clone());
        }
    
    }
}

UnifiedWorkspaceBinder 的 bind 函数中,首先拿到当前屏幕(就是呈现给用户的第一个屏幕)ID,然后优先往第一个屏幕上绑定内容,之后再绑定其他屏幕的内容。

Launcher.bindScreens()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/Launcher.java

public class Launcher extends StatefulActivity<LauncherState>
        implements LauncherExterns, Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
        PluginListener<LauncherOverlayPlugin> {
        
    @Override
    public void bindScreens(IntArray orderedScreenIds) {
        int firstScreenPosition = 0;
        if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
                orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) {
            orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
            orderedScreenIds.add(firstScreenPosition, Workspace.FIRST_SCREEN_ID);
        } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
            # If there are no screens, we need to have an empty screen
            mWorkspace.addExtraEmptyScreens();
        }
        bindAddScreens(orderedScreenIds);
    
        # After we have added all the screens, if the wallpaper was locked to the default state,
        # then notify to indicate that it can be released and a proper wallpaper offset can be
        # computed before the next layout
        mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout();
    }
    
}

Launcher.bindAddScreens()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/Launcher.java

public class Launcher extends StatefulActivity<LauncherState>
        implements LauncherExterns, Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
        PluginListener<LauncherOverlayPlugin> {
        
    private void bindAddScreens(IntArray orderedScreenIds) {
        if (mDeviceProfile.isTwoPanels) {
            # Some empty pages might have been removed while the phone was in a single panel
            # mode, so we want to add those empty pages back.
            IntSet screenIds = IntSet.wrap(orderedScreenIds);
            orderedScreenIds.forEach(screenId -> screenIds.add(mWorkspace.getScreenPair(screenId)));
            orderedScreenIds = screenIds.getArray();
        }
    
        int count = orderedScreenIds.size();
        for (int i = 0; i < count; i++) {
            int screenId = orderedScreenIds.get(i);
            if (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID) {
                # No need to bind the first screen, as its always bound.
                continue;
            }
            mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
        }
    }
    
}

Workspace.insertNewWorkspaceScreenBeforeEmptyScreen()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/Workspace.java

public class Workspace<T extends View & PageIndicator> extends PagedView<T>
        implements DropTarget, DragSource, View.OnTouchListener,
        DragController.DragListener, Insettable, StateHandler<LauncherState>,
        WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {
        
    public void insertNewWorkspaceScreenBeforeEmptyScreen(int 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();
        }
        insertNewWorkspaceScreen(screenId, insertIndex);
    }

}

Workspace.insertNewWorkspaceScreen()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/Workspace.java

public class Workspace<T extends View & PageIndicator> extends PagedView<T>
        implements DropTarget, DragSource, View.OnTouchListener,
        DragController.DragListener, Insettable, StateHandler<LauncherState>,
        WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {
        
    public CellLayout insertNewWorkspaceScreen(int 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.
        DeviceProfile dp = mLauncher.getDeviceProfile();
        CellLayout newScreen;
        if (FOLDABLE_SINGLE_PAGE.get() && dp.isTwoPanels) {
            newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                    R.layout.workspace_screen_foldable, this, false /* attachToRoot */);
        } else {
            newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                    R.layout.workspace_screen, this, false /* attachToRoot */);
        }
    
        mWorkspaceScreens.put(screenId, newScreen);
        mScreenOrder.add(insertIndex, screenId);
        addView(newScreen, insertIndex);
        mStateTransitionAnimation.applyChildState(
                mLauncher.getStateManager().getState(), newScreen, insertIndex);
    
        updatePageScrollValues();
        updateCellLayoutPadding();
        return newScreen;
    }

}

bindWorkspaceItems()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/BaseLauncherBinder.java

public abstract class BaseLauncherBinder {
    
    private class UnifiedWorkspaceBinder {
    
        private void bindWorkspaceItems(
                final ArrayList<ItemInfo> workspaceItems, final Executor executor) {
            # Bind the workspace items
            int count = workspaceItems.size();
            for (int i = 0; i < count; i += ITEMS_CHUNK) {
                final int start = i;
                final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
                executeCallbacksTask(
                        c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
                        executor);
            }
        }
    
    }
}

Launcher.bindItems()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/Launcher.java

public class Launcher extends StatefulActivity<LauncherState>
        implements LauncherExterns, Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
        PluginListener<LauncherOverlayPlugin> {
        
    @Override
    public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
        bindItems(items, forceAnimateIcons, /* focusFirstItemForAccessibility= */ false);
    }

    public void bindItems(
            final List<ItemInfo> items,
            final boolean forceAnimateIcons,
            final boolean focusFirstItemForAccessibility) {
        # Get the list of added items and intersect them with the set of items here
        final Collection<Animator> bounceAnims = new ArrayList<>();
        boolean canAnimatePageChange = canAnimatePageChange();
        Workspace<?> workspace = mWorkspace;
        int newItemsScreenId = -1;
        int end = items.size();
        View newView = null;
        for (int i = 0; i < end; i++) {
            final ItemInfo item = items.get(i);
    
            # Short circuit if we are loading dock items for a configuration which has no dock
            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
                    mHotseat == null) {
                continue;
            }
    
            final View view;
            switch (item.itemType) {
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
                    WorkspaceItemInfo info = (WorkspaceItemInfo) item;
                    view = createShortcut(info);
                    break;
                }
                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
                    view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                            (FolderInfo) item);
                    break;
                }
                case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
                    FolderInfo info = (FolderInfo) item;
                    # TODO (jeremysim b/274189428): Create app pair icon
                    view = null;
                    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");
            }
    
            /##
             # Remove colliding items.
             #/
            CellPos presenterPos = getCellPosMapper().mapModelToPresenter(item);
            if (item.container == CONTAINER_DESKTOP) {
                CellLayout cl = mWorkspace.getScreenWithId(presenterPos.screenId);
                if (cl != null && cl.isOccupied(presenterPos.cellX, presenterPos.cellY)) {
                    View v = cl.getChildAt(presenterPos.cellX, presenterPos.cellY);
                    if (v == null) {
                        Log.e(TAG, "bindItems failed when removing colliding item=" + item);
                    }
                    Object tag = v.getTag();
                    String desc = "Collision while binding workspace item: " + item
                            + ". Collides with " + tag;
                    if (FeatureFlags.IS_STUDIO_BUILD) {
                        throw (new RuntimeException(desc));
                    } else {
                        getModelWriter().deleteItemFromDatabase(item, desc);
                        continue;
                    }
                }
            }
            workspace.addInScreenFromBind(view, item);
            if (forceAnimateIcons) {
                # Animate all the applications up now
                view.setAlpha(0f);
                view.setScaleX(0f);
                view.setScaleY(0f);
                bounceAnims.add(createNewAppBounceAnimation(view, i));
                newItemsScreenId = presenterPos.screenId;
            }
    
            if (newView == null) {
                newView = view;
            }
        }
    
        View viewToFocus = newView;
        # Animate to the correct pager
        if (forceAnimateIcons && newItemsScreenId > -1) {
            AnimatorSet anim = new AnimatorSet();
            anim.playTogether(bounceAnims);
            if (focusFirstItemForAccessibility && viewToFocus != null) {
                anim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        viewToFocus.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
                    }
                });
            }
    
            int currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
            final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
            final Runnable startBounceAnimRunnable = anim::start;
    
            if (canAnimatePageChange && newItemsScreenId != currentScreenId) {
                # We post the animation slightly delayed to prevent slowdowns
                # when we are loading right after we return to launcher.
                mWorkspace.postDelayed(new Runnable() {
                    public void run() {
                        if (mWorkspace != null) {
                            closeOpenViews(false);
    
                            mWorkspace.snapToPage(newScreenIndex);
                            mWorkspace.postDelayed(startBounceAnimRunnable,
                                    NEW_APPS_ANIMATION_DELAY);
                        }
                    }
                }, NEW_APPS_PAGE_MOVE_DELAY);
            } else {
                mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
            }
        } else if (focusFirstItemForAccessibility && viewToFocus != null) {
            viewToFocus.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
        }
        workspace.requestLayout();
    }

}

WorkspaceLayoutManager.addInScreenFromBind()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/WorkspaceLayoutManager.java

public interface WorkspaceLayoutManager {

    default void addInScreenFromBind(View child, ItemInfo info) {
        CellPos presenterPos = getCellPosMapper().mapModelToPresenter(info);
        int x = presenterPos.cellX;
        int y = presenterPos.cellY;
        if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
                || info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
            int screenId = presenterPos.screenId;
            x = getHotseat().getCellXFromOrder(screenId);
            y = getHotseat().getCellYFromOrder(screenId);
        }
        addInScreen(child, info.container, presenterPos.screenId, x, y, info.spanX, info.spanY);
    }

}

WorkspaceLayoutManager.addInScreen()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/WorkspaceLayoutManager.java

public interface WorkspaceLayoutManager {

    default void addInScreen(View child, int container, int screenId, int x, int y,
            int spanX, int spanY) {
        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
            if (getScreenWithId(screenId) == null) {
                Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
                // DEBUGGING - Print out the stack trace to see where we are adding from
                new Throwable().printStackTrace();
                return;
            }
        }
        if (EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
            // This should never happen
            throw new RuntimeException("Screen id should not be extra empty screen: " + screenId);
        }
    
        final CellLayout layout;
        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
                || container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
            layout = getHotseat();
    
            // Hide folder title in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(false);
            }
        } else {
            // Show folder title if not in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(true);
            }
            layout = getScreenWithId(screenId);
        }
    
        ViewGroup.LayoutParams genericLp = child.getLayoutParams();
        CellLayoutLayoutParams lp;
        if (genericLp == null || !(genericLp instanceof CellLayoutLayoutParams)) {
            lp = new CellLayoutLayoutParams(x, y, spanX, spanY);
        } else {
            lp = (CellLayoutLayoutParams) genericLp;
            lp.setCellX(x);
            lp.setCellY(y);
            lp.cellHSpan = spanX;
            lp.cellVSpan = spanY;
        }
    
        if (spanX < 0 && spanY < 0) {
            lp.isLockedToGrid = false;
        }
    
        // Get the canonical child id to uniquely represent this view in this screen
        ItemInfo info = (ItemInfo) child.getTag();
        int childId = info.getViewId();
    
        boolean markCellsAsOccupied = !(child instanceof Folder);
        if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
            // TODO: This branch occurs when the workspace is adding views
            // outside of the defined grid
            // maybe we should be deleting these items from the LauncherModel?
            Log.e(TAG, "Failed to add to item at (" + lp.getCellX() + "," + lp.getCellY()
                    + ") to CellLayout");
        }
    
        child.setHapticFeedbackEnabled(false);
        child.setOnLongClickListener(getWorkspaceChildOnLongClickListener());
        if (child instanceof DropTarget) {
            onAddDropTarget((DropTarget) child);
        }
    }

}

CellLayout.addViewToCellLayout()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/CellLayout.java

public class CellLayout extends ViewGroup {

    public boolean addViewToCellLayout(View child, int index, int childId,
            CellLayoutLayoutParams params, boolean markCells) {
        final CellLayoutLayoutParams lp = params;
    
        // Hotseat icons - remove text
        if (child instanceof BubbleTextView) {
            BubbleTextView bubbleChild = (BubbleTextView) child;
            bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
        }
    
        child.setScaleX(mChildScale);
        child.setScaleY(mChildScale);
    
        // Generate an id for each view, this assumes we have at most 256x256 cells
        // per workspace screen
        if (lp.getCellX() >= 0 && lp.getCellX() <= mCountX - 1
                && lp.getCellY() >= 0 && lp.getCellY() <= mCountY - 1) {
            // If the horizontal or vertical span is set to -1, it is taken to
            // mean that it spans the extent of the CellLayout
            if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
            if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
    
            child.setId(childId);
            if (LOGD) {
                Log.d(TAG, "Adding view to ShortcutsAndWidgetsContainer: " + child);
            }
            mShortcutsAndWidgets.addView(child, index, lp);
    
            if (markCells) markCellsAsOccupiedForView(child);
    
            return true;
        }
        return false;
    }
    
}

bindAppWidgets()

# 源码路径:package/app/Launcher3/src/com/android/launcher3/model/BaseLauncherBinder.java

public abstract class BaseLauncherBinder {
    
    private class UnifiedWorkspaceBinder {
    
        private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets, Executor executor) {
            # Bind the widgets, one at a time
            int count = appWidgets.size();
            for (int i = 0; i < count; i++) {
                final ItemInfo widget = appWidgets.get(i);
                executeCallbacksTask(
                        c -> c.bindItems(Collections.singletonList(widget), false), executor);
            }
        }
    
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值