Launcher加载APP图标流程
前段时间看了桌面Launcher的启动流程《安卓桌面Launcher的启动》,但是还没完,桌面也是一个安卓应用且涉及到的面很广,光是桌面这个模块就需要一个团队维护开发。
AOSP Launcher源码:Launcher3
大牛维护的Launcher代码:fookwood/Launcher3
1、Launcher.onCreate()
接着Launcher的启动,通过Intent启动桌面之后,逻辑和正常的应用一样。执行主界面Activity的onCreate()方法,把这一百多行的代码分解一点一点看:
①Debug启用严格模式,系统应用调试非常苛刻。
protected void onCreate(Bundle savedInstanceState) {
RaceConditionTracker.onEvent(ON_CREATE_EVT, ENTER);
if (DEBUG_STRICT_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
...②接着调用super.onCreate(),Trance开始,并且中间记录了一次
TraceHelper.beginSection("Launcher-onCreate");
super.onCreate(savedInstanceState);
TraceHelper.partitionSection("Launcher-onCreate", "super call");③接着获取LauncherAppState实例,并将当前Launcher设置给LauncherAppState
LauncherAppState app = LauncherAppState.getInstance(this);
mOldConfig = new Configuration(getResources().getConfiguration());
mModel = app.setLauncher(this);
...④初始化一些成员变量,加载布局、setupViews()
...
mAppWidgetHost = new LauncherAppWidgetHost(this);
mAppWidgetHost.startListening();
mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
setupViews();
...调用了setupViews()就初始化了AllAppsContainerView布局(后面用到)
/**
* Finds all the views we need and configure them properly.
*/
private void setupViews() {
...
mAllAppsController.setupViews(mAppsView);
}⑤调用LauncherModel的startLoader()方法,这里的方法经过重构与之前源码略有不同。
...
if (!mModel.startLoader(currentScreen)) {
if (!internalStateHandled) {
// If we are not binding synchronously, show a fade in animation when
// the first page bind completes.
mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
}
} else {
// Pages bound synchronously.
mWorkspace.setCurrentPage(currentScreen);
setWorkspaceLoading(true);
}
...⑥到这里才给桌面界面setContentView(),并且注册监听手机息屏广播:Intent.ACTION_SCREEN_OFF。
....
setContentView(mLauncherView);
getRootView().dispatchInsets();
// Listen for broadcasts
registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
...大体流程分解完成,现在来看每一个详细执行的到底有哪些?
2、详细分析
在第③步中,获取LauncherAppState实例,并调用setLauncher(Launcher launcher)方法
LauncherModel setLauncher(Launcher launcher) {
getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher);
return mModel;
}mModel是LauncherModel类型的成员变量,调用initialize()方法将传入的launcher设置为当前加载程序的Launcher活动对象,封装为一个弱引用。
/**
* Set this as the current Launcher activity object for the loader.
*/
public void initialize(Callbacks callbacks) {
synchronized (mLock) {
Preconditions.assertUIThread();
mCallbacks = new WeakReference<>(callbacks);
}
}在第⑤步中,调用了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(int synchronousBindPage) {
// 在启动加载程序之前启用队列。 它将在Launcher#finishBindingItems中禁用
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
synchronized (mLock) {
// 如果我们知道它不会做任何事情,请不要费心启动线程
if (mCallbacks != null && mCallbacks.get() != null) {
final Callbacks oldCallbacks = mCallbacks.get();
// Clear any pending bind-runnables from the synchronized load process.
mUiExecutor.execute(oldCallbacks::clearPendingBinds);
// 如果已经在运行,请告诉它停止。
stopLoader();
LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
mBgAllAppsList, synchronousBindPage, mCallbacks);
if (mModelLoaded && !mIsLoaderTaskRunning) {
// 将已加载项的集合划分为我们要同步绑定的项,以及要正常(异步)绑定的所有其他项。
loaderResults.bindWorkspace();
// 现在,继续发布AllApp的绑定,因为这还会引起其他问题。
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
startLoaderForResults(loaderResults);
}
}
}
return false;
}其中一步是调用stopLoader()方法,如果已经有加载程序任务在运行,就停止。
public void stopLoader() {
synchronized (mLock) {
LoaderTask oldTask = mLoaderTask;
mLoaderTask = null;
if (oldTask != null) {
oldTask.stopLocked();
}
}
}这个这几很巧妙,和前面的startLoader()方法用了同一个对象mLock加锁互斥:
@Thunk final Object mLock = new Object();继续startLoader()方法,API 30 的系统源码明显经过很大的重构,现在将加载图标、桌面Widget、快捷方式的任务都封装在LoaderResults对象中。
这里我们只看其中一个,就是APP图标的加载,也就是LoaderResults的bindAllApps()方法
public void bindAllApps() {
// shallow copy
@SuppressWarnings("unchecked")
ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
executeCallbacksTask(c -> c.bindAllApplications(list), mUiExecutor);
}又调用了LoaderResults中的executeCallbacksTask(CallbackTask ...)方法。
protected void executeCallbacksTask(CallbackTask task, Executor executor) {
executor.execute(() -> {
if (mMyBindingId != mBgDataModel.lastBindId) {
Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
return;
}
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
task.execute(callbacks);
}
});
}方法中的参数类型CallbackTask类型似曾相识,往前面回忆,没错!就是定义在LauncherModel中的接口
/**
* A task to be executed on the current callbacks on the UI thread.
* If there is no current callbacks, the task is ignored.
*/
public interface CallbackTask {
void execute(Callbacks callbacks);
}它在binderAllApps()方法中是通过Lambda表达式初始化的,传入的参数正是Callbacks,同样是定义在LauncherModel中的接口:
public interface Callbacks {
public void rebindModel();
public int getCurrentWorkspaceScreen();
public void clearPendingBinds();
public void startBinding();
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
public void bindScreens(IntArray orderedScreenIds);
public void finishFirstPageBind(ViewOnDrawExecutor executor);
public void finishBindingItems(int pageBoundFirst);
public void bindAllApplications(ArrayList<AppInfo> apps);
public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
...
}那么LoaderResults中的Callbacks是怎么来的呢?还记的最开始调用startLoader()方法,其中一行就是用当前的mCallbacks作为参数传入:
...
LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
mBgAllAppsList, synchronousBindPage, mCallbacks);
...注意这个就是Launcher作为Callbacks的弱引用,原来饶了一圈最后又回到了Launcher,Launcher实现了Callbacks中的所有接口方法,其中bindAllApplications(ArrayList<AppInfo> apps):
/**
* Add the icons for all apps.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void bindAllApplications(ArrayList<AppInfo> apps) {
mAppsView.getAppsStore().setApps(apps);
}mAppsView是AllAppsContainerView类型,调用getAppsStore()方法
public AllAppsStore getAppsStore() {
return mAllAppsStore;
}返回AllAppsStore对象,调用setApps()方法
/**
* Sets the current set of apps.
*/
public void setApps(List<AppInfo> apps) {
mComponentToAppMap.clear();
addOrUpdateApps(apps);
}其实就是一个AllAppsContainerView的Adapter,更新数据通知View去刷新界面:
/**
* Adds or updates existing apps in the list
*/
public void addOrUpdateApps(List<AppInfo> apps) {
for (AppInfo app : apps) {
mComponentToAppMap.put(app.toComponentKey(), app);
}
notifyUpdate();
}提一下,回到最开始的onCreate()方法中调用了setupViews()就初始化了AllAppsContainerView等布局:
/**
* Finds all the views we need and configure them properly.
*/
private void setupViews() {
...
mAllAppsController.setupViews(mAppsView);
}至此,随着桌面的onCreate(),图标加载完成。有点复杂,但是逻辑比较清晰。

