Direct Boot Mode

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.

技术点

  1. 如何使APP支持Direct Boot?
    为了让APP应用能够在用户解锁设备之前运行,须在AndroidManinfest.xml清单文件中将组件显式标记为支持Direct Boot:

  1. 广播
    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
  2. 在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。

    

        
        
            
                
                
                
            
        

所以,在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的关系大致如下图所示:
Direct Boot Mode_第1张图片

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 的工作过程

你可能感兴趣的:(Android)