前两篇文章中我们已经将系统启动的过程推进到了系统服务启动完毕之后,本篇文章就来介绍Android系统启动的最后一步:启动Launcher。
这个Launcher我们可以通俗地理解为桌面,系统启动的最后一步就是启动一个应用程序来显示系统中已经安装完成的应用程序,这个应用程序就叫做Launcher。
在 Android 系统启动的最后一步,是启动系统的默认桌面(Launcher)。桌面是用户与 Android 设备交互的主要界面,它提供了应用程序图标、小部件、壁纸等元素,使用户可以访问和启动应用程序。Launcher 在 Android 系统中是一个独立的应用程序,负责管理桌面上的图标、布局和交互逻辑。它通常是作为系统的默认桌面应用程序预装在设备上,并在系统启动时自动启动。启动 Launcher 的过程通常是由系统服务(System Service)负责调用。在系统启动的最后阶段,当其他系统组件已经准备就绪后,系统服务会启动 Launcher 进程,并加载 Launcher 应用程序的主要组件,例如主活动(MainActivity)。一旦 Launcher 进程启动并加载完成,它就会显示在屏幕上,并呈现用户熟悉的桌面界面。用户可以通过在桌面上滑动、点击图标等方式与 Launcher 进行交互,启动其他应用程序、访问设备设置等操作。
启动 Launcher 是 Android 系统启动过程中的最后一步,它标志着整个系统已经初始化完成,并提供了用户与设备交互的入口。
前两篇文章:
这个Launcher实际上是属于otherService的范畴,也就是其他服务,我们先点进startOtherService方法,其中有一段:
mActivityManagerService.systemReady(() -> {
Slog.i(TAG, "Making services ready");
t.traceBegin("StartActivityManagerReadyPhase");
mSystemServiceManager.startBootPhase(t, SystemService.PHASE_ACTIVITY_MANAGER_READY);
t.traceEnd();
t.traceBegin("StartObservingNativeCrashes");
try {
mActivityManagerService.startObservingNativeCrashes();
} catch (Throwable e) {
reportWtf("observing native crashes", e);
}
t.traceEnd();
t.traceBegin("RegisterAppOpsPolicy");
try {
mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy(mSystemContext));
} catch (Throwable e) {
reportWtf("registering app ops policy", e);
}
t.traceEnd();
会调用ActivityManagerService的systemReady方法,接着进入这个方法,其实还是继续跳转,我们看这个方法:
mActivityTaskManager.onSystemReady();
调用到了ActivityTaskManager的onSystemReady方法,继续跳转到RootWindowContainer的startHomeOnAllDisplay方法,让我们细看这个方法:
boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,
boolean fromHomeKey) {
// Fallback to top focused display or default display if the displayId is invalid.
if (displayId == INVALID_DISPLAY) {
final Task rootTask = getTopDisplayFocusedRootTask();
displayId = rootTask != null ? rootTask.getDisplayId() : DEFAULT_DISPLAY;
}
final DisplayContent display = getDisplayContent(displayId);
return display.reduceOnAllTaskDisplayAreas((taskDisplayArea, result) ->
result | startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,
allowInstrumenting, fromHomeKey),
false /* initValue */);
}
这段代码是 Android 框架中的一部分,用于在指定的显示区域启动主屏幕(Launcher)。让我们逐行解释代码的含义:
首先,代码检查 displayId 是否为无效值(INVALID_DISPLAY)。如果 displayId 无效,它会回退到当前焦点所在的顶级显示区域(Task)或默认显示区域(DEFAULT_DISPLAY)。
接下来,代码通过 getDisplayContent(displayId) 方法获取指定 displayId 的显示内容(DisplayContent)。这个方法用于获取与给定 displayId 相关联的显示区域。
然后,代码调用 reduceOnAllTaskDisplayAreas() 方法,在所有的任务显示区域上执行指定的操作。该方法会遍历所有的任务显示区域,并将每个任务显示区域上的结果进行归约。在每个任务显示区域上,代码调用 startHomeOnTaskDisplayArea() 方法来尝试在该区域上启动主屏幕。它会传递一些参数,如用户ID、原因、是否允许检测仪器、是否来自Home键等。
最后,reduceOnAllTaskDisplayAreas() 方法返回归约的结果,表示是否在任何一个任务显示区域上成功启动了主屏幕。
总体来说,这段代码的作用是在指定的显示区域上启动主屏幕。它会遍历所有的任务显示区域,并尝试在每个任务显示区域上启动主屏幕,返回是否成功启动了主屏幕的结果。
我们接着点开里面的startHomeOnTaskDisplayArea,我们挑选里面最重要的几段:
boolean startHomeOnTaskDisplayArea(int userId, String reason, TaskDisplayArea taskDisplayArea,
boolean allowInstrumenting, boolean fromHomeKey) {
......
Intent homeIntent = null;
ActivityInfo aInfo = null;
if (taskDisplayArea == getDefaultTaskDisplayArea()) {
homeIntent = mService.getHomeIntent();
aInfo = resolveHomeActivity(userId, homeIntent);
} else if (shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) {
Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, taskDisplayArea);
aInfo = info.first;
homeIntent = info.second;
}
.....
final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
aInfo.applicationInfo.uid) + ":" + taskDisplayArea.getDisplayId();
mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
taskDisplayArea);
return true;
}
这里获取了用于启动Launcher的Intent,然后进行一系列设置,最后通过startHomeActivity方法启动了这个Launcher:
void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason,
TaskDisplayArea taskDisplayArea) {
.......
mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
.setOutActivity(tmpOutRecord)
.setCallingUid(0)
.setActivityInfo(aInfo)
.setActivityOptions(options.toBundle())
.execute();
mLastHomeActivityStartRecord = tmpOutRecord[0];
if (rootHomeTask.mInResumeTopActivity) {
mSupervisor.scheduleResumeTopActivities();
}
}
这里主要看obtainStarter这一串代码,这一串代码用Intent和reason构建出来了一个ActivityStarter对象,很显然这个对象是用来启动Activity的,那么我们也可以知道这个Launcher实际上也是一个Activity,所以说,作为一个Activity,Launcher具有与其他Activity相似的生命周期和功能。当用户点击设备上的主屏幕按钮或通过其他手势启动Launcher时,系统会创建一个Launcher的实例,并调用其生命周期方法,如onCreate()、onStart()、onResume()等。Launcher可以包含自定义的布局和逻辑,以展示应用程序列表、搜索功能、小部件等。
到这里,Launcher的启动过程也完成了。
上面说到,这个Launcher本质上也是一个Activity,所以我们先从onCreate入手:
protected void onCreate(Bundle savedInstanceState) {
.......
super.onCreate(savedInstanceState);
LauncherAppState app = LauncherAppState.getInstance(this);//1-----1
mModel = app.getModel();//2----2
mRotationHelper = new RotationHelper(this);
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
initDeviceProfile(idp);
idp.addOnChangeListener(this);
mSharedPrefs = LauncherPrefs.getPrefs(this);
mIconCache = app.getIconCache();
mAccessibilityDelegate = createAccessibilityDelegate();
initDragController();
......
}
这里还是截取最重要的一段代码,主要让我们看注释一二处的代码,在注释一处获取了LauncherAppState的实例,然后在第二处代码处设置当前实例的Model为app的Model。接下来这个model会调用addCallbacksAndLoad方法:
if (!mModel.addCallbacksAndLoad(this)) {
if (!internalStateHandled) {
// 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;
}
}
这个方法相当于是将当前这个Launcher对象添加尽量LauncherModel的callBacks中:
public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
synchronized (mLock) {
addCallbacks(callbacks);
return startLoader(new Callbacks[] { callbacks });
}
}
/**
* Adds a callbacks to receive model updates
*/
public void addCallbacks(@NonNull final Callbacks callbacks) {
Preconditions.assertUIThread();
synchronized (mCallbacksList) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NULL_INT_SET, "addCallbacks pointer: "
+ callbacks
+ ", name: "
+ callbacks.getClass().getName(), new Exception());
}
mCallbacksList.add(callbacks);
}
}
添加完毕之后就会紧接着调用到startLoader方法:
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 there is already one running, tell it to stop.
boolean wasRunning = stopLoader();
boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
if (callbacksList.length > 0) {
// Clear any pending bind-runnables from the synchronized load process.
for (Callbacks cb : callbacksList) {
MAIN_EXECUTOR.execute(cb::clearPendingBinds);
}
LoaderResults loaderResults = new LoaderResults(
mApp, mBgDataModel, mBgAllAppsList, callbacksList);
if (bindDirectly) {
// 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(bindAllCallbacks);
// 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);//1---1
// 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);//2---2
}
}
}
return false;
}
这段代码是Launcher中的一个私有方法startLoader(),用于启动加载器(Loader)。在Launcher中,加载器负责加载和准备主屏幕的数据,如应用程序列表、小部件等,并将其绑定到相应的界面上。
在注释一处,创建类一个LoaderTask,这显然是一个runnable对象,紧接着在注释二处将其提交到线程池中进行。而这个MODEL_EXECUTOR实在Executors中定义的:
public static final LooperExecutor MODEL_EXECUTOR =
new LooperExecutor(createAndStartNewLooper("launcher-loader"));
相当于一个有消息循环的线程,可以处理消息,其内部也有handler用于处理消息。
接下来看它执行的LoaderTask,直接看它的run方法:
public void run() {
synchronized (this) {
// Skip fast if we are already stopped.
if (mStopped) {
return;
}
}
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
TimingLogger logger = new TimingLogger(TAG, "run");
LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
Trace.beginSection("LoadWorkspace");
try {
loadWorkspace(allShortcuts, memoryLogger); //1--加载工作区空间
} finally {
Trace.endSection();
}
logASplit(logger, "loadWorkspace");
if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
verifyNotStopped();
sanitizeData();
logASplit(logger, "sanitizeData");
}
verifyNotStopped();
mResults.bindWorkspace(true /* incrementBindId */);//2--绑定工作空间
logASplit(logger, "bindWorkspace");
mModelDelegate.workspaceLoadComplete();
// Notify the installer packages of packages with active installs on the first screen.
sendFirstScreenActiveInstallsBroadcast();
logASplit(logger, "sendFirstScreenActiveInstallsBroadcast");
// Take a break
waitForIdle();
logASplit(logger, "step 1 complete");
verifyNotStopped();
// second step
Trace.beginSection("LoadAllApps");
List<LauncherActivityInfo> allActivityList;
try {
allActivityList = loadAllApps();//3--加载所有APP应用程序的信息
} finally {
Trace.endSection();
}
.........
}
Launcher是以工作区的形式来显示系统安装的应用程序的快捷图标的,每一个工作区都是用来描述一个抽象桌面的,它由n个屏幕组成,每个屏幕又分成n个单元格,每个单元格用来显示一个用用程序的快捷图标。
我们这里仍然是截取了重要部分的代码,让我们从上往下看,在注释一处通过loadWorkspace方法加载了工作区信息,注释二处则是绑定了工作区信息,注释三处的loadAllApps方法则是加载了所有APP应用程序的信息。
接下来看这个loadAllApps方法:
private List<LauncherActivityInfo> loadAllApps() {
final List<UserHandle> profiles = mUserCache.getUserProfiles();
List<LauncherActivityInfo> allActivityList = new ArrayList<>();
// Clear the list of apps
mBgAllAppsList.clear();
List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
for (UserHandle user : profiles) {
final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
if (apps == null || apps.isEmpty()) {
return allActivityList;
}
boolean quietMode = mUserManagerState.isUserQuiet(user);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
AppInfo appInfo = new AppInfo(app, user, quietMode);
iconRequestInfos.add(new IconRequestInfo<>(
appInfo, app, /* useLowResIcon= */ false));
mBgAllAppsList.add(
appInfo, app, !FeatureFlags.ENABLE_BULK_ALL_APPS_ICON_LOADING.get());
}
allActivityList.addAll(apps);
}
........
return allActivityList;
}
看中间这一段遍历的过程,实际上就是将所有APP的信息都给遍历然后添加到响应的列表中,然后调用一个allActivityList.addAll(apps)完成注册,最后会将这个List给返回出去,以便在Launcher进行操作。
接下来会进行图标的绑定,这个会调用到Launcher类的bindAllApplications方法:
public void bindAllApplications(AppInfo[] apps, int flags) {
mAppsView.getAppsStore().setApps(apps, flags);
PopupContainerWithArrow.dismissInvalidPopup(this);
if (Utilities.ATLEAST_S) {
Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
DISPLAY_ALL_APPS_TRACE_COOKIE);
}
}
public void setApps(AppInfo[] apps, int flags) {
mApps = apps;
mModelFlags = flags;
notifyUpdate();
}
具体就是调用这个setApps方法来设置之前获得到的一系列App信息,接下来的notifyUpdate方法又会调用到BaseAllAppsContainerView的onFinishInflater方法:
protected void onFinishInflate() {
super.onFinishInflate();
mAH.get(AdapterHolder.SEARCH).setup(mSearchRecyclerView,
/* Filter out A-Z apps */ itemInfo -> false);
rebindAdapters(true /* force */);
float cornerRadius = Themes.getDialogCornerRadius(getContext());
mBottomSheetCornerRadii = new float[]{
cornerRadius,
cornerRadius, // Top left radius in px
cornerRadius,
cornerRadius, // Top right radius in px
0,
0, // Bottom right
0,
0 // Bottom left
};
final TypedValue value = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, value, true);
mBottomSheetBackgroundColor = value.data;
updateBackground(mActivityContext.getDeviceProfile());
}
可以着重看第二个和第三个方法,代码通过mAH.get(AdapterHolder.SEARCH)获取到一个适配器持有者,并调用其setup()方法。这个方法将一个RecyclerView(mSearchRecyclerView)与适配器持有者关联起来,并传入一个过滤器函数itemInfo -> false,用于过滤掉一些特定的应用程序。然后,代码调用了rebindAdapters(true /* force */)方法,重新绑定适配器。传入的force参数为true表示强制重新绑定。
总体而言,这段代码在View布局加载完成后进行了一些后续的初始化操作,包括设置适配器、设置圆角信息、获取背景属性值等。这样也就最终将各种各样的应用图标显示到了Launcher上。