Android 14.0 | 文章 |
---|---|
Launcher 背后的魔法 | 📑 Gradle 构建与编译全攻略(超详细) |
Launcher 背后的魔法 | 📑 揭开 LoaderTask 的数据加载奥秘 |
# 核心代码类
核心类 | 路径 |
---|---|
Launcher.java | package/app/Launcher3/src/com/android/launcher3/Launcher.java |
LoaderTask | package/app/Launcher3/src/com/android/launcher3/model/LoaderTask.java |
LauncherSettings.java | package/app/Launcher3/src/com/android/launcher3/LauncherSettings.java |
LauncherProvider.java | package/app/Launcher3/src/com/android/launcher3/LauncherProvider.java |
ModelDbController.java | package/app/Launcher3/src/com/android/launcher3/model/ModelDbController.java |
InvariantDeviceProfile.java | package/app/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java |
LauncherAppState.java | package/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
#
# 会调用到 LauncherProvider 的 loadDefaultFavoritesIfNecessary() 方法,
# 这个方法来判断是否需要加载默认的布局,如果清除了桌面数据
# 或者手机恢复了出厂设置,就会加载默认的布局。
######################################################################
Settings.call(contentResolver, Settings.METHOD_LOAD_DEFAULT_FAVORITES);
// 加载数据库(我们放在后面分析)
...
}
}
3.1.2 加载默认桌面布局
我们来看下 Settings.Call() 的流程:
// 加载默认桌面布局
Log.d(TAG, "loadWorkspace: loading default favorites");
######################################################################
# 核心关注点 1
#
# 会调用到 LauncherProvider 的 loadDefaultFavoritesIfNecessary() 方法,
# 这个方法来判断是否需要加载默认的布局,如果清除了桌面数据
# 或者手机恢复了出厂设置,就会加载默认的布局。
######################################################################
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: 加载默认桌面布局
#
# 会调用到 LauncherProvider 的 loadDefaultFavoritesIfNecessary() 方法,
# 这个方法来判断是否需要加载默认的布局,如果清除了桌面数据
# 或者手机恢复了出厂设置,就会加载默认的布局。
######################################################################
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);
}
}
}
}