DirectBoot
阻塞开机
DirectBoot
介绍
当设备已开机但用户尚未解锁设备时,Android 7.0 将在安全的“直接启动”模式下运行。为支持此模式,系统为数据提供了两个存储位置:
- (Credential encrypted storage)凭据加密存储,这是默认存储位置,仅在用户解锁设备后可用。
- (Device encrypted storage)设备加密存储,该存储位置在“直接启动”模式下和用户解锁设备后均可使用。
默认情况下,应用不会在Direct Boot模式下运行,如果需要在该模式下执行操作,可以注册应在此模式下运行的应用组件,常见的一些应用用例包括:
- 已安排通知的应用,如闹钟应用。
- 提供重要用户通知的应用,如短信应用。
- 提供无障碍服务的应用,如 Talkback。
应用组件申请在Direct Boot模式下运行:将组件标记为加密感知来向系统注册,也就是在清单中``android:directBootAware=true`。
当设备重启后,加密感知组件可以注册以接收来自系统的 ACTION_LOCKED_BOOT_COMPLETED
广播消息。此时,设备加密存储可用,组件可以执行需要在“Direct Boot”模式下运行的任务,例如触发已设定的闹铃。如同这样:
<receiver
android:directBootAware="true" >
...
<intent-filter>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
在用户解锁设备后,所有组件均可访问设备加密存储(Device encrypted storage)和凭据加密存储(Credential encrypted storage)。
应用访问Device encrypted storage:通过调用 Context.createDeviceProtectedStorageContext()
创建另一个 Context
实例。通过此上下文发出的所有存储API
调用均访问设备加密存储。以下示例会访问设备加密存储并打开现有的应用数据文件:
Context directBootContext = appContext.createDeviceProtectedStorageContext();
// 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.moveSharedPreferencesFrom()
和 Context.moveDatabaseFrom()
可以在凭据加密存储与设备加密存储之间迁移偏好设置和数据库数据。具体使用方式查询官方文档。
启动FallbackHome
在之前的文章中提到过,Android 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>
..........
</application>
因此在ActivityManagerService
启动Home界面时,从PackageManagerService
中获取到的Home界面就是FallbackHome
。可以看出FallbackHome
的优先级为-1000
,后面PackageManagerService
里面对这样的情况是做了处理的。
Android开机过程中,会把各种系统服务拉起,并且调用其systemReady()
函数。其中最关键的ActivityManagerService
拉起后,systemReady()
中调用了一个函数startHomeActivityLocked()
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
...
startHomeActivityLocked(currentUserId, "systemReady");
...
}
下面是这个函数的内容:
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();
//获取Home activity信息
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;
//启动FallbackHome
mActivityStartController.startHomeActivity(intent, aInfo, myReason);
}
} else {
Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
}
return true;
}
可以看到需要构造一个Intent
,是通过getHomeIntent()
方法得到的,下面是该方法的内容:
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;
}
这里会构造一个带有CATEGORY_HOME
的Intent,用此intent来从PackageManagerService
查询对应的ActivityInfo
。
接着就会将FallbackHome
启动起来,创建FallbackHome
时注册ACTION_USER_UNLOCKED广播,然后进行判断用户是否都已经解锁,如果没有就结束执行。之后就会等待接收ACTION_USER_UNLOCKED广播,继续判断用户是否已经解锁,如果此时已经解锁,就找Home界面,如果没有找到就发延迟消息500ms
再找一次,如果找到Launcher就会将FallbackHome
finish
掉。
下面是FallbackHome.java
的代码(packages/apps/Settings/src/com/android/settings/FallbackHome.java
)
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)) {
if (UserManager.isSplitSystemUser()
&& UserHandle.myUserId() == UserHandle.USER_SYSTEM) {
// This avoids the situation where the system user has no home activity after
// SUW and this activity continues to throw out warnings. See b/28870689.
return;
}
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!");
getSystemService(PowerManager.class).userActivity(
SystemClock.uptimeMillis(), false);
finish();
}
}
}
在开机将近尾声的时候,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
来处理。该函数是WindowManagerService
的内部类H
的,代码如下
final class H extends Handler {
........
public static final int ENABLE_SCREEN = 16;
........
@Override
public void handleMessage(Message msg) {
case ENABLE_SCREEN: {
performEnableScreen();
break;
}
........
}
在performEnableScreen
函数中判断是否要enable Screen的两个主要因素有两个:
-
checkWaitingForWindowsLocked
所有Windows是否绘制 -
checkBootAnimationCompleteLocked
开机动画时候完成
如果都完成了会通知AMS
开机动画完成了,并且要enable Screen了。
部分performEnableScreen
方法代码如下:
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);
}
上面方法中检查Windows是否绘制完成主要是检查是否有启动message
,是否有Wallpaper
,Wallpaper
是否可用,是否有Keyguard
进行判断。也就是方法checkWaitingForWindowsLocked
中进行一系列判断的,该方法代码如下:
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;
}
检查完Windows是否绘制完成后,还要检查开机动画是否完成,主要就是判断开机动画服务是否在运行,如果仍然在运行,就会发送一个200ms
的延迟消息CHECK_IF_BOOT_ANIMATION_FINISHED,每200ms
都再检查一次,该方法是checkBootAnimationCompleteLocked
,下面是它的代码:
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;
}
在处理CHECK_IF_BOOT_ANIMATION_FINISHED消息时,会再次判断开机动画是否完成,如果完成了就会调用performEnableScreen
往下面执行,否则的还是每隔200ms
发一次消息检查开机动画是否完成。
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();
}
}
当开机动画完成后就会调用AMS
的bootAnimationComplete
函数。该方法代码如下:
@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);
}
}
在finishBooting
中通过mUserController
调用sendBootCompletedLocked
函数
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.java代码位置frameworks/base/services/core/java/com/android/server/am/
具体流程图如下:
经过一系列的代码跳转,最终调用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);
.................
}
当FallbackHome
接收到ACTION_USER_UNLOCKED广播后,并且此时用户已经解锁,就会将FallbackHome
finish掉,启动launcher。
到此,不出意外的话,心心念念的桌面就见到了!!!