Android 14 Launcher 数据加载分析(二)
2024-02-16 / 龙之叶   

前言

前面一篇 launcher 基础认识中,有分析到 Launcher.java 这个类,在该类的 onCreate() 方法中,有 launcher 布局相关的初始化。这里还是从 onCreate() 方法出发,进行深入。
说到数据加载,就不得不提到 launcher#onCreate() 方法,上篇说到该方法里面有布局相关的初始化,也是在 launcher#onCreate() 方法开始的。
先看 launcher#onCreate() 的部分代码:
packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected void onCreate(Bundle savedInstanceState) {
// 省略部分代码......
// 初始化View,进行各种View的初始化事件绑定
setupViews();

// 省略部分代码......
// 加载、绑定数据(这里与之前版本的 startLoader() 作用一样)
// 重点关注 addCallbacksAndLoad()
if (!mModel.addCallbacksAndLoad(this)) {
if (!internalStateHandled) {
Log.d(BAD_STATE, "Launcher onCreate not binding sync, prevent drawing");
// If we are not binding synchronously, pause drawing until initial bind complete,
// so that the system could continue to show the device loading prompt
mOnInitialBindListener = Boolean.FALSE::booleanValue;
}
}
// 省略部分代码......
}

上述代码调用了 LauncherModel 中的 addCallbacksAndLoad() 方法,一起看 LauncherModel#addCallbacksAndLoad()
packages/apps/Launcher3/src/com/android/launcher3/LauncherModel.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public boolean addCallbacksAndLoad(Callbacks callbacks) {
synchronized (mLock) {
addCallbacks(callbacks);
// 重点关注
return startLoader(new Callbacks[] { callbacks });
}
}
// 该方法是加载程序的启动,在启动过程中并对于数据进行尝试同步绑定,
// 若能够进行绑定则可以返回true,在实现的过程中调用的是工作线程LoaderTask
private boolean startLoader(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) {
// 对于旧的运行项进行一个清除
boolean wasRunning = stopLoader();
boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
if (callbacksList.length > 0) {
// 在同步加载过程中对于所有挂起的绑定可运行项进行清除
for (Callbacks cb : callbacksList) {
MAIN_EXECUTOR.execute(cb::clearPendingBinds);
}
LoaderResults loaderResults = new LoaderResults(
mApp, mBgDataModel, mBgAllAppsList, callbacksList);
if (bindDirectly) {
// 此处为数据恢复机制,例如:子用户切换到住用户时,会走到这一分支
// 将同步项绑定于workspace、allapps、快捷方式、桌面小组件
loaderResults.bindWorkspace(bindAllCallbacks);
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
// 数据加载,首次启动时 mModelLoaded 为 false,会走到这一分支
stopLoader(); // 当有加载任务正在运行时则进行其停止运行
// 对于LocalderTask进行启动:
mLoaderTask = new LoaderTask(
mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);
// 发布加载程序任务
MODEL_EXECUTOR.post(mLoaderTask);
}
}
}
return false;
}

上述代码,我们重点关注 LoaderTask.java ,分析数据如何加载,先看 LoaderTask 的构造方法 LoaderTask.java

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
ModelDelegate modelDelegate, LoaderResults results) {
mApp = app;
mBgAllAppsList = bgAllAppsList;
mBgDataModel = dataModel;
mModelDelegate = modelDelegate;
mResults = results;
// LauncherApps:用于检索当前用户和任何关联的托管配置文件的可启动活动列表的类。 这主要是供发射器使用的。 应用程序可以查询每个用户配置文件。
// 由于PackageManager不会为其他配置文件传送包广播,因此您可以在此注册包更改。要监视正在添加或删除的托管配置文件,请注册以下广播: ACTION_MANAGED_PROFILE_ADDED、ACTION_MANAGED_PROFILE_REMOVED;
// 从 Android O 开始,托管配置文件中的应用程序不再允许访问主配置文件中的应用程序。应用程序只能访问 {@link getProfiles()} 返回的配置文件。
mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
// 与多用户相关,在多用户系统上管理用户和用户详细信息。
mUserManager = mApp.getContext().getSystemService(UserManager.class);
mUserCache = UserCache.INSTANCE.get(mApp.getContext());
// 跟踪应用安装会话的实用程序类
mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext());
// 获取 图标管理工具 对象
mIconCache = mApp.getIconCache();
}

接着看 LoaderTask#run()
packages/apps/Launcher3/src/com/android/launcher3/model/LoaderTask.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
public void run() {
synchronized (this) {
// 如果已经停止,则快速跳过
if (mStopped) {
return;
}
}
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
TimingLogger logger = new TimingLogger(TAG, "run");
LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
// 这里与 LauncherModel.LoaderTransaction 关联上
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
Trace.beginSection("LoadWorkspace");
try {
// 第一步:加载工作区。****重点关注****
loadWorkspace(allShortcuts, memoryLogger);
} finally {
Trace.endSection();
}
logASplit(logger, "loadWorkspace");
// 根据从数据库加载的工作空间,清理数据重新同步小部件快捷方式。
// 如果桌面(workspace)是从 不变设备配置文件中定义的主数据库不同的数据库加载的,则不应调用 sanitizeData。
// 例如,网格预览和最小设备模式都使用不同的数据库
// mDbName 在 loadWorkspace() 方法里赋值
if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
verifyNotStopped();
sanitizeData();
logASplit(logger, "sanitizeData");
}
verifyNotStopped();
// 绑定 workspace
mResults.bindWorkspace(true /* incrementBindId */);
logASplit(logger, "bindWorkspace");
mModelDelegate.workspaceLoadComplete();
// 发送首屏广播。
sendFirstScreenActiveInstallsBroadcast();
logASplit(logger, "sendFirstScreenActiveInstallsBroadcast");
// 等待桌面(workspace)加载完成再加载抽屉(AllApps)
waitForIdle();
logASplit(logger, "step 1 complete");
verifyNotStopped();
// 第二步:
Trace.beginSection("LoadAllApps");
List<LauncherActivityInfo> allActivityList;
try {
// 加载所有应用App
allActivityList = loadAllApps();
} finally {
Trace.endSection();
}
logASplit(logger, "loadAllApps");
verifyNotStopped();
// 绑定 AllApps
mResults.bindAllApps();
logASplit(logger, "bindAllApps");
verifyNotStopped();
// 关于 IconCacheUpdateHandler 文章后面有补充。
IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
setIgnorePackages(updateHandler);
updateHandler.updateIcons(allActivityList,
LauncherActivityCachingLogic.newInstance(mApp.getContext()),
mApp.getModel()::onPackageIconsUpdated);
logASplit(logger, "update icon cache");
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
verifyNotStopped();
logASplit(logger, "save shortcuts in icon cache");
// 将对应图标进行缓存
updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
mApp.getModel()::onPackageIconsUpdated);
}
// 等待上一操作加载完成,在进行加载应用程序快捷方式
waitForIdle();
logASplit(logger, "step 2 complete");
verifyNotStopped();
// 第三步:加载应用程序快捷方式
List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
logASplit(logger, "loadDeepShortcuts");
verifyNotStopped();
// 数据同步绑定应用程序快捷方式
mResults.bindDeepShortcuts();
logASplit(logger, "bindDeepShortcuts");
if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
verifyNotStopped();
logASplit(logger, "save deep shortcuts in icon cache");
// 缓存对应图标
updateHandler.updateIcons(allDeepShortcuts,
new ShortcutCachingLogic(), (pkgs, user) -> { });
}
// 等待上一操作加载完成,在进行加载小部件等
waitForIdle();
logASplit(logger, "step 3 complete");
verifyNotStopped();
// 第四步:加载桌面小部件
List<ComponentWithLabelAndIcon> allWidgetsList =
mBgDataModel.widgetsModel.update(mApp, null);
logASplit(logger, "load widgets");
verifyNotStopped();
// 绑定 Widgets
mResults.bindWidgets();
logASplit(logger, "bindWidgets");
verifyNotStopped();
// 将小部件进行缓存
updateHandler.updateIcons(allWidgetsList,
new ComponentWithIconCachingLogic(mApp.getContext(), true),
mApp.getModel()::onWidgetLabelsUpdated);
logASplit(logger, "save widgets in icon cache");
// 第五步
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
// 加载文件夹名称
loadFolderNames();
}
verifyNotStopped();
updateHandler.finish();
logASplit(logger, "finish icon update");
mModelDelegate.modelLoadComplete();
// 提交完成。
transaction.commit();
memoryLogger.clearLogs();
} catch (CancellationException e) {
// Loader stopped, ignore
logASplit(logger, "Cancelled");
} catch (Exception e) {
memoryLogger.printLogs();
throw e;
} finally {
logger.dumpToLog();
}
TraceHelper.INSTANCE.endSection(traceToken);
}

上述 LoaderTask#run() 代码可以清晰的看到每一步操作所要做的工作。 loadWorkspace()bindWorkspace() ,也就是加载 workspace 的应用并且进行绑定, waitForIdle() 方法主要是等待加载数据结束。 sendFirstScreenActiveInstallsBroadcast() 发送首屏广播。 loadAllApps()bindAllApps() 加载并绑定所有的 APP 信息, loadDeepShortcuts()bindDeepShortcuts() 加载并绑定所有的快捷方式,然后加载并绑定所有的小部件。至此launcher数据加载基本就完成了。
补充:上述代码出现比较多的 IconCacheUpdateHandler

IconCacheUpdateHandler:用于处理图标缓存更新的实用程序类。
IconCacheUpdateHandler#updateIcons(): 更新持久数据库,以便只有与应用程序相对应的条目保留在数据库中并进行更新。

本文链接:
http://longzhiye.top/2024/02/16/2024-02-16/