Launcher進程的啟動到用戶界面的呈現,揭示每個階段的關鍵步驟
Launcher啟動器
Launcher(啟動器、桌面) 是 Android 操作系統上用于展示應用圖標、搜索應用、管理桌面快捷方式以及執行其他與設備主屏幕相關任務的用戶界面。設備的主屏幕布局和外觀是用戶與設備交互的主要方式。
Launcher特點:
- 「自定義性」:大多數 Launcher 都允許用戶自定義圖標、壁紙、桌面布局等。
- 「性能」:高效的 Launcher 可以提高設備的整體性能,因為它需要快速響應觸摸輸入并加載圖標和布局。
- 「兼容性」:隨著 Android 版本的更新,Launcher 需要確保與最新版本的 Android 兼容。
- 「安全性」:Launcher 必須確保用戶數據的安全,并防止惡意軟件的攻擊。
- 「多樣性」:市場上有許多不同的 Launcher 應用,每個應用都有其獨特的功能和界面設計。
Launcher進程啟動流程
- 「SystemServer進程啟動」:
SystemServer是Android系統中的一個核心進程,負責啟動和初始化各種系統服務。
在SystemServer的啟動過程中,會調用其他服務,如PackageManagerService(PMS)和ActivityManagerService(AMS)的初始化方法。
public final class SystemServer {
private void run() {
...
startBootstrapServices();
startOtherServices();
...
}
private void startBootstrapServices() {
...
mActivityManagerService = mSystemServiceManager.startService(ActivityManagerService.Lifecycle.class).getService();
mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
mActivityManagerService.setInstaller(installer);
...
}
private void startOtherServices() {
...
mActivityManagerService.systemReady(() -> {
}, BOOT_TIMINGS_TRACE_LOG);
}
}
在SystemServer啟動的時候,執行startOtherServices()方法中調用了AMS的systemReady()方法,通過該方法來啟動Launcher。
// Tag for timing measurement of main thread.
private static final String SYSTEM_SERVER_TIMING_TAG = "SystemServerTiming";
private static final TimingsTraceLog BOOT_TIMINGS_TRACE_LOG
= new TimingsTraceLog(SYSTEM_SERVER_TIMING_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
private void startOtherServices() {
...
mActivityManagerService.systemReady(() -> {
Slog.i(TAG, "Making services ready");
traceBeginAndSlog("StartActivityManagerReadyPhase");
mSystemServiceManager.startBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
...
}, BOOT_TIMINGS_TRACE_LOG);
}
- 「PMS服務初始化」:
PMS服務會完成系統中應用程序的安裝和管理工作。
PMS會掃描/data/app目錄,加載已經安裝的應用程序信息。
- 「AMS服務初始化」:
AMS是Android系統中負責管理應用程序生命周期和活動(Activity)狀態的服務。
在AMS的初始化過程中,會注冊各種系統廣播接收器,包括與Launcher啟動相關的廣播。
4.「Launcher應用程序的注冊」:
Launcher應用程序是一個特殊的系統應用,它在AndroidManifest.xml文件中配置了特定的Intent Filter,以便系統能夠識別并啟動它。
通常,Launcher應用程序的Action被設置為Intent.ACTION_MAIN,而Category被設置為Intent.CATEGORY_HOME。
5.「SystemReady階段」:
當系統完成初始化并準備好啟動桌面時,AMS會調用其systemReady()方法。
在systemReady()方法中,AMS會檢查系統是否準備好啟動Launcher,并調用相關方法來啟動。
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
...
synchronized (this) {
...
startHomeActivityLocked(currentUserId, "systemReady");
...
}
...
}
在startHomeActivityLocked()方法中,通過getHomeIntent()方法獲取到要啟動的HomeActivity的intent對象,mTopAction默認為INTENT.ACTION_MAIN,并添加CATEGORY_HOME的category標志。通過PackageManager去獲取對應符合的Activity,獲取對應的ActivityInfo,并獲取對應的進程記錄,此時對應的進程還沒啟動,為intent添加FLAG_ACTIVITY_NEW_TASK啟動參數開啟新棧,隨后調用ActivityStartController類的startHomeActivity()方法去執行啟動。
boolean startHomeActivityLocked(int userId, String reason) {
...
Intent intent = getHomeIntent();
ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
if (aInfo != null) {
intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
// Don't do this if the home app is currently being instrumented.
aInfo = new ActivityInfo(aInfo);
aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
ProcessRecord app = getProcessRecordLocked(aInfo.processName, aInfo.applicationInfo.uid, true);
if (app == null || app.instr == null) {
intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
// For ANR debugging to verify if the user activity is the one that actually launched.
final String myReason = reason + ":" + userId + ":" + resolvedUserId;
mActivityStartController.startHomeActivity(intent, aInfo, myReason);
}
}
...
return true;
}
Intent getHomeIntent() {
Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
intent.setComponent(mTopComponent);
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);
}
return intent;
}
- 「啟動Launcher進程」:
AMS啟動Launcher進程。
該方法會創建一個新的進程(如果Launcher尚未運行)來啟動Launcher應用程序。
void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) {
mSupervisor.moveHomeStackTaskToTop(reason);
mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
.setOutActivity(tmpOutRecord)
.setCallingUid(0)
.setActivityInfo(aInfo)
.execute();
mLastHomeActivityStartRecord = tmpOutRecord[0];
if (mSupervisor.inResumeTopActivity) {
// If we are in resume section already, home activity will be initialized, but not
// resumed (to avoid recursive resume) and will stay that way until something pokes it
// again. We need to schedule another resume.
mSupervisor.scheduleResumeTopActivities();
}
}
int execute() {
try {
// TODO(b/64750076): Look into passing request directly to these methods to allow
// for transactional diffs and preprocessing.
if (mRequest.mayWait) {
return startActivityMayWait(mRequest.caller, mRequest.callingUid, ...);
} else {
return startActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent, ...);
}
} finally {
onExecutionComplete();
}
}
- 「Launcher進程啟動后的操作」:
Launcher進程啟動后,會向PMS請求已安裝應用程序的信息,并將這些信息展示在桌面上。
用戶可以通過點擊桌面上的應用程序圖標來啟動相應的應用程序。
@TargetApi(23)
public InvariantDeviceProfile(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
...
ArrayList<InvariantDeviceProfile> closestProfiles = findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles(context));
...
}
ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles(Context context) {
ArrayList<InvariantDeviceProfile> profiles = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if ((type == XmlPullParser.START_TAG) && "profile".equals(parser.getName())) {
TypedArray a = context.obtainStyledAttributes(Xml.asAttributeSet(parser), R.styleable.InvariantDeviceProfile);
int numRows = a.getInt(R.styleable.InvariantDeviceProfile_numRows, 0);
int numColumns = a.getInt(R.styleable.InvariantDeviceProfile_numColumns, 0);
float iconSize = a.getFloat(R.styleable.InvariantDeviceProfile_iconSize, 0);
profiles.add(new InvariantDeviceProfile(
a.getString(R.styleable.InvariantDeviceProfile_name),
a.getFloat(R.styleable.InvariantDeviceProfile_minWidthDps, 0),
a.getFloat(R.styleable.InvariantDeviceProfile_minHeightDps, 0),
numRows,
numColumns,
a.getInt(R.styleable.InvariantDeviceProfile_numFolderRows, numRows),
a.getInt(R.styleable.InvariantDeviceProfile_numFolderColumns, numColumns),
iconSize,
a.getFloat(R.styleable.InvariantDeviceProfile_landscapeIconSize, iconSize),
a.getFloat(R.styleable.InvariantDeviceProfile_iconTextSize, 0),
a.getInt(R.styleable.InvariantDeviceProfile_numHotseatIcons, numColumns),
a.getResourceId(R.styleable.InvariantDeviceProfile_defaultLayoutId, 0),
a.getResourceId(R.styleable.InvariantDeviceProfile_demoModeLayoutId, 0)));
a.recycle();
}
}
} catch (IOException|XmlPullParserException e) {
throw new RuntimeException(e);
}
return profiles;
}
InvariantDeviceProfile對象主要是存儲App的基本配置信息,例如App圖標的尺寸大小,文字大小,每個工作空間或文件夾能顯示多少App等。
在LauncherModel的startLoader()方法中,新建了一個LoaderResults對象,通過startLoaderForResults()方法創建出一個LoaderTask的Runnable任務。
public boolean startLoader(int synchronousBindPage) {
...
synchronized (mLock) {
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
...
LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel, mBgAllAppsList, synchronousBindPage, mCallbacks);
if (mModelLoaded && !mIsLoaderTaskRunning) {
...
return true;
} else {
startLoaderForResults(loaderResults);
}
}
}
return false;
}
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
runOnWorkerThread(mLoaderTask);
}
}
private static void runOnWorkerThread(Runnable r) {
if (sWorkerThread.getThreadId() == Process.myTid()) {
r.run();
} else {
// If we are not on the worker thread, then post to the worker handler
sWorker.post(r);
}
}
在LoaderTask的run()方法中,加載手機已安裝的App的信息,查詢數據庫獲取已安裝的App的相關信息,加載Launcher布局,并將數據轉化為View,綁定到界面上,最終就可以看到桌面顯示的宮格列表的桌面圖標了。
public void run() {
...
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
// 查詢數據庫整理App信息,轉化為View綁定到界面
loadWorkspace();
mResults.bindWorkspace();
loadAllApps();
mResults.bindAllApps();
loadDeepShortcuts();
mResults.bindDeepShortcuts();
mBgDataModel.widgetsModel.update(mApp, null);
mResults.bindWidgets();
transaction.commit();
} catch (CancellationException e) {
// Loader stopped, ignore
TraceHelper.partitionSection(TAG, "Cancelled");
}
TraceHelper.endSection(TAG);
}
隨著Android系統的不斷發展和更新,Launcher進程的啟動流程也可能會發生相應的變化和優化。Android系統還支持多種啟動Launcher的方式,如開機后自動啟動、短按Home鍵啟動以及異常崩潰后自動重啟等。這些啟動方式的實現流程也有所不同,但基本流程都與上述步驟相似。