Android(安卓)8.0 启动Service适配(Not allowed to start service Intent)
16lz
2021-01-26
问题现象:
App出现异常: java.lang.IllegalStateException: Not allowed to start service Intent xxxx app is in background uid UidRecord
App直接崩溃。
问题原因:
App targetSdkVersion>= 26的情况下,用户允许App开机自启动,App被杀死或者系统重启后,系统直接将App后台启动起来,App在后台启动的过程中有使用startService()方法。
Google在Android 8.0之后对于处于后台的App启动Service进行了严格的限制,不再允许后台App启动后台Service,如果使用会直接抛出异常。
问题调研:
- 微信:反编译App后,没有找到对应Service的代码,怀疑可能是热加载的
- QQ:目前targetSdkVersion = 23,不存在此问题
- 小天才:使用startForegroundService方法,Notification设置channel为null,状态栏不显示通知
- 360儿童卫士:使用startForegroundService方法,Notification正常设置,状态栏显示通知
解决方法:
使用startForegroundService,此方法会在状态栏显示通知
// 启动服务的地方if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(new Intent(context, MyService.class));} else { context.startService(new Intent(context, MyService.class));}
// 在MyService中@Overridepublic void onCreate() { super.onCreate(); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); NotificationChannel channel = null; channel = new NotificationChannel(CHANNEL_ID_STRING, getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH); notificationManager.createNotificationChannel(channel); Notification notification = new Notification.Builder(getApplicationContext(), CHANNEL_ID_STRING).build(); startForeground(1, notification); }}
去除状态栏方法
stopForeground()
//在 startForeground(1, notification);后添加如下代码,取消前台服务myHandler.postDelayed(new Runnable() { @Override public void run() { stopForeground(true); }}, 1000);
channel 设置为null,此方法只适用于targetSdkVersion = 26的情况
Notification.Builder builder = new Notification.Builder(this, null).setContentTitle(getString(R.string.app_name)).setContentText("").setAutoCancel(true);Notification notification = builder.build();startForeground(1, notification);
对于去除通知的第2种方法,源码查看:
final class ServiceRecord extends Binder implements ComponentName.WithComponentName {public void postNotification() { if (foregroundId != 0 && foregroundNoti != null) { ... ams.mHandler.post(new Runnable() { public void run() { NotificationManagerInternal nm = LocalServices.getService(NotificationManagerInternal.class); if (nm == null) { return; } Notification localForegroundNoti = _foregroundNoti; try { if (localForegroundNoti.getSmallIcon() == null) { ...//没有smallIcon进行处理 } if (nm.getNotificationChannel(localPackageName, appUid, localForegroundNoti.getChannelId()) == null) { int targetSdkVersion = Build.VERSION_CODES.O_MR1; try { final ApplicationInfo applicationInfo = ams.mContext.getPackageManager().getApplicationInfoAsUser(appInfo.packageName, 0, userId); targetSdkVersion = applicationInfo.targetSdkVersion; } catch (PackageManager.NameNotFoundException e) { } if (targetSdkVersion >= Build.VERSION_CODES.O_MR1) { throw new RuntimeException("invalid channel for service notification: " + foregroundNoti); } } nm.enqueueNotification(localPackageName, localPackageName, appUid, appPid, null, localForegroundId, localForegroundNoti, userId); foregroundNoti = localForegroundNoti; // save it for amending next time } catch (RuntimeException e) { Slog.w(TAG, "Error showing notification for service", e); // If it gave us a garbage notification, it doesn't // get to be foreground. ams.setServiceForeground(name, ServiceRecord.this, 0, null, 0); ams.crashApplication(appUid, appPid, localPackageName, -1, "Bad notification for startForeground: " + e); } } }); }}}
从源码中可以看出,在发送通知检查时,如果targetSdkVersion >= Build.VERSION_CODES.O_MR1即targetSdkVersion >= 27时,如果设置channel为null才会抛出RuntimeException,crash当前App。
对于targetSdkVersion = 26的app会捕获异常,正常将notification加入队列,但此notification并不会进行显示。
更多相关文章
- 【Android】 Activity Lifecycle
- Android(安卓)RecyclerView 二级列表实现
- Android-NDK开发之基础--Android(安卓)JNI实例代码(三)-- 在JNI
- android绑定点击事件的四种方法
- Android中关于数据库SQLite的insert插入操作的理解
- android 自动化测试方法
- Android(安卓)启动过程
- Android(安卓)8.0 SystemUI(一)
- ListView的两种使用方法1.继承ListActivity2.自己定义ListView