深入谈谈Launcher的启动流程

Launcher 的启动 ---- 从开机到桌面显示


一、全景流程图

scss 复制代码
系统启动
  │
  ├── Zygote 进程启动
  │     └── fork SystemServer 进程
  │
  ├── SystemServer 启动
  │     ├── startBootstrapServices()
  │     │     ├── AMS (ActivityManagerService)
  │     │     ├── PMS (PackageManagerService)
  │     │     └── WMS (WindowManagerService)
  │     │
  │     ├── startOtherServices()
  │     │     └── AMS.systemReady()
  │     │           └── startHomeActivityLocked() ★ 启动 Launcher
  │     │
  │     ↓
  │
  ├── AMS 启动 Launcher Activity
  │     ├── 解析 Intent(ACTION_MAIN + CATEGORY_HOME)
  │     ├── PMS 查询匹配的 Activity → Launcher3
  │     ├── 创建 Launcher 进程 (Zygote fork)
  │     └── 启动 Launcher Activity
  │
  ├── Launcher.onCreate()
  │     ├── LauncherAppState 初始化
  │     ├── DeviceProfile 计算
  │     ├── setContentView (创建视图树)
  │     └── LauncherModel.addCallbacksAndLoad()
  │
  ├── LoaderTask.run() (后台线程)
  │     ├── loadWorkspace()    → 读 DB
  │     ├── bindWorkspace()    → 回调 UI ─→ 桌面图标显示
  │     ├── loadAllApps()      → 读 PMS
  │     ├── bindAllApps()      → 回调 UI ─→ AllApps 就绪
  │     ├── loadDeepShortcuts()
  │     └── loadWidgets()      → 回调 UI ─→ Widget 显示
  │
  └── 桌面完整显示 ✅

二、阶段一:SystemServer 启动 Launcher

2.1 AMS.systemReady() 触发

java 复制代码
// frameworks/base/services/java/com/android/server/SystemServer.java

private void startOtherServices() {
    // ... 启动各种系统服务 ...

    // 所有服务就绪后
    mActivityManagerService.systemReady(() -> {
        // 系统准备完毕的回调
        // 启动系统 UI、Launcher 等
    }, BOOT_TIMINGS_TRACE_LOG);
}

2.2 AMS 启动 Home Activity

java 复制代码
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public void systemReady(final Runnable goingCallback, ...) {
    // ... 系统准备工作 ...

    // ★ 启动桌面
    mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
}

// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
boolean startHomeOnAllDisplays(int userId, String reason) {
    // 对每个显示屏启动 Home Activity
    for (int displayId : getDisplayIds()) {
        startHomeOnDisplay(userId, reason, displayId);
    }
}

boolean startHomeOnDisplay(int userId, String reason, int displayId) {
    // ★ 构建 Home Intent
    Intent homeIntent = getHomeIntent();
    
    // 启动 Activity
    mActivityStartController.startHomeActivity(homeIntent, aInfo,
            reason, displayId);
}

2.3 构建 Home Intent

java 复制代码
// ActivityTaskManagerService.java

Intent getHomeIntent() {
    Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.addCategory(Intent.CATEGORY_HOME);   // ★ 关键 Category
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
            | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    return intent;
}

// Launcher3 的 AndroidManifest.xml 中匹配此 Intent:
// <activity android:name=".Launcher">
//     <intent-filter>
//         <action android:name="android.intent.action.MAIN" />
//         <category android:name="android.intent.category.HOME" />
//         <category android:name="android.intent.category.DEFAULT" />
//     </intent-filter>
// </activity>

2.4 PMS 解析匹配

java 复制代码
// PackageManagerService 查找匹配 HOME Intent 的 Activity

// PMS 内部查询过程:
// 1. 遍历所有已安装应用的 AndroidManifest
// 2. 找到声明了 ACTION_MAIN + CATEGORY_HOME 的 Activity
// 3. 如果有多个匹配(如有多个 Launcher),弹出选择器
// 4. 如果只有一个或用户已设置默认,直接启动
// 5. 系统预装的 Launcher 通常在 /system/priv-app/ 中

2.5 创建 Launcher 进程

scss 复制代码
AMS.startProcessLocked("com.android.launcher3")
    │
    ├── 通过 Socket 通知 Zygote
    │
    ├── Zygote.fork() 创建新进程
    │
    ├── 新进程执行 ActivityThread.main()
    │     ├── Looper.prepareMainLooper()
    │     ├── ActivityThread thread = new ActivityThread()
    │     ├── thread.attach(false) → 向 AMS 注册
    │     └── Looper.loop() → 进入消息循环
    │
    └── AMS 调度启动 Launcher Activity
          └── ActivityThread.handleLaunchActivity()
                └── Launcher.onCreate()

时序图

scss 复制代码
SystemServer          AMS              Zygote           Launcher进程
    │                  │                 │                    │
    │── systemReady →  │                 │                    │
    │                  │                 │                    │
    │          startHomeActivity         │                    │
    │                  │                 │                    │
    │                  │── fork req ───→ │                    │
    │                  │                 │── fork() ────────→ │
    │                  │                 │                    │
    │                  │                 │    ActivityThread.main()
    │                  │                 │                    │
    │                  │← attach ────────│────────────────── │
    │                  │                 │                    │
    │                  │── scheduleLaunchActivity ──────────→ │
    │                  │                 │                    │
    │                  │                 │         Launcher.onCreate()
    │                  │                 │                    │

三、阶段二:Launcher.onCreate()

3.1 完整的 onCreate 流程

java 复制代码
// packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // ━━━━━━━━━━━━ Step 1: 获取全局单例 ━━━━━━━━━━━━
    LauncherAppState app = LauncherAppState.getInstance(this);
    mModel = app.getModel();
    mIconCache = app.getIconCache();

    // ━━━━━━━━━━━━ Step 2: 设备配置 ━━━━━━━━━━━━
    InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
    mDeviceProfile = idp.getDeviceProfile(this);
    // 计算: 屏幕尺寸 → 网格行列数、图标大小、各区域尺寸

    // ━━━━━━━━━━━━ Step 3: 设置布局 ━━━━━━━━━━━━
    setContentView(R.layout.launcher);
    setupViews();

    // ━━━━━━━━━━━━ Step 4: 恢复状态(如果有) ━━━━━━━━━━━━
    if (savedInstanceState != null) {
        restoreState(savedInstanceState);
    }

    // ━━━━━━━━━━━━ Step 5: 启动数据加载 ━━━━━━━━━━━━
    mModel.addCallbacksAndLoad(this);
    // ↑ 这里触发后台加载数据
}

3.2 Step 1: LauncherAppState 初始化

java 复制代码
// LauncherAppState.java --- 全局单例

public class LauncherAppState {

    private static LauncherAppState INSTANCE;

    private final IconCache mIconCache;
    private final LauncherModel mModel;
    private final InvariantDeviceProfile mInvariantDeviceProfile;

    public static LauncherAppState getInstance(Context context) {
        if (INSTANCE == null) {
            INSTANCE = new LauncherAppState(context);
        }
        return INSTANCE;
    }

    private LauncherAppState(Context context) {
        // ① 设备配置(行列数、图标大小等)
        mInvariantDeviceProfile = new InvariantDeviceProfile(context);

        // ② 图标缓存
        mIconCache = new IconCache(context, mInvariantDeviceProfile);

        // ③ 数据模型(管理桌面数据加载和更新)
        mModel = new LauncherModel(context, this, mIconCache, ...);

        // ④ 注册应用安装/卸载监听
        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
        launcherApps.registerCallback(mModel.getLauncherAppsCallback());

        // ⑤ 注册包变更广播
        // ACTION_PACKAGE_ADDED / REMOVED / CHANGED 等
    }
}
scss 复制代码
LauncherAppState 持有关系:

LauncherAppState (单例)
    ├── InvariantDeviceProfile (设备配置)
    │     └── DeviceProfile (具体屏幕配置)
    │
    ├── IconCache (图标缓存)
    │     ├── 内存缓存 HashMap
    │     └── 数据库缓存 app_icons.db
    │
    └── LauncherModel (数据管理)
          ├── LoaderTask (后台加载)
          ├── BgDataModel (后台数据模型)
          ├── AllAppsList (应用列表)
          └── ModelWriter (数据写入)

3.3 Step 2: DeviceProfile 计算

java 复制代码
// InvariantDeviceProfile.java

public InvariantDeviceProfile(Context context) {
    // 1. 获取屏幕尺寸
    WindowManager wm = context.getSystemService(WindowManager.class);
    Display display = wm.getDefaultDisplay();
    Point screenSize = new Point();
    display.getRealSize(screenSize);

    // 2. 读取 device_profiles.xml 中的所有配置
    ArrayList<GridOption> allOptions = parseGridOptions(context);

    // 3. 根据屏幕尺寸匹配最佳配置
    GridOption closestProfile = findClosestProfile(
            allOptions, screenSize.x, screenSize.y);

    // 4. 设置参数
    numRows = closestProfile.numRows;           // 如: 5行
    numColumns = closestProfile.numColumns;     // 如: 4列
    numHotseatIcons = closestProfile.numHotseatIcons;  // 如: 4个
    iconSize = closestProfile.iconSize;         // 如: 52dp
}

// DeviceProfile.java --- 计算像素级尺寸
public DeviceProfile(...) {
    // 将 dp 转换为 px
    iconSizePx = (int)(iconSize * metrics.density);

    // 计算各区域尺寸
    int availableHeight = heightPx - statusBarHeight - navigationBarHeight;
    hotseatBarSizePx = computeHotseatBarSize();
    int workspaceHeight = availableHeight - hotseatBarSizePx;

    // 计算格子尺寸
    cellWidthPx = (widthPx - workspacePaddingLeft - workspacePaddingRight)
                  / numColumns;
    cellHeightPx = workspaceHeight / numRows;
}
ini 复制代码
DeviceProfile 计算结果示例(1080x1920 手机):

┌──────────────── 1080px ────────────────┐
│         StatusBar (48px)                │
├─────────────────────────────────────────┤
│                                         │  ← workspacePadding
│  ┌────┐ ┌────┐ ┌────┐ ┌────┐          │
│  │270 │ │270 │ │270 │ │270 │  cellW   │  ← 每格 270px
│  │px  │ │px  │ │px  │ │px  │          │
│  │    │ │    │ │    │ │    │  cellH   │  ← 每格 300px
│  └────┘ └────┘ └────┘ └────┘          │
│  ┌────┐ ┌────┐ ┌────┐ ┌────┐          │  5行 × 4列
│  │    │ │    │ │    │ │    │          │
│  └────┘ └────┘ └────┘ └────┘          │
│  ...                                    │  ← Workspace 区域
│  ┌────┐ ┌────┐ ┌────┐ ┌────┐          │
│  │    │ │    │ │    │ │    │          │
│  └────┘ └────┘ └────┘ └────┘          │
│                                         │
├─────────────────────────────────────────┤
│  ┌────┐ ┌────┐ ┌────┐ ┌────┐          │  ← Hotseat (96px)
│  │    │ │    │ │    │ │    │          │
│  └────┘ └────┘ └────┘ └────┘          │
├─────────────────────────────────────────┤
│         NavigationBar (48px)            │
└─────────────────────────────────────────┘

iconSizePx = 52dp × 2.75 = 143px
cellWidthPx = (1080 - padding) / 4 = ~270px
cellHeightPx = workspaceHeight / 5 = ~300px

3.4 Step 3: setupViews() 创建视图

java 复制代码
// Launcher.java

private void setupViews() {
    setContentView(R.layout.launcher);

    // 找到各核心 View
    mDragLayer = findViewById(R.id.drag_layer);
    mWorkspace = mDragLayer.findViewById(R.id.workspace);
    mHotseat = mDragLayer.findViewById(R.id.hotseat);
    mAppsView = findViewById(R.id.apps_view);

    // 设置 Workspace
    mWorkspace.initParentViews(mDragLayer);
    mWorkspace.setup(mDragController);  // 绑定拖拽控制器

    // 设置 Hotseat
    mHotseat.setWorkspace(mWorkspace);

    // 设置 DragController
    mDragController.setMoveTarget(mWorkspace);
    mDragController.addDropTarget(mWorkspace);
    mDragController.addDropTarget(mDeleteDropTarget);

    // 设置 AllApps
    mAppsView.setup(mWorkspace);
}
xml 复制代码
<!-- res/layout/launcher.xml -->
<com.android.launcher3.DragLayer
    android:id="@+id/drag_layer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 桌面工作区 -->
    <com.android.launcher3.Workspace
        android:id="@+id/workspace"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- 底部常驻栏 -->
    <com.android.launcher3.Hotseat
        android:id="@+id/hotseat"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />

    <!-- 页面指示器 -->
    <com.android.launcher3.pageindicators.WorkspacePageIndicator
        android:id="@+id/page_indicator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <!-- 搜索栏 -->
    <include layout="@layout/search_container_workspace" />

    <!-- 拖拽时的删除/信息目标栏 -->
    <include layout="@layout/drop_target_bar" />

    <!-- 全部应用列表(初始隐藏,上滑展开) -->
    <com.android.launcher3.allapps.AllAppsContainerView
        android:id="@+id/apps_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="invisible" />

    <!-- Widget 选择面板 -->
    <include layout="@layout/widgets_full_sheet" />

</com.android.launcher3.DragLayer>
scss 复制代码
View 层级树:

DragLayer (FrameLayout)
  ├── Workspace (PagedView --- 可横向翻页)
  │     ├── CellLayout [Page 0]
  │     │     └── ShortcutAndWidgetContainer
  │     │           ├── BubbleTextView (图标)
  │     │           ├── BubbleTextView (图标)
  │     │           ├── FolderIcon (文件夹)
  │     │           └── AppWidgetHostView (Widget)
  │     ├── CellLayout [Page 1]
  │     │     └── ShortcutAndWidgetContainer
  │     │           └── ...
  │     └── CellLayout [Page N]
  │
  ├── Hotseat
  │     └── CellLayout
  │           └── ShortcutAndWidgetContainer
  │                 ├── BubbleTextView (Phone)
  │                 ├── BubbleTextView (Messages)
  │                 ├── BubbleTextView (Chrome)
  │                 └── BubbleTextView (Camera)
  │
  ├── SearchContainerView (搜索栏)
  ├── WorkspacePageIndicator (页面指示器 ● ● ●)
  ├── DropTargetBar (拖拽目标栏: 删除/卸载)
  │
  └── AllAppsContainerView (全部应用,初始隐藏)
        ├── SearchEditText
        └── AllAppsRecyclerView
              ├── BubbleTextView (App1)
              ├── BubbleTextView (App2)
              └── ...

3.5 Step 5: 触发数据加载

java 复制代码
// Launcher.onCreate() 最后一步
mModel.addCallbacksAndLoad(this);

// LauncherModel.java
public void addCallbacksAndLoad(Callbacks callbacks) {
    // 注册回调(Launcher 实现了 Callbacks 接口)
    synchronized (mLock) {
        mCallbacks = new WeakReference<>(callbacks);
    }

    // ★ 启动后台加载
    startLoader();
}

public void startLoader() {
    // 取消之前的加载任务
    stopLoader();

    // 创建新的 LoaderTask
    LoaderResults loaderResults = new LoaderResults(
            mApp, mBgDataModel, mBgAllAppsList, mCallbacks);

    mLoaderTask = new LoaderTask(
            mApp, mBgAllAppsList, mBgDataModel,
            mModelDelegate, loaderResults);

    // ★ 在专用后台线程执行
    MODEL_EXECUTOR.execute(mLoaderTask);
}

四、阶段三:LoaderTask 后台加载

4.1 LoaderTask.run() 总流程

java 复制代码
// LoaderTask.java

public class LoaderTask implements Runnable {

    @Override
    public void run() {
        try (LauncherModel.LoaderTransaction transaction =
                mApp.getModel().beginLoader(this)) {

            // ━━━ Step 1: 加载桌面数据 ━━━
            loadWorkspace();

            // ━━━ Step 2: 绑定桌面到 UI ━━━
            mResults.bindWorkspace(true /* incrementBindId */);

            // ━━━ Step 3: 加载全部应用 ━━━
            loadAllApps();

            // ━━━ Step 4: 绑定全部应用到 UI ━━━
            mResults.bindAllApps();

            // ━━━ Step 5: 加载快捷方式 ━━━
            loadDeepShortcuts();
            mResults.bindDeepShortcuts();

            // ━━━ Step 6: 加载 Widget ━━━
            loadWidgets();
            mResults.bindWidgets();

            // 提交事务
            transaction.commit();

        } catch (CancellationException e) {
            // 加载被取消
        }
    }
}
scss 复制代码
LoaderTask 执行时序:

后台线程 MODEL_EXECUTOR                    主线程 (UI)
        │                                      │
        │── loadWorkspace() ──────────┐        │
        │   读取 launcher.db           │        │
        │   构建 ItemInfo 列表         │        │
        │   验证包是否存在              │        │
        │←────────────────────────────┘        │
        │                                      │
        │── mResults.bindWorkspace() ────────→ │
        │                                      │── bindScreens() 创建 CellLayout
        │                                      │── bindItems() 添加图标到格子
        │                                      │── bindAppWidgets() 添加 Widget
        │                                      │── finishBindingItems()
        │                                      │
        │   ★ 此时桌面已经可以显示              │   桌面显示! ✅
        │                                      │
        │── loadAllApps() ────────────┐        │
        │   LauncherApps.getActivityList()│    │
        │   构建 AppInfo 列表          │        │
        │   IconCache 加载图标         │        │
        │←────────────────────────────┘        │
        │                                      │
        │── mResults.bindAllApps() ──────────→ │
        │                                      │── bindAllApplications()
        │                                      │   AllAppsContainerView 更新
        │                                      │
        │── loadDeepShortcuts() ──────┐        │
        │←────────────────────────────┘        │
        │── mResults.bindDeepShortcuts() ────→ │
        │                                      │
        │── loadWidgets() ────────────┐        │
        │←────────────────────────────┘        │
        │── mResults.bindWidgets() ──────────→ │
        │                                      │   全部加载完成! ✅

4.2 loadWorkspace() ------ 读取桌面数据

java 复制代码
// LoaderTask.java

private void loadWorkspace() {
    // 1. 查询数据库
    final ContentResolver cr = mContext.getContentResolver();
    final Uri uri = LauncherSettings.Favorites.CONTENT_URI;
    final Cursor c = cr.query(uri, null, null, null, null);

    // 2. 遍历每条记录,构建数据对象
    try {
        final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
        final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
        final int containerIndex = c.getColumnIndexOrThrow(Favorites.CONTAINER);
        final int itemTypeIndex = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
        final int screenIndex = c.getColumnIndexOrThrow(Favorites.SCREEN);
        final int cellXIndex = c.getColumnIndexOrThrow(Favorites.CELLX);
        final int cellYIndex = c.getColumnIndexOrThrow(Favorites.CELLY);
        final int spanXIndex = c.getColumnIndexOrThrow(Favorites.SPANX);
        final int spanYIndex = c.getColumnIndexOrThrow(Favorites.SPANY);

        while (c.moveToNext()) {
            int itemType = c.getInt(itemTypeIndex);
            int container = c.getInt(containerIndex);

            switch (itemType) {
                case Favorites.ITEM_TYPE_APPLICATION:
                case Favorites.ITEM_TYPE_SHORTCUT: {
                    // ★ 快捷方式/应用图标
                    String intentStr = c.getString(intentIndex);
                    Intent intent = Intent.parseUri(intentStr, 0);

                    // 验证目标应用是否仍然安装
                    ComponentName cn = intent.getComponent();
                    if (!isValidPackage(cn.getPackageName())) {
                        // 应用已卸载,标记删除
                        itemsToRemove.add(id);
                        continue;
                    }

                    WorkspaceItemInfo info = new WorkspaceItemInfo();
                    info.id = c.getLong(idIndex);
                    info.intent = intent;
                    info.container = container;
                    info.screenId = c.getInt(screenIndex);
                    info.cellX = c.getInt(cellXIndex);
                    info.cellY = c.getInt(cellYIndex);

                    // 加载图标
                    mIconCache.getTitleAndIcon(info, false);

                    // 添加到数据模型
                    if (container == Favorites.CONTAINER_DESKTOP) {
                        mBgDataModel.workspaceItems.add(info);
                    } else if (container == Favorites.CONTAINER_HOTSEAT) {
                        mBgDataModel.workspaceItems.add(info);
                    } else {
                        // 在文件夹中
                        FolderInfo folder = findOrMakeFolder(container);
                        folder.add(info);
                    }
                    break;
                }

                case Favorites.ITEM_TYPE_FOLDER: {
                    // ★ 文件夹
                    FolderInfo folderInfo = new FolderInfo();
                    folderInfo.id = c.getLong(idIndex);
                    folderInfo.title = c.getString(titleIndex);
                    folderInfo.cellX = c.getInt(cellXIndex);
                    folderInfo.cellY = c.getInt(cellYIndex);
                    folderInfo.screenId = c.getInt(screenIndex);

                    mBgDataModel.folders.put(folderInfo.id, folderInfo);
                    mBgDataModel.workspaceItems.add(folderInfo);
                    break;
                }

                case Favorites.ITEM_TYPE_APPWIDGET: {
                    // ★ Widget
                    LauncherAppWidgetInfo widgetInfo =
                            new LauncherAppWidgetInfo();
                    widgetInfo.appWidgetId = c.getInt(appWidgetIdIndex);
                    widgetInfo.spanX = c.getInt(spanXIndex);
                    widgetInfo.spanY = c.getInt(spanYIndex);
                    widgetInfo.cellX = c.getInt(cellXIndex);
                    widgetInfo.cellY = c.getInt(cellYIndex);

                    mBgDataModel.appWidgets.add(widgetInfo);
                    break;
                }
            }
        }
    } finally {
        c.close();
    }

    // 3. 清理无效数据
    for (long id : itemsToRemove) {
        mContext.getContentResolver().delete(uri, "_id=?",
                new String[]{String.valueOf(id)});
    }
}

4.3 bindWorkspace() ------ 绑定到 UI

java 复制代码
// LoaderResults.java / BaseLoaderResults.java

public void bindWorkspace(boolean incrementBindId) {
    // 按优先级分组:先显示当前页,再显示其他页
    final int currentScreen = mCallbacks.get().getPageToBindSynchronously();

    // 分离当前页和其他页的数据
    ArrayList<ItemInfo> currentScreenItems = new ArrayList<>();
    ArrayList<ItemInfo> otherScreenItems = new ArrayList<>();

    for (ItemInfo item : mBgDataModel.workspaceItems) {
        if (item.screenId == currentScreen) {
            currentScreenItems.add(item);
        } else {
            otherScreenItems.add(item);
        }
    }

    // ★ 切换到主线程执行绑定
    MAIN_EXECUTOR.execute(() -> {
        Callbacks callbacks = mCallbacks.get();
        if (callbacks == null) return;

        // Step 1: 创建空白桌面页
        callbacks.clearPendingBinds();
        callbacks.bindScreens(mBgDataModel.collectWorkspaceScreens());

        // Step 2: 先绑定当前页(让用户最快看到桌面)★★★
        callbacks.bindItems(currentScreenItems, false);

        // Step 3: 再绑定其他页
        callbacks.bindItems(otherScreenItems, false);

        // Step 4: 绑定 Widget
        callbacks.bindAppWidgets(mBgDataModel.appWidgets);

        // Step 5: 完成
        callbacks.finishBindingItems(currentScreen);
    });
}

4.4 Launcher 接收绑定回调

java 复制代码
// Launcher.java 实现 Callbacks 接口

@Override
public void bindScreens(IntArray orderedScreenIds) {
    // 为每个页面创建 CellLayout
    for (int i = 0; i < orderedScreenIds.size(); i++) {
        int screenId = orderedScreenIds.get(i);
        CellLayout cellLayout = mWorkspace.insertNewWorkspaceScreen(screenId);
    }
}

@Override
public void bindItems(List<ItemInfo> items, boolean forceAnimateIcons) {
    for (ItemInfo item : items) {
        View view;

        switch (item.itemType) {
            case Favorites.ITEM_TYPE_APPLICATION:
            case Favorites.ITEM_TYPE_SHORTCUT: {
                // ★ 创建图标 View
                WorkspaceItemInfo si = (WorkspaceItemInfo) item;
                view = createShortcut(si);
                break;
            }
            case Favorites.ITEM_TYPE_FOLDER: {
                // ★ 创建文件夹 View
                FolderInfo fi = (FolderInfo) item;
                view = FolderIcon.inflateIcon(R.layout.folder_icon,
                        this, mWorkspace.getPageAt(item.screenId), fi);
                break;
            }
            default:
                continue;
        }

        // ★ 添加到桌面对应位置
        mWorkspace.addInScreenFromBind(view, item);
    }
}

// 创建图标 View
public View createShortcut(WorkspaceItemInfo info) {
    BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(this)
            .inflate(R.layout.app_icon, mWorkspace, false);

    // 设置图标和文字
    favorite.applyFromWorkspaceItem(info);
    // → setIcon(info.bitmap)
    // → setText(info.title)

    favorite.setOnClickListener(ItemClickHandler.INSTANCE);
    favorite.setOnLongClickListener(ItemLongClickListener.INSTANCE);
    favorite.setOnFocusChangeListener(mFocusHandler);

    return favorite;
}

// 添加到 CellLayout 的指定格子
// Workspace.java
public void addInScreenFromBind(View child, ItemInfo info) {
    int screenId = info.screenId;
    int cellX = info.cellX;
    int cellY = info.cellY;
    int spanX = info.spanX;
    int spanY = info.spanY;

    CellLayout layout = getScreenWithId(screenId);
    layout.addViewToCellLayout(child, -1, info.id,
            new CellLayout.LayoutParams(cellX, cellY, spanX, spanY),
            true /* markCells */);
}

@Override
public void bindAppWidgets(List<LauncherAppWidgetInfo> widgets) {
    for (LauncherAppWidgetInfo item : widgets) {
        // 创建 Widget 视图
        AppWidgetHostView hostView = mAppWidgetHost.createView(
                this, item.appWidgetId, item.providerName);

        // 添加到桌面
        mWorkspace.addInScreenFromBind(hostView, item);
    }
}

@Override
public void finishBindingItems(int currentScreen) {
    // 桌面绑定完成!
    mWorkspace.restoreInstanceStateForRemainingPages();

    // 显示桌面(如果之前有 loading 界面)
    setWorkspaceLoading(false);

    // ★ 桌面已可见,用户看到桌面图标
}

4.5 loadAllApps() ------ 加载全部应用

java 复制代码
// LoaderTask.java

private void loadAllApps() {
    final List<UserHandle> profiles = mUserCache.getUserProfiles();

    for (UserHandle user : profiles) {
        // ★ 通过系统服务获取所有可启动的应用
        final List<LauncherActivityInfo> apps =
                mLauncherApps.getActivityList(null, user);

        // 内部等价于:
        // Intent intent = new Intent(ACTION_MAIN).addCategory(CATEGORY_LAUNCHER);
        // PackageManager.queryIntentActivities(intent)

        boolean quietMode = mUserManagerState.isUserQuiet(user);

        synchronized (mBgAllAppsList) {
            mBgAllAppsList.clear();

            for (LauncherActivityInfo info : apps) {
                // 构建 AppInfo
                AppInfo appInfo = new AppInfo(info, user, quietMode);

                // 加载图标和标题
                mIconCache.getTitleAndIcon(appInfo, info, false);

                // 添加到列表
                mBgAllAppsList.add(appInfo, info);
            }
        }
    }
}

// 绑定到 UI
// LoaderResults.java
public void bindAllApps() {
    final ArrayList<AppInfo> list = new ArrayList<>(mBgAllAppsList.data);

    // 排序
    Collections.sort(list, mAppNameComparator);

    MAIN_EXECUTOR.execute(() -> {
        Callbacks callbacks = mCallbacks.get();
        if (callbacks != null) {
            callbacks.bindAllApplications(list);
        }
    });
}

// Launcher.java
@Override
public void bindAllApplications(ArrayList<AppInfo> apps) {
    // 传递给 AllApps 视图
    mAppsView.getAppsStore().setApps(apps);
    // → AlphabeticalAppsList 排序
    // → AllAppsRecyclerView.Adapter 更新
    // → AllApps 页面就绪(上滑可展开)
}

五、首次启动:默认布局加载

xml 复制代码
首次开机时 launcher.db 为空,需要加载默认布局:

LauncherProvider.loadDefaultFavoritesIfNecessary()
    │
    ├── 检查是否首次启动(DB 中无数据)
    │
    ├── 尝试从 partner 配置加载
    │   └── 读取 /data/system/partner_default_workspace.xml
    │
    ├── 若无 partner 配置,使用内置默认
    │   └── 解析 res/xml/default_workspace.xml
    │
    └── DefaultLayoutParser.loadLayout()
          ├── 解析 <favorite> 标签 → 插入应用图标
          ├── 解析 <folder> 标签 → 插入文件夹
          ├── 解析 <appwidget> 标签 → 插入 Widget
          └── 解析 <resolve> 标签 → 解析 Intent 匹配
java 复制代码
// LauncherProvider.java

public void loadDefaultFavoritesIfNecessary() {
    SharedPreferences sp = getSharedPreferences();

    if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
        // 首次启动,数据库是空的

        // 创建布局解析器
        AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction();

        if (loader == null) {
            // 使用默认布局
            loader = new DefaultLayoutParser(
                    getContext(),
                    mOpenHelper,
                    R.xml.default_workspace);
        }

        // ★ 解析 XML 并插入数据库
        int count = loader.loadLayout(mOpenHelper.getWritableDatabase());

        sp.edit().remove(EMPTY_DATABASE_CREATED).apply();
    }
}

六、完整时序图

scss 复制代码
时间 →

SystemServer    AMS          Zygote      Launcher 进程        后台线程         主线程 UI
    │            │             │              │                   │               │
    │─ sysReady→ │             │              │                   │               │
    │            │             │              │                   │               │
    │            │─ startHome→ │              │                   │               │
    │            │  (HOME      │              │                   │               │
    │            │   Intent)   │              │                   │               │
    │            │             │              │                   │               │
    │            │─────────── fork ──────────→│                   │               │
    │            │             │              │                   │               │
    │            │             │    ActivityThread.main()         │               │
    │            │             │              │                   │               │
    │            │← attach ───────────────── │                   │               │
    │            │             │              │                   │               │
    │            │── schedule ────────────── →│                   │               │
    │            │  LaunchActivity            │                   │               │
    │            │             │              │                   │               │
    │            │             │              │─ onCreate() ─────────────────────→│
    │            │             │              │   LauncherAppState init           │
    │            │             │              │   DeviceProfile 计算               │
    │            │             │              │   setContentView                  │
    │            │             │              │                   │               │
    │            │             │              │── startLoader() ─→│               │
    │            │             │              │                   │               │
    │            │             │              │                   │─loadWorkspace │
    │            │             │              │                   │  读取 DB       │
    │            │             │              │                   │               │
    │            │             │              │                   │─bindWorkspace→│
    │            │             │              │                   │               │─ 创建CellLayout
    │            │             │              │                   │               │─ 添加图标View
    │            │             │              │                   │               │─ 添加Widget
    │            │             │              │                   │               │
    │            │             │              │     ★ 桌面显示 ✅  │               │
    │            │             │              │                   │               │
    │            │             │              │                   │─loadAllApps   │
    │            │             │              │                   │  读取 PMS      │
    │            │             │              │                   │  加载图标       │
    │            │             │              │                   │               │
    │            │             │              │                   │─bindAllApps─→ │
    │            │             │              │                   │               │─ 更新AllApps
    │            │             │              │                   │               │
    │            │             │              │                   │─loadWidgets   │
    │            │             │              │                   │─bindWidgets─→ │
    │            │             │              │                   │               │
    │            │             │              │     ★ 全部完成 ✅  │               │

七、关键设计思想总结

复制代码
┌────────────────────────────────────────────────────────────────┐
│                     设计思想                                     │
├────────────────────┬───────────────────────────────────────────┤
│ 异步加载            │ 所有数据操作在 MODEL_EXECUTOR 后台线程      │
│                    │ 通过 Callbacks 回调到主线程更新 UI           │
├────────────────────┼───────────────────────────────────────────┤
│ 分阶段绑定          │ Workspace → AllApps → Shortcuts → Widgets │
│                    │ 优先显示桌面,其他后续加载                    │
├────────────────────┼───────────────────────────────────────────┤
│ 当前页优先          │ bindWorkspace 时先绑定当前页的图标           │
│                    │ 让用户最快看到内容                           │
├────────────────────┼───────────────────────────────────────────┤
│ 多级缓存            │ 图标: 内存 → SQLite → PackageManager       │
│                    │ 避免重复解析 APK                             │
├────────────────────┼───────────────────────────────────────────┤
│ 增量更新            │ 安装/卸载通过 LauncherApps.Callback 监听    │
│                    │ 不需要全量重载                               │
├────────────────────┼───────────────────────────────────────────┤
│ 数据持久化          │ 桌面布局存储在 SQLite (launcher.db)          │
│                    │ 重启后从 DB 恢复,不需要重新排列               │
└────────────────────┴───────────────────────────────────────────┘
相关推荐
锦木烁光2 小时前
多端项目太乱?我是这样用 Monorepo 重构的
前端·架构
jwn9992 小时前
Laravel11.x新特性全解析
android·开发语言·php·laravel
C'ᴇsᴛ.小琳 ℡2 小时前
App架构的演化
架构
程序员小郭832 小时前
MySQL分库分表策略全解析(实战版)
数据库·mysql·架构
2301_771717212 小时前
微服务架构:多模块之间通信OpenFeign
微服务·架构·asp.net
我就是马云飞2 小时前
停更5年后,我为什么重新开始写技术内容了
android·前端·程序员
独特的螺狮粉3 小时前
开源鸿蒙跨平台Flutter开发:微波射频阻抗匹配系统-极坐标史密斯圆图与天线信号渲染架构
开发语言·flutter·华为·架构·开源·harmonyos
stevenzqzq3 小时前
Kotlin 协程:withContext 与 async 核心区别与使用场景
android·开发语言·kotlin
唔663 小时前
原生 Android(Kotlin)仅串口「侵入式架构」完整案例三
android·架构·kotlin