【WorkManager】無法在 Direct Boot 模式下初始化
- 一、問題描述
- 二、問題分析
- 2.1 關于 Direct Boot 模式
- 2.2 支持 Direct Boot 模式
- 2.3 手動初始化 WorkManager 組件
- 2.4 WorkManager 不支持 Direct Boot 的官方修改
- 三、解決方案
一、問題描述
在使用 WorkManager 庫來實現開機上報設備信息和定時上報設備信息時,當 Android 設備設置屏幕鎖定密碼并未解鎖時,此時 WorkManager 會無法初始化導致異常無法執行任務。
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 模式
關于設備開啟重啟后的幾個廣播
// 無論直接啟動支持如何,在啟動時立即發送
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";
查詢用戶是否已解鎖設備的方法
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
if (userManager != null) {boolean isUserUnlocked = userManager.isUserUnlocked();Log.d(TAG, "Application isUserUnlocked:" + isUserUnlocked);
}
訪問設備加密存儲空間的方法,通過此上下文 createDeviceProtectedStorageContext()
發出的所有存儲 API 調用均訪問設備加密存儲空間,包括文件
、數據庫
和 SharedPreferences
。
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 組件自動初始化
<providerandroid: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
。)
class MyApplication extends Application implements Configuration.Provider {@Overridepublic 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
Throw an exception when trying to initialize WorkManager in direct boot mode.* WorkManager does not support direct boot mode. Therefore when an app tries toinitialize WorkManager in direct boot mode, we now throw an IllegalStateException.Test: Added unit tests.
Change-Id: I549cca9aae0e8d5136a59719226c647dd3b1bb8b
/*** 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 modeif (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,否則可能會出現如下報錯:
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
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
<applicationandroid: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 即可
public class DeviceReportApplication extends Application {private static final String TAG = "DeviceReportApplication";/*** default false to androidx.work.*/@Overridepublic boolean isDeviceProtectedStorage() {return false;}
}
因為 WokrManager 內部實現的數據庫訪問無法修改,所以需要全局訪問設備加密存儲,有 2 種方式。
可以直接在 Application 標簽下聲明 defaultToDeviceProtectedStorage
<applicationandroid:name=".DeviceReportApplication"android:directBootAware="true"android:defaultToDeviceProtectedStorage="true"android:label="@string/app_name"android:persistent="true"android:usesCleartextTraffic="true" />
或者在 Application 中替換全局 Context,修改 attachBaseContext
public class DeviceReportApplication extends Application {private static final String TAG = "DeviceReportApplication";@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base.createDeviceProtectedStorageContext());}/*** default false to androidx.work.*/@Overridepublic boolean isDeviceProtectedStorage() {return false;}
}
相關參考
深入了解 Jetpack WorkManager: 高效的后臺任務調度
Android DirectBoot模式及其數據存儲