Direct Boot Mode
简介
在Android M及之前,当开机启动到锁屏界面时,所有程序阻塞,等待用户解锁(即使未设置开机密码,也需要滑屏解锁)后才会继续。
而Android 7.0引入了Direct Boot模式,当手机已经通电开机但是用户并有解锁锁屏的时候,Android N运行于一个安全的模式,也就是Dierect Boot模式。
而Direct Boot模式下,仅限于运行一些关键的、紧急的APP,比如:
- Apps that have scheduled noti cations, such as alarm clock apps.
- Apps that provide important user noti cations, like SMS apps.
- Apps that provide accessibility services, like Talkback.
技术点
- 如何使APP支持Direct Boot?
为了让APP应用能够在用户解锁设备之前运行,须在AndroidManinfest.xml清单文件中将组件显式标记为支持Direct Boot:
<activity|provider|receiver|service ...
android:directBootAware=”true”>
-
广播
Android N在引入DBM后,也新增了一条ACTION_LOCKED_BOOT_COMPLETED广播:- 在进入DRM后,APP会收到广播消息:
Intent.ACTION_LOCKED_BOOT_COMPLETED
// 未解锁 - 在用户解锁后,APP会收到另一条广播:
Intent.ACTION_BOOT_COMPLETED
应用获取解锁的通知:
- 监听广播:ACTION_USER_UNLOCKED
- 监听广播:ACTION_BOOT_COMPLETED
- 在进入DRM后,APP会收到广播消息:
-
在Direct Boot模式下,应用只能访问其他支持DirectBoot的应用和组件;如果依赖外部服务或Activity,需要妥善处理外部服务或Activity不可用的情形;默认情况下,Intent过滤器仅匹配当前状态(已解锁/未解锁)下可用的组件。
启动FallbackHome流程
在Android N里,在启动Launcher之前会先启动一个FallbackHome;
FallbackHome是Settings里的一个activity,Settings的android:directBootAware
为true,而且FallbackHome在category中配置了Home属性;而Launcher的android:directBootAware
为false,所以在DirectBoot模式下,只有FallbackHome可以启动。即先启动com.android.settings/.FallbackHome ,待用户解锁后再启动com.android.launcher3/.Launcher。
<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中获取到的就是FallbackHome。参考getHomeIntent()
和startHomeActivityLocked()
等函数。
阅读FallbackHome源码:packages/apps/Settings/src/com/android/settings/FallbackHome.java
FallbackHome是个透明的Activity,其代码不足100行。FallbackHome的onCreate()
方法里面会注册监听ACTION_USER_UNLOCKED广播,并调用maybeFinish()
方法。
而在ACTION_USER_UNLOCKED广播的BroadcastReceiver里面,当此广播到来,也是调用maybeFinish()
方法。
关键的maybeFinish()
方法:
- 判断用户是否已解锁,如果未解锁就什么都不做,函数退出;
- 如果已解锁,则去寻找Launcher,如果找到,FallbackHome就会调用finish()结束自己;如若找不到就延迟500ms再找。
注意两点:
- 如何寻找Launcher?
- 如何延迟500ms ?
如下是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();
}
};
附录:
看一下第一次启动HomeActivity(即FallbackHome)的调用栈:
at com.android.server.am.ActivityStack.resumeTopActivityInnerLocked(ActivityStack.java:2597)
at com.android.server.am.ActivityStack.resumeTopActivityUncheckedLocked(ActivityStack.java:2127)
at com.android.server.am.ActivityStackSupervisor.resumeFocusedStackTopActivityLocked(ActivityStackSupervisor.java:1830)
at com.android.server.am.ActivityStarter.startActivityUnchecked(ActivityStarter.java:1249)
at com.android.server.am.ActivityStarter.startActivityLocked(ActivityStarter.java:516)
at com.android.server.am.ActivityStarter.startHomeActivityLocked(ActivityStarter.java:642)
at com.android.server.am.ActivityManagerService.startHomeActivityLocked(ActivityManagerService.java:3969)
at com.android.server.am.ActivityManagerService.systemReady(ActivityManagerService.java:13384)
at com.android.server.SystemServer.startOtherServices(SystemServer.java:1318)
at com.android.server.SystemServer.run(SystemServer.java:333)
at com.android.server.SystemServer.main(SystemServer.java:218)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:708)
注意:关注ActivityStarter.computeStackFocus()
函数,它决定了Activity在哪个stack上启动!
ACTION_USER_UNLOCKED广播(EnableScreen流程)
在开机接近尾声,WindowManagerService会调用enableScreenIfNeededLocked()
函数判断是否enable screen,通过Handler发生ENABLE_SCREEN消息到主线程。而在mH的handleMessage()中处理ENABLE_SCREEN消息时,会调用performEnableScreen()函数。
WindowManagerService.performEnableScreen()
函数中判断是否要enable screen的两个因素:
- checkWaitingForWindows()
Don’t enable the screen until all existing windows have been drawn. - checkBootAnimationCompleteLocked()
在设置service.bootanim.exit
属性后等待开机动画结束;
插叙:关于checkBootAnimationCompleteLocked()的内容:
- 检查BOOT_ANIMATION_SERVICE是否已结束,如果已结束,返回true;
- 如果未结束,则向Handler发送一个200ms的延时消息CHECK_IF_BOOT_ANIMATION_FINISHED;在此消息的处理过程中,会调用checkBootAnimationCompleteLocked()检查开机动画是否已结束,如果已结束,则会再次调用performEnableScreen(),否则继续延时等待;总之就是每200ms检查一次;
- 也就是说,performEnableScreen()函数是一直等待开机动画结束,才会继续下面的流程;
注:checkBootAnimationCompleteLocked()向我们展示了通过Handler机制polling轮询的方法,这比while循环的方式更有效率,能够改善用户响应能力。
WindowManagerService.performEnableScreen()还做了如下动作:
- 结束开机动画(即设置
service.bootanim.exit
属性为1) - 通知SurfaceFlinger,系统已Boot,如下:
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
data, null, 0);
data.recycle();
- 设置
mDisplayEnabled=true
- 调用
InputMonitor.setEventDispatchingLw()
// Enable input dispatch
很关键,解释了为什么副屏APP界面早早起来了但却触摸无响应! - 调用
ActivityManagerService.bootAnimationComplete()
它会继续调用ActivityManagerService.finishBooting()
; - 调用
PhoneWindowManager.enableScreenAfterBoot()
- 调用
updateRotationUnchecked()
// 疑问:如何强制横屏显示?
继续:ActivityManagerService.bootAnimationComplete()
函数
这里的关键是AMS的finishBooting()函数,它做了啥:
- 关闭一些APP // ??
- Enable Net Opts
- 调用
SystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED)
函数;- 会对所有注册到ServiceManager的system service,调用其onBootPhase()函数;
- 调用
UserController.sendBootCompletedLocked()
函数;
UserController类负责管理用户状态,如下:
- finishUserBoot() // STATE_BOOTING --> STATE_RUNNING_LOCKED
- finishUserUnlocking() // STATE_RUNNING_LOCKED --> STATE_RUNNING_UNLOCKING
- finishUserUnlocked() // STATE_RUNNING_UNLOCKING --> STATE_RUNNING_UNLOCKED
- stopSingleUserLocked() // STATE_RUNNING_XX --> STATE_STOPPING
- finishUserStopping() // STATE_STOPPING --> STATE_SHUTDOWN
上述函数是依次被调用的关系:
UserController.sendBootCompletedLocked()
调用finishUserBoot()
;finishUserBoot()
会调用maybeUnlockUser(),进而调用到finishUserUnlocking()
;- 而
finishUserUnlocking()
会向mHandler发送SYSTEM_USER_UNLOCK_MSG消息; - ActivityManagerService负责处理SYSTEM_USER_UNLOCK_MSG消息,会调用
finishUserUnlocked()
函数;
而且,UserController类在不同阶段会发送不同广播:
UserController.finishUserBoot()
会发送Intent.ACTION_LOCKED_BOOT_COMPLETED
广播;UserController.finishUserUnlocked()
会发送Intent.ACTION_USER_UNLOCKED
广播;UserController.finishUserUnlockedCompleted()
会发送Intent.ACTION_BOOT_COMPLETED
广播;
AMS与UserController的关系大致如下图所示:
OK,我们再回头看下FallbackHome,当收到ACTION_USER_UNLOCKED广播,它就会调用maybeFinish()
方法去启动Launcher!
注意:UserController不是一个独立模块,它是ActivityManager的一部分!
疑问:怎么没看到用户解锁的处理过程?
扩展:使能InputDispatcher
上面提到的InputMonitor.setEventDispatchingLw()
会通过InputManagerService.setInputDispatchMode()
最终调用Native层的InputDispatcher::setInputDispatchMode()
函数。
Native层的InputDispatcher::setInputDispatchMode()
函数,它就设置两个flag变量:
- mDispatchEnabled
- mDispatchFrozen
可以在shell里通过dumpsys input | grep DispatchEnabled
命令来查看此变量的值。
在InputDispatcher::dispatchOnceInnerLocked()
函数分发Input事件时,会判断mDispatchEnabled和mDispatchFrozen这俩变量的值:
- 若mDispatchEnabled为true,则丢弃此次Input事件(dropReason 为 DROP_REASON_DISABLED);
- 若mDispatchFrozen为true,则直接return;
后记:如何优化启动速度
引入Direct Boot Mode后,由于FallbackHome的原因,开机时间明显变长,如何优化?
以下内容摘录自Android7.0 DirectBoot阻塞开机分析:
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开机流程改动较大,暂时尚未有较好的优化方案,欢迎大神指教。
参考链接
- Support Direct Boot mode
- Android 7.0: What is Direct Boot, and how will it improve your experience?
- Android7.0 DirectBoot阻塞开机分析
- Android N新特性 : Direct Boot Mode
- Android Launcher 启动 Activity 的工作过程