Android 14拒绝位置权限时App崩溃问题解决方案咨询
问题场景
应用升级至targetSdkVersion 34后,当用户拒绝位置权限时会触发崩溃。该应用使用WorkManager每20秒向服务器更新位置,崩溃触发于:应用从后台切换至前台,且用户已在系统设置中拒绝位置权限。
崩溃堆栈信息
Fatal Exception: java.lang.SecurityException: Starting FGS with type location callerApp=ProcessRecord{7499c86 16252:com.prasko.ovdriver/u0a304} targetSDK=34 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_LOCATION] any of the permissions allOf=false [android.permission.ACCESS_COARSE_LOCATION, android.permission.ACCESS_FINE_LOCATION] and the app must be in the eligible state/exemptions to access the foreground only permission
at android.os.Parcel.createExceptionOrNull(Parcel.java:3087)
at android.os.Parcel.createException(Parcel.java:3071)
at android.os.Parcel.readException(Parcel.java:3054)
at android.os.Parcel.readException(Parcel.java:2996)
at android.app.IActivityManager$Stub$Proxy.setServiceForeground(IActivityManager.java:6761)
at android.app.Service.startForeground(Service.java:862)
at androidx.work.impl.foreground.SystemForegroundService$Api31Impl.startForeground(SystemForegroundService.java:193)
at androidx.work.impl.foreground.SystemForegroundService$1.run(SystemForegroundService.java:129)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:257)
at android.os.Looper.loop(Looper.java:368)
at android.app.ActivityThread.main(ActivityThread.java:8839)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:572)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)
Manifest权限配置
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /> <service android:name="androidx.work.impl.foreground.SystemForegroundService" android:foregroundServiceType="location|dataSync" tools:ignore="Instantiatable" tools:node="merge" />
核心原因
Android 14对前台服务(FGS)的权限校验更严格:当前台服务类型包含location时,必须同时持有至少一个位置权限(ACCESS_FINE/COARSE_LOCATION),否则调用startForeground()会直接抛出SecurityException。仅在启动WorkManager前检查权限无法覆盖用户后续通过系统设置撤销权限的场景,导致WorkManager触发前台服务时权限缺失。
解决方案
1. Worker执行前动态校验权限
在Worker的doWork()方法开头再次检查位置权限,若权限缺失直接终止任务,避免触发前台服务:
public class LocationUpdateWorker extends Worker { public LocationUpdateWorker(@NonNull Context context, @NonNull WorkerParameters params) { super(context, params); } @NonNull @Override public Result doWork() { // 检查是否持有位置权限 boolean hasLocationPerm = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED; if (!hasLocationPerm) { // 权限缺失,直接返回失败 return Result.failure(); } // 执行位置更新逻辑 updateLocationToServer(); return Result.success(); } }
2. 动态调整前台服务类型
根据当前权限状态,在构建ForegroundInfo时动态修改前台服务类型:
- 有位置权限时,使用
location|dataSync类型 - 无位置权限时,仅使用
dataSync类型
@Override @NonNull public ForegroundInfo getForegroundInfo() { boolean hasLocationPerm = ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED; int foregroundType = hasLocationPerm ? (ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION | ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) : ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC; Notification notification = createLocationUpdateNotification(); // 自定义通知逻辑 return new ForegroundInfo(LOCATION_NOTIFICATION_ID, notification, foregroundType); }
3. 全局捕获SecurityException兜底
在Application中设置全局异常捕获,针对前台服务权限异常做兜底处理,避免App崩溃:
public class App extends Application { @Override public void onCreate() { super.onCreate(); Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { if (throwable instanceof SecurityException) { String msg = throwable.getMessage(); if (msg != null && msg.contains("FOREGROUND_SERVICE_LOCATION")) { // 停止相关WorkManager任务 WorkManager.getInstance(this).cancelAllWorkByTag("LOCATION_UPDATE_TAG"); // 可选:引导用户开启权限 showPermissionGuide(); return; } } // 其他异常按原有逻辑处理 handleOtherExceptions(throwable); }); } }
4. 优化Manifest配置
Android 14中FOREGROUND_SERVICE权限已废弃,移除该声明,保留细分的前台服务权限即可:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /> <service android:name="androidx.work.impl.foreground.SystemForegroundService" android:foregroundServiceType="location|dataSync" tools:ignore="Instantiatable" tools:node="merge" />
内容的提问来源于stack exchange,提问作者nitin kadukar

