DirectBoot阻塞开机详解

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属性,而Launcherandroid: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);
}

mHhandleMessage中处理消息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的两个主要因素有两个:

  1. checkWaitingForWindowsLocked所有Windows是否绘制
  2. 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,是否有WallpaperWallpaper是否可用,是否有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();
    }
}

当开机动画完成后就会调用AMSbootAnimationComplete函数。该方法代码如下:

@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广播后,并且此时用户已经解锁,就会将FallbackHomefinish掉,启动launcher。

到此,不出意外的话,心心念念的桌面就见到了!!!

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值