一. 需求场景
开机需要第一时间快速启动指定app
二. 需求分析
在Android 6.0及之前,在开机启动到锁屏界面时,所有程序阻塞,等待用户解锁(即使未设置开机密码,也需要滑屏解锁)后才会继续。
Android 7.0引入的新特性:Direct Boot Mode:设备启动后进入的一个新模式,直到用户解锁(unlock)设备此阶段结束。
Direct Boot模式下,仅限于运行一些关键的、紧急的APP,比如:
Apps that have scheduled notifications, such as alarm clock apps. //AlarmClock操作
Apps that provide important user notifications, like SMS apps. //重要通知
Apps that provide accessibility services, like Talkback. //特殊服务
另外需要注意的是,Direct Boot Mode下使用一种新的存储空间:Device Protected Storage。在正常模式下是看不到这个空间的数据的,所以在Direct Boot Mode下启动app,也是需要Device Protected Storage的访问权限。
所以要想在android 7.0及以后的系统上实现开机启动指定app,app必须打开directBootAware模式。
三. 实现方法
1. app修改
为了让该应用能够在用户解锁设备之前运行,必须在AndroidManifest中将application及启动的组件显式标记为支持直接启动:
<application
......
android:directBootAware="true"
android:defaultToDeviceProtectedStorage="true">
<activity
......
android:directBootAware=”true”
......
/activity>
<!--also can use at provider or receiver or service -->
......
2. 如果无法修改app,framework也可以针对特定app进行修改
我们可以在parse package的时候,强制把对应的包名及要启动的组件强制设置directBootAware
android版本不同,可能位置有点差异,比如
android 11是在
framework/base/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java解析component
@NonNull
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
static <Component extends ParsedMainComponent> ParseResult<Component> parseMainComponent(
Component component, String tag, String[] separateProcesses, ParsingPackage pkg,
TypedArray array, int flags, boolean useRoundIcon, ParseInput input,
int bannerAttr, int descriptionAttr, @Nullable Integer directBootAwareAttr,
@Nullable Integer enabledAttr, int iconAttr, int labelAttr, int logoAttr, int nameAttr,
@Nullable Integer processAttr, int roundIconAttr, @Nullable Integer splitNameAttr) {
......
if(component.getName().equals("com.example.test.MainActivity")) {
component.directBootAware = true;
pkg.setPartiallyDirectBootAware(true);
}
......
framework/ base/core/java/android/content/pm/parsing/ParsingPackageUtils.java解析package
private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) {
......
if (pkg.getPackageName().equals("com.example.test")) {
pkg.setDirectBootAware(true);
}
......
android 9则是在
framework/base/core/java/android/content/pm/PackageParser.java解析package和组件的
//解析package
private boolean parseBaseApplication(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
......
if (sa.getBoolean(
R.styleable.AndroidManifestApplication_defaultToDeviceProtectedStorage,
false)) {
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
}
if (sa.getBoolean(
R.styleable.AndroidManifestApplication_directBootAware,
false)) {
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
}
//add
if (pkgName.equals("com.example.test")) {
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
}
//end
......
}
//解析activity
private Activity parseActivity(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs,
boolean receiver, boolean hardwareAccelerated)
throws XmlPullParserException, IOException {
......
if (a.info.directBootAware) {
owner.applicationInfo.privateFlags |=
ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
}
//add
if (a.info.name.equals("com.example.test.MainActivity")) {
owner.applicationInfo.privateFlags |=
ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
}
//end
......
}
四. 遇到的问题
之前搞快速启动app的时候开了directBootAware模式,但启动的时候报如下错误
java.lang.IllegalStateException: SharedPreferences in credential encrypted storage are not available until after user is unlocked
......
android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14 SQLITE_CANTOPEN): Could not open database
原因就是上面说的,没有Device Protected Storage权限
解决方案就是设置defaultToDeviceProtectedStorage为true