首先来看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中有一个很重要的方法:startLoader
/**
* Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
* @return true if the page could be bound synchronously.
*/
public boolean startLoader() {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
ItemInstallQueue.INSTANCE.get(mApp.getContext())
.pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
synchronized (mLock) {
// Don't bother to start the thread if we know it's not going to do anything
final Callbacks[] callbacksList = getCallbacks();
if (callbacksList.length > 0) {
// Clear any pending bind-runnables from the synchronized load process.
for (Callbacks cb : callbacksList) {
MAIN_EXECUTOR.execute(cb::clearPendingBinds);
}
// If there is already one running, tell it to stop.
stopLoader();
LoaderResults loaderResults = new LoaderResults(mApp, mBgDataModel, mBgAllAppsList, callbacksList);
if (mModelLoaded && !mIsLoaderTaskRunning) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
loaderResults.bindWorkspace();
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);
// Always post the loader task, instead of running directly
// (even on same thread) so that we exit any nested synchronized blocks
MODEL_EXECUTOR.post(mLoaderTask);
}
}
}
return false;
}
可以看出该方法中创建了LoaderTask用来加载数据,该类实现了Runnable
接口,我们直接看它的run
方法:
public void run() {
synchronized (this) {
// Skip fast if we are already stopped.
//中文:如果我们已经停止快速跳过
if (mStopped) {
return;
}
}
......
......
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
......
......
mResults.bindWorkspace();
......
// Take a break
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容器里:
/**
* Add the icons for all apps.
* 为应用程序添加图标
* Implementation of the method from LauncherModel.Callbacks.
*/
@Override
public void bindAllApplications(AppInfo[] apps, int flags) {
mAppsView.getAppsStore().setApps(apps, flags);
PopupContainerWithArrow.dismissInvalidPopup(this);
}
就拿Workspace的加载为例:
另外AllApps和Widget的加载流程都差不太多。
基本上整体的流程就是这样…