當手機已經通電開機但是用戶並有解鎖鎖屏的時候,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 的存儲空間。
-
Context directBootContext = Context.createDeviceEncryptedStorageContext();
-
// Access appDataFilename that lives in device encrypted storage
-
FileInputStream inStream = directBootContext.openFileInput(appDataFilename);
-
// 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模式下啓動。
-
<application android:label="@string/settings_label"
-
android:icon="@mipmap/ic_launcher_settings"
-
............
-
android:directBootAware="true">
-
<!-- Triggered when user-selected home app isn't encryption aware -->
-
<activity android:name=".FallbackHome"
-
android:excludeFromRecents="true"
-
android:theme="@style/FallbackHome">
-
<intent-filter android:priority="-1000">
-
<action android:name="android.intent.action.MAIN" />
-
<category android:name="android.intent.category.HOME" />
-
<category android:name="android.intent.category.DEFAULT" />
-
</intent-filter>
-
</activity>
所以在ActivityManagerService啓動Home界面時,從PackageManagerService中獲取到的Home界面就是FallbackHome
-
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;
-
}
-
boolean startHomeActivityLocked(int userId, String reason) {
-
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
-
&& mTopAction == null) {
-
// We are running in factory test mode, but unable to find
-
// the factory test app, so just sit around displaying the
-
// error message and don't try to start anything.
-
return false;
-
}
-
Intent intent = getHomeIntent();
-
ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId); //獲取Home activity信息
-
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.instrumentationClass == null) {
-
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
-
mActivityStarter.startHomeActivityLocked(intent, aInfo, reason); //啓動FallbackHome
-
}
-
} else {
-
Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
-
}
-
return true;
-
}
接着就會將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
-
* Copyright (C) 2015 The Android Open Source Project
-
package com.android.settings;
-
import android.app.Activity;
-
public class FallbackHome extends Activity {
-
private static final String TAG = "FallbackHome";
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
// Set ourselves totally black before the device is provisioned so that
-
// we don't flash the wallpaper before SUW
-
if (Settings.Global.getInt(getContentResolver(),
-
Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
-
setTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen);
-
}
-
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
-
maybeFinish();
-
}
-
@Override
-
protected void onDestroy() {
-
super.onDestroy();
-
unregisterReceiver(mReceiver);
-
}
-
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-
@Override
-
public void onReceive(Context context, Intent intent) {
-
maybeFinish();
-
}
-
};
-
private void maybeFinish() {
-
if (getSystemService(UserManager.class).isUserUnlocked()) {
-
final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
-
.addCategory(Intent.CATEGORY_HOME);
-
final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);
-
if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
-
Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
-
mHandler.sendEmptyMessageDelayed(0, 500);
-
} else {
-
Log.d(TAG, "User unlocked and real home found; let's go!");
-
finish();
-
}
-
}
-
}
-
private Handler mHandler = new Handler() {
-
@Override
-
public void handleMessage(Message msg) {
-
maybeFinish();
-
}
-
};
-
}
發送ACTION_USER_UNLOCKED廣播
在開機將近尾聲時WindowManagerService會調用enableScreenIfNeededLocked函數來判斷是否將Screen enable。通過Handler發送ENABLE_SCREEN消息到主線程
-
void enableScreenIfNeededLocked() {
-
if (mDisplayEnabled) {
-
return;
-
}
-
if (!mSystemBooted && !mShowingBootMessages) {
-
return;
-
}
-
mH.sendEmptyMessage(H.ENABLE_SCREEN);
-
}
在mH的handleMessage中處理消息ENABLE_SCREEN,調用函數performEnableScreen來處理。
-
final class H extends Handler {
-
........
-
public static final int ENABLE_SCREEN = 16;
-
........
-
@Override
-
public void handleMessage(Message msg) {
-
case ENABLE_SCREEN: {
-
performEnableScreen();
-
break;
-
}
-
........
-
public void performEnableScreen() {
-
synchronized(mWindowMap) {
-
if (mDisplayEnabled) { //如果設備已經enabled,返回
-
return;
-
}
-
if (!mSystemBooted && !mShowingBootMessages) { //如果不是系統啓動,並且沒有啓動信息,返回
-
return;
-
}
-
// Don't enable the screen until all existing windows have been drawn.
-
if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) { //如果不是強制設備enable,並且Windows還沒有繪製完成,返回
-
return;
-
}
-
...........
-
if (!mForceDisplayEnabled && !checkBootAnimationCompleteLocked()) { //如果不是強制設備enable,並且開機動畫還沒有結束,返回
-
return;
-
}
-
EventLog.writeEvent(EventLogTags.WM_BOOT_ANIMATION_DONE, SystemClock.uptimeMillis());
-
mDisplayEnabled = true;
-
if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG_WM, "**** ENABLING SCREEN!");
-
// Enable input dispatch.
-
mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);
-
}
-
try {
-
mActivityManager.bootAnimationComplete(); //通知ActivityManagerService開機動畫完成
-
} catch (RemoteException e) {
-
}
-
mPolicy.enableScreenAfterBoot(); //通知ActivityManagerService Screen可以enable
-
// Make sure the last requested orientation has been applied.
-
updateRotationUnchecked(false, false);
-
}
-
private boolean checkWaitingForWindowsLocked() {
-
boolean haveBootMsg = false; //是否有啓動message
-
boolean haveApp = false; //是否有APP
-
// if the wallpaper service is disabled on the device, we're never going to have
-
// wallpaper, don't bother waiting for it
-
boolean haveWallpaper = false; //是否有Wallpaper
-
boolean wallpaperEnabled = mContext.getResources().getBoolean(
-
com.android.internal.R.bool.config_enableWallpaperService)
-
&& !mOnlyCore; //Wallpaper是否可用
-
boolean haveKeyguard = true; //是否有Keyguard
-
// TODO(multidisplay): Expand to all displays?
-
final WindowList windows = getDefaultWindowListLocked(); //獲取所有的Windows
-
final int N = windows.size();
-
for (int i=0; i<N; i++) {
-
WindowState w = windows.get(i);
-
if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
-
return true;
-
}
-
if (w.isDrawnLw()) { 判斷Window的屬性
-
if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
-
haveBootMsg = true;
-
} else if (w.mAttrs.type == TYPE_APPLICATION) {
-
haveApp = true;
-
} else if (w.mAttrs.type == TYPE_WALLPAPER) {
-
haveWallpaper = true;
-
} else if (w.mAttrs.type == TYPE_STATUS_BAR) {
-
haveKeyguard = mPolicy.isKeyguardDrawnLw();
-
}
-
}
-
}
-
// If we are turning on the screen to show the boot message,
-
// don't do it until the boot message is actually displayed.
-
if (!mSystemBooted && !haveBootMsg) {
-
return true;
-
}
-
// If we are turning on the screen after the boot is completed
-
// normally, don't do so until we have the application and
-
// wallpaper.
-
if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
-
(wallpaperEnabled && !haveWallpaper))) {
-
return true;
-
}
-
return false;
-
}
-
private boolean checkBootAnimationCompleteLocked() {
-
if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {
-
mH.removeMessages(H.CHECK_IF_BOOT_ANIMATION_FINISHED);
-
mH.sendEmptyMessageDelayed(H.CHECK_IF_BOOT_ANIMATION_FINISHED,
-
BOOT_ANIMATION_POLL_INTERVAL);
-
if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Waiting for anim complete");
-
return false;
-
}
-
if (DEBUG_BOOT) Slog.i(TAG_WM, "checkBootAnimationComplete: Animation complete!");
-
return true;
-
}
-
case CHECK_IF_BOOT_ANIMATION_FINISHED: {
-
final boolean bootAnimationComplete;
-
synchronized (mWindowMap) {
-
if (DEBUG_BOOT) Slog.i(TAG_WM, "CHECK_IF_BOOT_ANIMATION_FINISHED:");
-
bootAnimationComplete = checkBootAnimationCompleteLocked();
-
}
-
if (bootAnimationComplete) {
-
performEnableScreen();
-
}
-
}
-
@Override
-
public void bootAnimationComplete() {
-
final boolean callFinishBooting;
-
synchronized (this) {
-
callFinishBooting = mCallFinishBooting;
-
mBootAnimationComplete = true; //設置mBootAnimationComplete爲true
-
}
-
if (callFinishBooting) {
-
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "FinishBooting");
-
finishBooting(); //調用finishBooting
-
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
}
-
}
-
final void finishBooting() {
-
synchronized (this) {
-
if (!mBootAnimationComplete) {
-
mCallFinishBooting = true;
-
return;
-
}
-
mCallFinishBooting = false;
-
}
-
................
-
// Let system services know.
-
mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-
...............
-
mUserController.sendBootCompletedLocked(
-
new IIntentReceiver.Stub() {
-
@Override
-
public void performReceive(Intent intent, int resultCode,
-
String data, Bundle extras, boolean ordered,
-
boolean sticky, int sendingUser) {
-
synchronized (ActivityManagerService.this) {
-
requestPssAllProcsLocked(SystemClock.uptimeMillis(),
-
true, false);
-
}
-
}
-
});
經過一系列的代碼跳轉,最終調用UserController的finishUserUnlocked函數來發送ACTION_USER_UNLOCKED廣播。
-
void finishUserUnlocked(final UserState uss) {
-
.................
-
final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
-
unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-
unlockedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-
mService.broadcastIntentLocked(null, null, unlockedIntent, null, null, 0, null, null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID, userId);
-
.................
-
}
問題分析
就是因爲現在啓動Launcher時多了一個流程,導致啓動launcher比原來6.0要慢。通過查看開機log可以看到從啓動FallbackHome到啓動google桌面花費了4s
-
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
-
18:10:54.586 2029 2029 D FallbackHome: User unlocked and real home found; let's go!
-
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
來源:簡書