Launcher3 桌面加载流程分析
主入口Launcher
首先来看Launcher.java的onCreate方法,里面代码很多,只看主流程部分:
@Override
protected void onCreate(Bundle savedInstanceState) {
......
LauncherAppState app = LauncherAppState.getInstance(this);
......
}
这里的LauncherAppState类是用来保存一些全局的、核心的对象。主要有整个Launcher的工作台workspace、Launcher的控制器LauncherModel、Launcher的应用图标缓存机制 IconCache,以及设备的配置信息InvarianDeviceProfile等等。比如说核心对象IconCache、LauncherModel就是在这里面进行初始化的。
接着看onCreate中的代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
......
LauncherAppState app = LauncherAppState.getInstance(this);
mOldConfig = new Configuration(getResources().getConfiguration());
mModel = app.getModel();
mRotationHelper = new RotationHelper(this);
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
......
}
通过Configuration获取屏幕的配置,通过LauncherAppState获取到LauncherModel的实例对象以及InvariantDeviceProfile的实例对象(在这个类中会去获取设备的配置信息、硬件参数等等)。
在InvariantDeviceProfile的getPredefinedDeviceProfiles中会去device_profiles 配置文件中去适配对应的配置信息:
private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context, String gridName, boolean isSplitDisplay) {
ArrayList<DisplayOption> profiles = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
final int depth = parser.getDepth();
int type;
......
}
device_profiles.xml就不贴出来了,当新的机型没有适配,就可以在这里进行修改或新增配置。
…算了,还是贴一部分出来吧,自己也方便看:
<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
<grid-option
launcher:name="3_by_3"
launcher:numRows="3"
launcher:numColumns="3"
launcher:numFolderRows="2"
launcher:numFolderColumns="3"
launcher:numHotseatIcons="3"
launcher:dbFile="launcher_3_by_3.db"
launcher:defaultLayoutId="@xml/default_workspace_3x3" >
<display-option
launcher:name="Super Short Stubby"
launcher:minWidthDps="255"
launcher:minHeightDps="300"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
<display-option
launcher:name="Shorter Stubby"
launcher:minWidthDps="255"
launcher:minHeightDps="400"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:canBeDefault="true" />
</grid-option>
........
? 到这里Launcher的配置初始化到这里就基本加载完成了,还有一些其他的对象的初始化,包括workspace状态变化的动画加载控制LauncherStateTransitionAnimation, 应用组件的管理器AppWidgetManagerCompat, 处理组件长按事件的ViewLauncherAppWidgetHost之类的就没有去管。
LauncherModel加载应用信息
LauncherModel中有一个很重要的方法:startLoader
public boolean startLoader() {
ItemInstallQueue.INSTANCE.get(mApp.getContext())
.pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
synchronized (mLock) {
final Callbacks[] callbacksList = getCallbacks();
if (callbacksList.length > 0) {
for (Callbacks cb : callbacksList) {
MAIN_EXECUTOR.execute(cb::clearPendingBinds);
}
stopLoader();
LoaderResults loaderResults = new LoaderResults(mApp, mBgDataModel, mBgAllAppsList, callbacksList);
if (mModelLoaded && !mIsLoaderTaskRunning) {
loaderResults.bindWorkspace();
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);
MODEL_EXECUTOR.post(mLoaderTask);
}
}
}
return false;
}
可以看出该方法中创建了LoaderTask用来加载数据,该类实现了Runnable 接口,我们直接看它的run 方法:
public void run() {
synchronized (this) {
if (mStopped) {
return;
}
}
......
......
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
......
......
mResults.bindWorkspace();
......
waitForIdle();
logASplit(logger, "step 1 complete");
verifyNotStopped();
......
......
verifyNotStopped();
mResults.bindAllApps();
......
}
首先根据mStopped的状态进行一个判断,然后最先执行的是loadWorkspace 方法,里面是对Workspace 的一些加载,包括屏幕数、应用数据、widget组建信息等等,代码很多就不贴出来了。
接下来会调用mResults.bindWorkspace 进行一个绑定,然后就会调用waitForIdle 方法进行一个休息,等待其它子线程执行完,代码中在多个地方都进行了等待,有兴趣的可以自己去看看,这里并没有都贴出来。
再往下就是verifyNotStopped 方法,在该方法中对mStopped 又进行了一次判断,如果为true 就抛异常,否则什么也不做。
最后就是调用mResults.bindAllApps 方法绑定所有app,mResults.bindWidgets 绑定小部件等等的一些列绑定。
小结:Launcher里面数据比较多,包括所有应用的图标和应用数据,所有应用的Widget数据,桌面已添加的用户数据等,随着Android大版本演进,还有DeepShortcuts等新的数据类型。如果按照常规的加载做法,等加载数据完成后再显示到View,耗时就太长了。为了优化体验,Launcher于是采用了分批加载、分批绑定的做法。
整体加载绑定流程如下:
最后会调用Launcher里实现的回调方法bindAllApplications,将数据填充到View容器里:
@Override
public void bindAllApplications(AppInfo[] apps, int flags) {
mAppsView.getAppsStore().setApps(apps, flags);
PopupContainerWithArrow.dismissInvalidPopup(this);
}
就拿Workspace的加载为例:
另外AllApps和Widget的加载流程都差不太多。
基本上整体的流程就是这样…
|