文章目录

  • 一、背景
  • 二、后台的定义
  • 三、后台限制服务
  • 四、如何避免引起上述 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”