处理 Android 8.0 及以上手机 Not allowed to start service Intent app is in background 的问题

一、背景

每次在后台运行时,应用都会消耗一部分有限的设备资源,例如 RAM。 这可能会影响用户体验,如果用户正在使用占用大量资源的应用(例如玩游戏或观看视频),影响尤为明显。
为了提升用户体验,Android 8.0 对应用在后台运行时可以执行的操作施加了限制。
其中,Not allowed to start service Intent app is in background 是后台服务限制引起的。[1]

二、后台的定义

在后台中运行的服务会消耗设备资源,这可能降低用户体验。 为了缓解这一问题,系统对这些服务施加了一些限制。

系统可以区分 前台 和 后台 应用。如果满足以下任意条件,应用将被视为处于前台:

  • 具有可见 Activity(不管该 Activity 已启动还是已暂停)
  • 具有前台服务
  • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的服务,那么该应用处于前台:
    • IME
    • 壁纸服务
    • 通知侦听器
    • 语音或文本服务

如果以上条件均不满足,应用将被视为处于后台。[1]

三、后台限制服务

处于前台时,应用可以自由创建和运行前台服务与后台服务。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用服务。
在该时间窗结束后,应用将被视为处于 空闲(idle) 状态。 此时,系统将停止应用的后台服务,就像应用已经调用服务的“Service.stopSelf()”方法。[1]

注:将应用不显示在前台,然后调用如下命令,直接把应用的状态由 active 至 idle。[2]

adb shell am make-uid-idle <packagename>

四、如何避免引起上述 Not allowed to start service Intent app is in background

Android 8.0 引入了一种全新的方法,即 Context.startForegroundService()
在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground() 方法以显示新服务的用户可见通知。
如果应用在此时间限制内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR

  • 启用 service: [3]
if (VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            ctx.startForegroundService(getVSPushService(ctx));
        } else {
            ctx.startService(getVSPushService(ctx));
        }
  • 修改原先的 service 的 onCreate 方法
public void onCreate() {
    super.onCreate();
	if (Build.VERSION.SDK_INT >=    Build.VERSION_CODES.O) {
                String NOTIFICATION_CHANNEL_ID = "package_name";
                String channelName = "My Background Service";
                NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,channelName, NotificationManager.IMPORTANCE_LOW);
                NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 
                manager.createNotificationChannel(channel);
        Notification notification = new Notification.Builder(this,NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_launcher)  // the status icon
                .setWhen(System.currentTimeMillis())  // the time stamp
                .setContentText("IM服务正在运行")  // the contents of the entry
                .build();

            startForeground(2, notification);
        }
}

五、变通?

比如我就是想在后台启用 service, Android 8.0 及以上只能通过 startForegroundService 的方式进行启动,这样会在通知栏常驻一个通知。那么我是否可以调用 startForeground() 之后立马调用 stopForeground 来消除改通知?
是的,是可以做到上述效果,但是之后会出现什么呢?

02-13 20:03:39.968  1307  1326 W ActivityManager: Stopping service due to app idle: u0a404 -1m0s113ms packageName/ServiceName

观察日志,会发现出现上述的内容,也就是在1分钟后,该Service 依旧会被结束。所以这种方式行不通。

参考文献

[1] : https://developer.android.com/about/versions/oreo/background “Background Execution Limits”
[2] : https://xiaozhuanlan.com/topic/2487365091 “从源码角度看 Android O AMS 新特性”
[3] : https://github.com/zengjingfang/AndroidBox/issues/23 “Android8.0系统:.RemoteServiceException: Bad notification for startForeground”

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页