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 恢复,不需要重新排列 │
└────────────────────┴───────────────────────────────────────────┘