问题Not allowed to start service Intent XXX : app is in background uid UidRecord
项目很早targetSdkVersion升到了28(Android 9.0)P,一直忙业务,最近查看了下后台报错信息,发现几个崩溃次数比较多的问题,共同点都是项目中的后台service,报错信息如下
Not allowed to start service Intent XXX : app is in background uid UidRecord
这个报错是8.0做的限制,不允许其创建的后台服务使用startService()函数,该函数会出现IllegalStateException错误;查看官方如下
问题分析
问题复现,首先是app处于后台发起的starService,如果app处于前台期间starService不会报错。于是先简单写个demo,在activity点击返回键时,在onDestory中去starService,但是点击之后并没有出现错误。
后来我开启服务之后,置于后台一段时间后报了同样异常。于是我就在onDestory中加了一个延时测试,延时5s,也没有出现,让人头大,直到把延时加到60s,发现报了异常,Not allowed to start service Intent XXX : app is in background uid UidRecord。
解决方法
官网提示也说了只需要判断下系统为8.0+的情况下,用startForegroundService开启一个前台服务。而且需要在service里面添加移除的通知的方法。
//9.0之后要求加入权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
Intent intent = new Intent(MainActivity.this,MyIntentService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
MainActivity.this.startForegroundService(intent);
} else {
MainActivity.this.startService(intent);
}
public static final String CHANNEL_ID_STRING = "service_02";
public static final String CHANNEL_ID_NAME = "亮屏";
public static final int NOTIFICATION_ID = 2;
void startForeground() {
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager notificationManager = (NotificationManager) getSystemService(ns);
NotificationChannel mChannel = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mChannel = new NotificationChannel(CHANNEL_ID_STRING, CHANNEL_ID_NAME,
NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(mChannel);
Notification notification = new Notification.Builder(getApplicationContext(), CHANNEL_ID_STRING).build();
startForeground(NOTIFICATION_ID, notification);
}
}
Context.startForegroundService() did not then call Service.startForeground()?
需要注意的是service创建5s后必须调用startForeground,否则报Context.startForegroundService() did not then call Service.startForeground()
我的处理是在service中onCreat和onDestory中成对的出现startForeground和stopForeground(true),并且在启动service中会判断该service是否处于run的状态,避免多次启动已经存在service的。
public static boolean isRunningService(Context context, Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
Log.i(TAG, true + "");
return true;
}
}
Log.i(TAG, false + "");
return false;
}
如何关闭通知?
startService开启的服务,要通过stopService关闭服务;bindService的服务需要通过unbindService(serviceConnection)关闭服务;如果service既startService开启又bindService,这时候就需要同事调用stopService又要调用unbindService(serviceConnection)。如果不这样做,在app中使用开关,关闭状态栏的通知不能实时展示或者消失。
思考
问题解决后,回想下,之所以线上没有反馈,是因为这个service是后台执行,用户是不可见的,而且报错的情况,也是部分机型,停留一段时间造成anr或者crash。用户看到的最坏结果就是,将app置于后台一段时间,重新再开app,不再是热启而是冷启。
XXX正在运行,如何隐藏通知栏?
这样处理之后的确不会出现Not allowed to start service,但前台状态栏会有一个常驻通知(XXX正在运行),清理通知,也不会被清理掉,除非杀死App。这样用户体验度上会特别差,更容易有杀App的冲动。那么新问题来了,XXX正在运行,是否可以隐藏这条消息?
一番折腾之后,在8.0+上隐藏并没有合适的方法。而且谷歌的目的就是为了让开发者不能瞒着用户在后台做一些耗电耗内存任务,如果做也可以就是发通知到状态栏,让用户能看到。至于用户看到会不会烦,就看产品设计了,例如百度地图通知栏上提示正在导航,keep通知栏上提示keep正在记录你的运动,如果用户还是杀手app就证明就是不想用app,也没有办法。于是,我就把前台服务要干什么老老实实告诉用户即可,并且我在app中提供了,用户可关闭常驻通知的开关。
public static final String CHANNEL_ID_STRING = "service_02";
public static final String CHANNEL_ID_NAME = "亮屏";
public static final int NOTIFICATION_ID = 2;
void startForeground() {
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager notificationManager = (NotificationManager) getSystemService(ns);
NotificationChannel mChannel = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mChannel = new NotificationChannel(CHANNEL_ID_STRING, CHANNEL_ID_NAME,
NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(mChannel);
Notification notification =new NotificationCompat.Builder(getApplicationContext(),CHANNEL_ID_STRING)
.setContentTitle("亮屏开门")
.setContentText("更方便您开门")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.icon)
.build();
startForeground(NOTIFICATION_ID, notification);
}
}