DirectBoot功能介紹

原文鏈接:https://www.jianshu.com/p/c9534e487f15

當手機已經通電開機但是用戶並有解鎖鎖屏的時候,Android N運行於一個安全的模式,也就是Dierect Boot模式。
​ 爲了支持Dierect Boot模式,系統提供了兩個存儲數據的地方:
1.Credential encrypted storage,默認存儲數據的地方,僅在用戶解鎖手機後可用。2.Device encrypted storage,主要對應的就是Direct Boot使用的存儲空間。在Direct Boot模式下和用戶解鎖手機後都可以使用的存儲空間。
​ 系統把部分系統數據和已經註冊了相關權限的Apps的數據保存在device-encrypted store 。其他的數據默認保存到credential-encrypted store。當手機開機,首先進入一個Dierect Boot的模式,在這個模式下只可以訪問device-encrypted store下的數據,無法訪問credential-encrypted store下的數據。當用戶解鎖後就都可以訪問了。
​ 一般情況下,應用是無法在Direct Boot模式下運行的如果需要某個app能夠在Direct Boot模式下運行,需要註冊相關APP的組件。通常需要在這個模式下運行的app:1.計劃通知的應用,例如Clock2.重要的用戶通知的應用,例如sms3.提供無障礙服務的應用,例如Talkback
​ 應用組件申請在Direct Boot模式下運行:在AndroidManinfest.xml中設置 android:directBootAware="true"。
​ 應用訪問device encrypted storage:創建Context.createDeviceEncryptedStorageContext().然後通過這個Context來使用device encrypted storage 的存儲空間。


 
  1. Context directBootContext = Context.createDeviceEncryptedStorageContext();

  2. // Access appDataFilename that lives in device encrypted storage

  3. FileInputStream inStream = directBootContext.openFileInput(appDataFilename);

  4. // Use inStream to read content...

應用獲取解鎖的通知:

監聽廣播ACTION_USER_UNLOCKED 。

或者接收ACTION_BOOT_COMPLETED ,這個廣播的意思是手機開機並且用戶解鎖。

也可調用UserManager.isUserUnlocked()方法來查詢。

應用遷移已經存在的數據:

Context.migrateSharedPreferencesFrom()

Context.migrateDatabaseFrom()

兩種方法在credential encrypted storage 和device encrypted storage存儲空間之間去遷移preference 和database的數據.

實例分析:啓動FallbackHome流程
在分析7.0過程中發現在啓動Launcher之前會先啓動一個FallbackHome,之後纔會啓動Launcher,通過調查發現FallbackHome屬於Settings中的一個activity,Settings的android:directBootAware爲true,並且FallbackHome在category中配置了Home屬性,而Launcher的android:directBootAware爲false,所有隻有FallbackHome可以在direct boot模式下啓動。


 
  1. <application android:label="@string/settings_label"

  2. android:icon="@mipmap/ic_launcher_settings"

  3. ............

  4. android:directBootAware="true">

  5.  
  6. <!-- Triggered when user-selected home app isn't encryption aware -->

  7. <activity android:name=".FallbackHome"

  8. android:excludeFromRecents="true"

  9. android:theme="@style/FallbackHome">

  10. <intent-filter android:priority="-1000">

  11. <action android:name="android.intent.action.MAIN" />

  12. <category android:name="android.intent.category.HOME" />

  13. <category android:name="android.intent.category.DEFAULT" />

  14. </intent-filter>

  15. </activity>

所以在ActivityManagerService啓動Home界面時,從PackageManagerService中獲取到的Home界面就是FallbackHome


 
  1. Intent getHomeIntent() {

  2. Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);

  3. intent.setComponent(mTopComponent);

  4. intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);

  5. if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {

  6. intent.addCategory(Intent.CATEGORY_HOME);

  7. }

  8. return intent;

  9. }

  10.  
  11. boolean startHomeActivityLocked(int userId, String reason) {

  12. if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL

  13. && mTopAction == null) {

  14. // We are running in factory test mode, but unable to find

  15. // the factory test app, so just sit around displaying the

  16. // error message and don't try to start anything.

  17. return false;

  18. }

  19. Intent intent = getHomeIntent();

  20. ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId); //獲取Home activity信息

  21. if (aInfo != null) {

  22. intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));

  23. // Don't do this if the home app is currently being

  24. // instrumented.

  25. aInfo = new ActivityInfo(aInfo);

  26. aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);

  27. ProcessRecord app = getProcessRecordLocked(aInfo.processName,

  28. aInfo.applicationInfo.uid, true);

  29. if (app == null || app.instrumentationClass == null) {

  30. intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);

  31. mActivityStarter.startHomeActivityLocked(intent, aInfo, reason); //啓動FallbackHome

  32. }

  33. } else {

  34. Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());

  35. }

  36. return true;

  37. }

接着就會將FallbackHome啓動起來,其實這個activity的代碼非常簡單不到100行,是個透明的activity,創建FallbackHome時註冊ACTION_USER_UNLOCKED廣播,然後進行判斷用戶是否都已經解鎖,如果沒有就結束執行。之後就會等待接收ACTION_USER_UNLOCKED廣播,繼續判斷用戶是否已經解鎖,如果此時已經解鎖,就找Home界面,如果沒有找到就發延遲消息500ms再找一次,如果找到Launcher就會將FallbackHome finish掉。
​ 下面就要看具體什麼時候發送ACTION_USER_UNLOCKED廣播了。
​ 代碼位置packages/apps/Settings/src/com/android/settings/FallbackHome.Java


 
  1. * Copyright (C) 2015 The Android Open Source Project

  2.  
  3. package com.android.settings;

  4.  
  5. import android.app.Activity;

  6.  
  7. public class FallbackHome extends Activity {

  8. private static final String TAG = "FallbackHome";

  9.  
  10. @Override

  11. protected void onCreate(Bundle savedInstanceState) {

  12. super.onCreate(savedInstanceState);

  13.  
  14. // Set ourselves totally black before the device is provisioned so that

  15. // we don't flash the wallpaper before SUW

  16. if (Settings.Global.getInt(getContentResolver(),

  17. Settings.Global.DEVICE_PROVISIONED, 0) == 0) {

  18. setTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen);

  19. }

  20. registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));

  21. maybeFinish();

  22. }

  23.  
  24. @Override

  25. protected void onDestroy() {

  26. super.onDestroy();

  27. unregisterReceiver(mReceiver);

  28. }

  29.  
  30. private BroadcastReceiver mReceiver = new BroadcastReceiver() {

  31. @Override

  32. public void onReceive(Context context, Intent intent) {

  33. maybeFinish();

  34. }

  35. };

  36.  
  37. private void maybeFinish() {

  38. if (getSystemService(UserManager.class).isUserUnlocked()) {

  39. final Intent homeIntent = new Intent(Intent.ACTION_MAIN)

  40. .addCategory(Intent.CATEGORY_HOME);

  41. final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);

  42. if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {

  43. Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");

  44. mHandler.sendEmptyMessageDelayed(0, 500);

  45. } else {

  46. Log.d(TAG, "User unlocked and real home found; let's go!");

  47. finish();

  48. }

  49. }

  50. }

  51.  
  52. private Handler mHandler = new Handler() {

  53. @Override

  54. public void handleMessage(Message msg) {

  55. maybeFinish();

  56. }

  57. };

  58. }

發送ACTION_USER_UNLOCKED廣播

在開機將近尾聲時WindowManagerService會調用enableScreenIfNeededLocked函數來判斷是否將Screen enable。通過Handler發送ENABLE_SCREEN消息到主線程


 
  1. void enableScreenIfNeededLocked() {

  2.  
  3. if (mDisplayEnabled) {

  4. return;

  5. }

  6. if (!mSystemBooted && !mShowingBootMessages) {

  7. return;

  8. }

  9.  
  10. mH.sendEmptyMessage(H.ENABLE_SCREEN);

  11. }

在mH的handleMessage中處理消息ENABLE_SCREEN,調用函數performEnableScreen來處理。


 
  1. final class H extends Handler {

  2. ........

  3. public static final int ENABLE_SCREEN = 16;

  4. ........

  5. @Override

  6. public void handleMessage(Message msg) {

  7.  
  8. case ENABLE_SCREEN: {

  9. performEnableScreen();

  10. break;

  11. }

  12. ........

  13.  
  14.  
  15.  
  16. public void performEnableScreen() {

  17. synchronized(mWindowMap) {

  18. if (mDisplayEnabled) { //如果設備已經enabled,返回

  19. return;

  20. }

  21. if (!mSystemBooted && !mShowingBootMessages) { //如果不是系統啓動,並且沒有啓動信息,返回

  22. return;

  23. }

  24.  
  25. // Don't enable the screen until all existing windows have been drawn.

  26. if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) { //如果不是強制設備enable,並且Windows還沒有繪製完成,返回

  27. return;

  28. }

  29.  
  30. ...........

  31.  
  32. if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) { //如果不是強制設備enable,並且開機動畫還沒有結束,返回

  33. return;

  34. }

  35.  
  36. EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis());

  37. mDisplayEnabled = true;

  38. if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "**** ENABLING SCREEN!");

  39.  
  40. // Enable input dispatch.

  41. mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);

  42. }

  43.  
  44. try {

  45. mActivityManager.bootAnimationComplete(); //通知ActivityManagerService開機動畫完成

  46. } catch (RemoteException e) {

  47. }

  48.  
  49. mPolicy.enableScreenAfterBoot(); //通知ActivityManagerService Screen可以enable

  50.  
  51. // Make sure the last requested orientation has been applied.

  52. updateRotationUnchecked(false, false);

  53. }

  54.  
  55. private boolean checkWaitingForWindowsLocked() {

  56.  
  57. boolean haveBootMsg = false; //是否有啓動message

  58. boolean haveApp = false; //是否有APP

  59. // if the wallpaper service is disabled on the device, we're never going to have

  60. // wallpaper, don't bother waiting for it

  61. boolean haveWallpaper = false; //是否有Wallpaper

  62. boolean wallpaperEnabled = mContext.getResources().getBoolean(

  63. com.android.internal.R.bool.config_enableWallpaperService)

  64. && !mOnlyCore; //Wallpaper是否可用

  65. boolean haveKeyguard = true; //是否有Keyguard

  66. // TODO(multidisplay): Expand to all displays?

  67. final WindowList windows = getDefaultWindowListLocked(); //獲取所有的Windows

  68. final int N = windows.size();

  69. for (int i=0; i<N; i++) {

  70. WindowState w = windows.get(i);

  71. if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {

  72. return true;

  73. }

  74. if (w.isDrawnLw()) { 判斷Window的屬性

  75. if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {

  76. haveBootMsg = true;

  77. } else if (w.mAttrs.type == TYPE_APPLICATION) {

  78. haveApp = true;

  79. } else if (w.mAttrs.type == TYPE_WALLPAPER) {

  80. haveWallpaper = true;

  81. } else if (w.mAttrs.type == TYPE_STATUS_BAR) {

  82. haveKeyguard = mPolicy.isKeyguardDrawnLw();

  83. }

  84. }

  85. }

  86.  
  87. // If we are turning on the screen to show the boot message,

  88. // don't do it until the boot message is actually displayed.

  89. if (!mSystemBooted && !haveBootMsg) {

  90. return true;

  91. }

  92.  
  93. // If we are turning on the screen after the boot is completed

  94. // normally, don't do so until we have the application and

  95. // wallpaper.

  96. if (mSystemBooted && ((!haveApp && !haveKeyguard) ||

  97. (wallpaperEnabled && !haveWallpaper))) {

  98. return true;

  99. }

  100.  
  101. return false;

  102. }

  103.  
  104. private boolean checkBootAnimationCompleteLocked() {

  105. if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {

  106. mH.removeMessages(H.CHECK_IF_BOOT_ANIMATION_FINISHED);

  107. mH.sendEmptyMessageDelayed(H.CHECK_IF_BOOT_ANIMATION_FINISHED,

  108. BOOT_ANIMATION_POLL_INTERVAL);

  109. if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Waiting for anim complete");

  110. return false;

  111. }

  112. if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Animation complete!");

  113. return true;

  114. }

  115.  
  116. case CHECK_IF_BOOT_ANIMATION_FINISHED: {

  117. final boolean bootAnimationComplete;

  118. synchronized (mWindowMap) {

  119. if (DEBUG_BOOT) Slog.i(TAG_WM, "CHECK_IF_BOOT_ANIMATION_FINISHED:");

  120. bootAnimationComplete = checkBootAnimationCompleteLocked();

  121. }

  122. if (bootAnimationComplete) {

  123. performEnableScreen();

  124. }

  125. }

  126.  
  127. @Override

  128. public void bootAnimationComplete() {

  129. final boolean callFinishBooting;

  130. synchronized (this) {

  131. callFinishBooting = mCallFinishBooting;

  132. mBootAnimationComplete = true; //設置mBootAnimationComplete爲true

  133. }

  134. if (callFinishBooting) {

  135. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "FinishBooting");

  136. finishBooting(); //調用finishBooting

  137. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

  138. }

  139. }

  140.  
  141. final void finishBooting() {

  142. synchronized (this) {

  143. if (!mBootAnimationComplete) {

  144. mCallFinishBooting = true;

  145. return;

  146. }

  147. mCallFinishBooting = false;

  148. }

  149. ................

  150. // Let system services know.

  151. mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);

  152. ...............

  153. mUserController.sendBootCompletedLocked(

  154. new IIntentReceiver.Stub() {

  155. @Override

  156. public void performReceive(Intent intent, int resultCode,

  157. String data, Bundle extras, boolean ordered,

  158. boolean sticky, int sendingUser) {

  159. synchronized (ActivityManagerService.this) {

  160. requestPssAllProcsLocked(SystemClock.uptimeMillis(),

  161. true, false);

  162. }

  163. }

  164. });

經過一系列的代碼跳轉,最終調用UserController的finishUserUnlocked函數來發送ACTION_USER_UNLOCKED廣播。


 
  1. void finishUserUnlocked(final UserState uss) {

  2. .................

  3. final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);

  4. unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);

  5. unlockedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);

  6. mService.broadcastIntentLocked(null, null, unlockedIntent, null, null, 0, null, null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID, userId);

  7. .................

  8. }

問題分析
就是因爲現在啓動Launcher時多了一個流程,導致啓動launcher比原來6.0要慢。通過查看開機log可以看到從啓動FallbackHome到啓動google桌面花費了4s


 
  1. 18:10:50.653 769 1910 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.android.settings/.FallbackHome} from uid 0 on display 0

  2.  
  3. 18:10:54.586 2029 2029 D FallbackHome: User unlocked and real home found; let's go!

  4.  
  5. 18:10:54.615 769 2207 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.google.android.setupwizard/.SetupWizardActivity} from uid 0 on display 0

如果啓動FallbackHome到啓動launcher之間相隔的時間再長一點就可能發生開機過程中顯示launcher時發生幾秒的黑屏

小結

Android 7.0新增了DirectBoot功能,AOSP中爲實現該功能修改了開機代碼流程,並且這部分流程並未根據設備是否支持DirectBoot做區分,只是流程上做了兼容,確保不支持DirectBoot的設備在這套流程下也能正常開機。

在這套流程下,用戶解鎖後纔可進入非directBootAware應用,包括Launcher。

com.android.settings/.FallbackHome中判斷用戶解鎖狀態,已解鎖纔會Finish掉去啓動Launcher,未解鎖就

等待ACTION_USER_UNLOCKED廣播後再去啓動Launcher。非DirectBoot模式下耗時4s就是在等待
finishBooting後的系統廣播ACTION_USER_UNLOCKED。

目前已從APP和PackageManagerService的角度嘗試修改,在開機流程中繞過FallbackHome,但驗證失敗:

1)去除FallbackHome的android.intent.category.Home屬性會導致停留在開機動畫之後的界面。因爲此時仍舊處於未解鎖狀態,且Launcher非directBootAware應用,PMS中的限制導致此時無法啓動Launcher;

2)修改FallbackHome和Launcher的優先級仍舊先啓動FallbackHome;

3)將Launcher標記爲directBootAware應用會導致開機後Launcher crash。因爲Launcher中的widget仍舊是非directBootAware的,此時仍舊無法啓動,除非將widget相關的APP都標記爲directBootAware;

4)PMS依賴手機當前的狀態,需要user解鎖才能正常查詢。如果強制修改,不考慮DirectBoot和當前啓動狀態,即使當前user未解鎖,依然

可以查詢符合條件的component,修改後會有無法開機的現象。因爲Launcher不是directBootAware的,當前手機user尚未解鎖,涉及存儲相關的解鎖也未進行。
開機繞過FallbackHome涉及的修改面很多,並非通過修改APP或PMS可以實現,還涉及存儲區域解鎖以及用戶狀態和ACTION_USER_UNLOCKED廣播的修改,對AOSP開機流程改動較大,暫時尚未有較好的優化方案。



作者:mrLiangshuang
鏈接:https://www.jianshu.com/p/c9534e487f15
來源:簡書

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章