【WorkManager】无法在 Direct Boot 模式下初始化

【WorkManager】无法在 Direct Boot 模式下初始化

  • 一、问题描述
  • 二、问题分析
    • [2.1 关于 Direct Boot 模式](#2.1 关于 Direct Boot 模式)
    • [2.2 支持 Direct Boot 模式](#2.2 支持 Direct Boot 模式)
    • [2.3 手动初始化 WorkManager 组件](#2.3 手动初始化 WorkManager 组件)
    • [2.4 WorkManager 不支持 Direct Boot 的官方修改](#2.4 WorkManager 不支持 Direct Boot 的官方修改)
  • 三、解决方案

一、问题描述

在使用 WorkManager 库来实现开机上报设备信息和定时上报设备信息时,当 Android 设备设置屏幕锁定密码并未解锁时,此时 WorkManager 会无法初始化导致异常无法执行任务。

bash 复制代码
09-08 09:44:41.744  1827  1827 E AndroidRuntime: FATAL EXCEPTION: main
09-08 09:44:41.744  1827  1827 E AndroidRuntime: Process: com.xxx.devicereport, PID: 1827
09-08 09:44:41.744  1827  1827 E AndroidRuntime: java.lang.RuntimeException: Unable to get provider androidx.startup.InitializationProvider: androidx.startup.StartupException: java.lang.IllegalStateException: Cannot initialize WorkManager in direct boot mode
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.app.ActivityThread.installProvider(ActivityThread.java:7940)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.app.ActivityThread.installContentProviders(ActivityThread.java:7444)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7148)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2328)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:106)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.os.Looper.loopOnce(Looper.java:205)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.os.Looper.loop(Looper.java:294)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:8408)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:640)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:982)
09-08 09:44:41.744  1827  1827 E AndroidRuntime: Caused by: androidx.startup.StartupException: java.lang.IllegalStateException: Cannot initialize WorkManager in direct boot mode
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.startup.AppInitializer.doInitialize(AppInitializer.java:187)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.startup.AppInitializer.discoverAndInitialize(AppInitializer.java:239)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.startup.AppInitializer.discoverAndInitialize(AppInitializer.java:207)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.startup.InitializationProvider.onCreate(InitializationProvider.java:49)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.content.ContentProvider.attachInfo(ContentProvider.java:2621)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.content.ContentProvider.attachInfo(ContentProvider.java:2590)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at android.app.ActivityThread.installProvider(ActivityThread.java:7935)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        ... 11 more
09-08 09:44:41.744  1827  1827 E AndroidRuntime: Caused by: java.lang.IllegalStateException: Cannot initialize WorkManager in direct boot mode
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.work.impl.WorkManagerImpl.<init>(WorkManagerImpl.java:237)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.work.impl.WorkManagerImplExtKt.createWorkManager(WorkManagerImplExt.kt:48)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.work.impl.WorkManagerImplExtKt.createWorkManager$default(WorkManagerImplExt.kt:29)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.work.impl.WorkManagerImplExtKt.createWorkManager(WorkManagerImplExt.kt:0)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.work.impl.WorkManagerImpl.initialize(WorkManagerImpl.java:206)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.work.WorkManager.initialize(WorkManager.java:212)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.work.WorkManagerInitializer.create(WorkManagerInitializer.java:39)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.work.WorkManagerInitializer.create(WorkManagerInitializer.java:30)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        at androidx.startup.AppInitializer.doInitialize(AppInitializer.java:180)
09-08 09:44:41.744  1827  1827 E AndroidRuntime:        ... 17 more

二、问题分析

2.1 关于 Direct Boot 模式

Android中的 Direct Boot 模式(直接启动模式)是Android 7.它允许设备在启动后、用户解锁前的锁定状态下运行特定的应用组件,确保关键功能(如闹钟、紧急通知和无障碍服务)在设备重启后仍能正常工作。

核心设计目标

Direct Boot模式解决了全盘加密(FDE)设备重启后需用户解锁才能运行应用的问题。通过文件级加密(FBE)和两个独立的存储分区,系统在未解锁状态下仍能有限运行:

  • 设备加密存储(DE):使用设备硬件绑定的密钥加密,在Direct Boot模式和用户解锁后均可访问。
  • 凭据加密存储(CE):使用用户凭据(如密码、PIN)加密,仅在用户解锁后可用。

加密路径

/data/user_de/0/com.xxx.aidevicereport

非加密

/data/user/0/com.xxx.devicereport

实现方式

组件标记 在AndroidManifest.xml中将组件(Application、Activity、Service、BroadcastReceiver等)设置为android:directBootAware="true"

2.2 支持 Direct Boot 模式

关于设备开启重启后的几个广播

java 复制代码
// 无论直接启动支持如何,在启动时立即发送
public static final String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";

// 目标用户解锁凭据加密的专用存储时发送
public static final String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";

// 设备解锁后发送
public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";

查询用户是否已解锁设备的方法

java 复制代码
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
if (userManager != null) {
    boolean isUserUnlocked = userManager.isUserUnlocked();
    Log.d(TAG, "Application isUserUnlocked:" + isUserUnlocked);
}

访问设备加密存储空间的方法,通过此上下文 createDeviceProtectedStorageContext() 发出的所有存储 API 调用均访问设备加密存储空间,包括文件数据库SharedPreferences

java 复制代码
Context directBootContext = appContext.createDeviceProtectedStorageContext();
// Access appDataFilename that lives in device encrypted storage
FileInputStream inStream = directBootContext.openFileInput(appDataFilename);
// Use inStream to read content...

官方文档:https://developer.android.com/privacy-and-security/direct-boot?hl=zh-cn

2.3 手动初始化 WorkManager 组件

网上很多解决方案是手动完成 WorkManager 组件的初始化,对于大多数的应用是在设备解锁后使用的是可以解决的,但是在需要在设备未解锁的状态下仍需要执行任务的情况就不适用了。

停用 WorkManager 组件自动初始化

xml 复制代码
 <provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove">
 </provider>

让你的 Application 类实现 Configuration.Provider接口,并提供您自己的 Configuration.Provider.getWorkManagerConfiguration。 当您需要使用 WorkManager 时,请务必调用 WorkManager.getInstance(Context)。 WorkManager 会调用应用的自定义 getWorkManagerConfiguration() 方法来发现其 Configuration。(无需自行调用 WorkManager.initialize。)

java 复制代码
class MyApplication extends Application implements Configuration.Provider {
    @Override
    public Configuration getWorkManagerConfiguration() {
        return new Configuration.Builder()
                .setMinimumLoggingLevel(android.util.Log.INFO)
                .build();
    }
}

官方文档:
https://developer.android.com/topic/libraries/app-startup?hl=zh-cn#manual
https://developer.android.com/develop/background-work/background-tasks/persistent/configuration/custom-configuration?hl=zh-cn

2.4 WorkManager 不支持 Direct Boot 的官方修改

Gerrit Commit:https://android-review.googlesource.com/c/platform/frameworks/support/+/1196948

markdown 复制代码
Throw an exception when trying to initialize WorkManager in direct boot mode.

* WorkManager does not support direct boot mode. Therefore when an app tries to
  initialize WorkManager in direct boot mode, we now throw an IllegalStateException.

Test: Added unit tests.
Change-Id: I549cca9aae0e8d5136a59719226c647dd3b1bb8b
java 复制代码
/**
 * Initializes an instance of {@link WorkManagerImpl}.
 *
 * @param context The application {@link Context}
 * @param configuration The {@link Configuration} configuration
 * @param workDatabase The {@link WorkDatabase} instance
 * @param schedulers The {@link List} of {@link Scheduler}s to use
 * @param processor The {@link Processor} instance
 */
private void internalInit(@NonNull Context context,
        @NonNull Configuration configuration,
        @NonNull TaskExecutor workTaskExecutor,
        @NonNull WorkDatabase workDatabase,
        @NonNull List<Scheduler> schedulers,
        @NonNull Processor processor) {

    context = context.getApplicationContext();
    mContext = context;
    mConfiguration = configuration;
    mWorkTaskExecutor = workTaskExecutor;
    mWorkDatabase = workDatabase;
    mSchedulers = schedulers;
    mProcessor = processor;
    mPreferenceUtils = new PreferenceUtils(workDatabase);
    mForceStopRunnableCompleted = false;

    // Check for direct boot mode
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && context.isDeviceProtectedStorage()) {
        throw new IllegalStateException("Cannot initialize WorkManager in direct boot mode");
    }

    // Checks for app force stops.
    mWorkTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));
}

三、解决方案

当应用会在 Direct Boot 模式下启动并完成初始化,还需要执行相关的任务,这里需要解决 2 个问题,其一是 WorkManager 的初始化,其二是访问设备加密存储,操作文件、数据库或 SharedPreferences,否则可能会出现如下报错:

bash 复制代码
09-05 16:17:43.361  1976  2111 E AndroidRuntime: Caused by: android.database.sqlite.SQLiteCantOpenDatabaseException: Cannot open database '/data/user/0/com.xxx.devicereport/no_backup/androidx.work.workdb' with flags 0x30000000: Directory /data/user/0/com.revomovil.devicereport/no_backup doesn't exist
bash 复制代码
09-04 14:47:40.445  5355  5355 E AndroidRuntime: Caused by: java.lang.IllegalStateException: SharedPreferences in credent
ial encrypted storage are not available until after user (id 0) is unlocked

因为应用是支持 Direct Boot 模式是,所以你的应用需要声明 directBootAware

xml 复制代码
<application
    android:name=".DeviceReportApplication"
    android:directBootAware="true"
    android:label="@string/app_name"
    android:persistent="true"
    android:usesCleartextTraffic="true" />

规避 WorkManager 的初始化时的 IllegalStateException: Cannot initialize WorkManager in direct boot mode,根据源码处的判断将 isDeviceProtectedStorage() 返回 false 即可

java 复制代码
public class DeviceReportApplication extends Application {

    private static final String TAG = "DeviceReportApplication";

    /**
     * default false to androidx.work.
     */
    @Override
    public boolean isDeviceProtectedStorage() {
        return false;
    }
}

因为 WokrManager 内部实现的数据库访问无法修改,所以需要全局访问设备加密存储,有 2 种方式。

可以直接在 Application 标签下声明 defaultToDeviceProtectedStorage

xml 复制代码
<application
    android:name=".DeviceReportApplication"
    android:directBootAware="true"
    android:defaultToDeviceProtectedStorage="true"
    android:label="@string/app_name"
    android:persistent="true"
    android:usesCleartextTraffic="true" />

或者在 Application 中替换全局 Context,修改 attachBaseContext

java 复制代码
public class DeviceReportApplication extends Application {

    private static final String TAG = "DeviceReportApplication";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base.createDeviceProtectedStorageContext());
    }

    /**
     * default false to androidx.work.
     */
    @Override
    public boolean isDeviceProtectedStorage() {
        return false;
    }
}

相关参考

深入了解 Jetpack WorkManager: 高效的后台任务调度

Android DirectBoot模式及其数据存储

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui