Android開機動畫的顯示(二)
參考部落格
接著上面Android開機動畫的顯示(一)分析,開機動畫怎麼結束的,又是如何顯示桌面(鎖屏介面)的。
一、程式碼流程
在前面的 Android開機流程 可以知道,SystemServer最後會呼叫到AMS.systemReady
1.1 AMS.systemReady
......
Slog.i(TAG, "System now ready");
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_AMS_READY,
SystemClock.uptimeMillis ());
synchronized(this) {
// mFactoryTest 就是屬性 ro.factorytest ,這個值預設為0,代表非工廠模式
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
// 正常不進入.......
}
......
// 標記正在啟動,啟動完成後置回false
mBooting = true;
// isSplitSystemUser()最後獲取的還是屬性ro.fw.system_user_split,預設值是false,目前沒有配置這個值
// USER_SETUP_COMPLETE 這個標記了是否設定過了開機嚮導
if (UserManager.isSplitSystemUser() &&
Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0) != 0) {
ComponentName cName = new ComponentName (mContext, SystemUserHomeActivity.class);
try {
AppGlobals.getPackageManager().setComponentEnabledSetting(cName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0,
UserHandle.USER_SYSTEM);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
// 重點在這裡,啟動桌面也就是launcher
startHomeActivityLocked(currentUserId, "systemReady");
......
1.2 AMS.startHomeActivityLocked
boolean startHomeActivityLocked(int userId, String reason) {
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
&& mTopAction == null) {
// 如上分析,這裡是進入了工廠測試模式,但是找不到工模應用
// 直接返回,顯示錯誤資訊
return false;
}
// 1.2.1 獲取待啟動的桌面Activity的Intent
Intent intent = getHomeIntent();
// 解析桌面應用,在這裡面識別預設桌面應用,這裡不深究,後續分析桌面應用的啟動
ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
if (aInfo != null) {
intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
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);
final String myReason = reason + ":" + userId + ":" + resolvedUserId;
// 1.3 啟動桌面應用的Activity!
mActivityStartController.startHomeActivity(intent, aInfo, myReason);
}
} else {
Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
}
return true;
}
1.2.1 AMS.getHomeIntent
獲取HomeActivity的啟動Intent,我們知道只要帶有Intent.ACTION_MAIN以及Intent.CATEGORY_HOME,就會被識別成桌面應用。
// 正常模式下mTopComponent和mTopData都是null的
ComponentName mTopComponent;
String mTopAction = Intent.ACTION_MAIN;
String mTopData;
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;
}
1.3 Activity啟動準備完畢
一個Activity元件在啟動起來之後,就會被記錄起來,等到它所執行在的主執行緒空閒的時候,這個主執行緒就會向ActivityManagerService傳送一個Activity元件空閒的通知。由於應用程式Launcher是系統中第一個被啟動的應用程式,即它的根Activity元件是系統中第一個被啟動的Activity元件,因此,當ActivityManagerService接收到它的空閒通知的時候,就可以知道系統是剛剛啟動起來的。在這種情況下,ActivityManagerService就會停止顯示開機動畫,以便可以在螢幕中顯示應用程式Lancher的介面。 應用程式的主執行緒是通過ActivityThread類來描述的,它實現在檔案frameworks/base/core/java/android/app/ActivityThread.java中。每當有一個新的Activity元件啟動起來的時候,ActivityThread類都會向它所描述的應用程式主執行緒的訊息佇列註冊一個型別為Idler的空閒訊息處理器(詳見Android訊息機制的分析)。這樣一個應用程式的主執行緒就可以在空閒的時候,向ActivityManagerService傳送一個Activity元件空閒通知,相當於是通知ActivityManagerService,一個新的Activity元件已經準備就緒了。 應用程式主執行緒通知AMS有Activity元件空閒通知後,AMS就會呼叫mStackSupervisor.activityIdleInternalLocked,在這裡面檢查booting狀態並決定是否向AMS報告結束啟動過程,也就是結束顯示開機動畫。
1.3.1 ActivityStack.activityIdleInternal
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
boolean processPausingActivities, Configuration config) {
if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token);
......
if (isFocusedStack(r.getStack()) || fromTimeout) {
// 檢查當前booting狀態
booting = checkFinishBootingLocked();
}
......
}
private boolean checkFinishBootingLocked() {
// mService就是AMS
final boolean booting = mService.mBooting;
boolean enableScreen = false;
mService.mBooting = false;
if (!mService.mBooted) {
mService.mBooted = true;
enableScreen = true;
}
if (booting || enableScreen) {
// 1.3.2 當系統正處於啟動狀態,且mBooted(已經啟動過了)為false
// AMS呼叫postFinishBooting,結束開機動畫的顯示
// booting和enableScreen均為true
mService.postFinishBooting(booting, enableScreen);
}
return booting;
}
1.3.2 AMS.postFinishBooting
void postFinishBooting(boolean finishBooting, boolean enableScreen) {
mHandler.sendMessage(mHandler.obtainMessage(FINISH_BOOTING_MSG,
finishBooting ? 1 : 0, enableScreen ? 1 : 0));
}
final class MainHandler extends Handler {
......
public void handleMessage(Message msg) {
case FINISH_BOOTING_MSG: {
if (msg.arg1 != 0) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "FinishBooting");
finishBooting();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
if (msg.arg2 != 0) {
// 1.3.3 結束開機動畫
enableScreenAfterBoot();
}
break;
......
}
1.3.3 AMS.enableScreenAfterBoot
void enableScreenAfterBoot() {
// 在event.log裡面打印出 boot_progress_enable_screen
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN,
SystemClock.uptimeMillis());
// 1.3.4 通知到WMS可以停止顯示開機動畫了
mWindowManager.enableScreenAfterBoot();
synchronized (this) {
updateEventDispatchingLocked();
}
}
1.3.4 WMS.enableScreenAfterBoot
public void enableScreenAfterBoot() {
synchronized(mWindowMap) {
if (mSystemBooted) {
return;
}
// 用來記錄系統是否已經啟動完成,true代表完成
mSystemBooted = true;
hideBootMessagesLocked();
// 設定30s超時
mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30 * 1000);
}
mPolicy.systemBooted();
// 1.3.5 執行停止顯示開機動畫的操作
performEnableScreen();
}
1.3.5 WMS.performEnableScreen
private void performEnableScreen() {
synchronized(mWindowMap) {
if (mDisplayEnabled) {
return;
}
......
if (!mBootAnimationStopped) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
// 設定屬性以通知bootanim程序退出
SystemProperties.set("service.bootanim.exit", "1");
mBootAnimationStopped = true;
}
// 等待確認開機動畫確實退出了
if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) {
if (DEBUG_BOOT) Slog.i(TAG_WM, "performEnableScreen: Waiting for anim complete");
return;
}
// 執行SurfaceFlinger結束開機動畫的操作
try {
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
if (surfaceFlinger != null) {
Slog.i(TAG_WM, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
data, null, 0);
data.recycle();
}
} catch (RemoteException ex) {
Slog.e(TAG_WM, "Boot completed: SurfaceFlinger is dead!");
}
EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis());
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
mDisplayEnabled = true;
if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "******************** ENABLING SCREEN!");
// Enable input dispatch.
mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);
}
try {
mActivityManager.bootAnimationComplete();
} catch (RemoteException e) {
}
mPolicy.enableScreenAfterBoot();
// Make sure the last requested orientation has been applied.
updateRotationUnchecked(false, false);
}
在這個步驟裡就會設定屬性service.bootanim.exit為1以通知bootanim可以結束開機動畫的播放了。還記得之前的BootAnimation.cpp:playAnimation(const Animation&)方法,這個方法在每幀渲染結束後都會區檢查屬性service.bootanim.exit的值以決定是否退出開機動畫。
1.4. BootAnimation.cpp:checkEixt()
由Android開機動畫的顯示(一)可以知道,播放開機動畫過程中每幀繪製結束後都會呼叫這個方法。
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
void BootAnimation::checkExit() {
char value[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME, value, "0");
int exitnow = atoi(value);
if (exitnow) {
// 屬性值不為0,代表可以AMS層啟動完畢,launcher介面也準備好了
// Thread請求退出
requestExit();
// 這裡的callback其實是AudioAnimationCallbacks
mCallbacks->shutdown();
}
}
二、總結
至此,Android系統的三個開機畫面的顯示過程就分析完成了。通過這個三個開機畫面的顯示過程分析,我們學習到:
-
在核心層,系統螢幕是使用一個稱為幀緩衝區的硬體裝置來描述的,而使用者空間的應用程式可以通過裝置檔案/dev/fb0或者/dev/graphics/fb0來操作這個硬體裝置。實際上,幀緩衝區本身並不是一個真正的硬體,它只不過是對顯示卡的一個抽象表示,不過,我們通過訪幀緩衝區就可以間接地操作顯示卡記憶體以及顯示卡中的其它暫存器。
-
OpenGL是通過EGL介面來渲染螢幕,而EGL介面是通過ANativeWindow類來間接地渲染螢幕的。我們可以將ANativeWindow類理解成一個Android系統的本地視窗類,即相當於是Windows系統中的視窗控制代碼概念,它最終是通過檔案/dev/fb0或者/dev/graphics/fb0來渲染螢幕的。
-
每當我們設定一個系統屬性的時候,init程序都會接收到一個系統屬性變化事件。當發生變化的系統屬性的名稱等於“ctl.start”或者“ctl.stop”,那麼實際上是向init程序發出一個啟動或者停止服務的命令。
-
BootAnimation通過在每幀繪製完畢後檢查屬性service.bootanim.exit的值判斷是否結束播放開機動畫,而這個值會在launcher應用的根Activity啟動完成處於idle狀態時由該應用通知AMS進而通知WMS設定的。其實在這一過程中SurfaceFlinger也會設定一次。
開機動畫的流程是分析完畢了,但還有個疑問,launcher介面是什麼時候、怎麼顯示到螢幕上的?這就涉及到SurfaceFlinger、WMS與應用程序的互動了。後面先分析SurfaceFlinger的工作原理,一步步剖析Andorid的整個UI架構。