【安卓】Android兼容性优化-8.0之后禁止在后台启动服务的兼容性优化

前言

一、Android8.0之后IntentService启动异常跟踪

项目中在做启动优化时,在Application 通过IntentService启动第三方组件时,bugly时常会上报如下问题:

*android.app.RemoteServiceException
Context.startForegroundService() did not then call Service.startForeground()*

# main(2)

android.app.RemoteServiceException

Context.startForegroundService() did not then call Service.startForeground()

1 android.app.ActivityThread$H.handleMessage(ActivityThread.java:2056)

2 android.os.Handler.dispatchMessage(Handler.java:106)

3 android.os.Looper.loop(Looper.java:192)

4 android.app.ActivityThread.main(ActivityThread.java:6959)

5 java.lang.reflect.Method.invoke(Native Method)

6 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:557)

7 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:875)

1、Service和IntentService使用场景以及区别

Service的使用场景:

IntentService特点

==Android 8.0新增了startForegroundService方法,用于启动前台服务,前台服务是指带有通知栏的服务,如果我们使用startForegroundService启动服务,那么必须在5秒内调用startForeground()显示一个通知栏,否则就会报错==

2、明明调用startforeground了为什么还会报Context.startForegroundService() did not then call Service.startForeground()

首先看下启动IntentService的调用:

private void startInitService() {

Intent intent = new Intent(this, InitIntentService.class);

intent.setAction("com.pxwx.student.action");

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

startForegroundService(intent);

} else {

startService(intent);

}

}

在IntentService中的处理:

在onCreate方法中调用了startForeground方法

public class InitIntentService extends IntentService {

public InitIntentService() {

super("InitIntentService");

}

@RequiresApi(api = Build.VERSION_CODES.O)

private Notification getNotification() {

NotificationChannel channel = new NotificationChannel("init", "", NotificationManager.IMPORTANCE_LOW);

NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

if (manager != null) {

manager.createNotificationChannel(channel);

}

return new Notification.Builder(this, "init")

.setContentTitle("")

.setContentText("")

.setAutoCancel(true)

.setSmallIcon(com.pxwx.student.core.R.mipmap.small_icon)

.build();

}

@Override

public void onCreate() {

super.onCreate();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

//notification ID must not be 0

startForeground(1,getNotification());

}

}

@Override

protected void onHandleIntent(@Nullable Intent intent) {

initThirdSDK();

}

onCreate方法中明明调用startforeground了为什么还会报Context.startForegroundService() did not then call Service.startForeground()?

分析:

1、启动IntentService服务在Application中执行了多次,IntentService在第二次启动时还未停止的话不知执行onCreate方法,但会走onStart()方法,所以在onStart()方法中也执行startforeground

@Override

public void onStart(@Nullable Intent intent, int startId) {

super.onStart(intent, startId);

//主要是针对后台保活的服务,如果在服务A运行期间,保活机制又startForegroundService启动了一次服务A,那么这样不会调用服务A的onCreate方法,只会调用onStart方法

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

startForeground(1,getNotification());

}

}

2、但是问题依旧存在,只是减少了该问题的发生,然后我又尝试了讲starttForeground方法放在了onHandleIntent中执行

@Override

protected void onHandleIntent(@Nullable Intent intent) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

startForeground(1,getNotification());

}

initThirdSDK();

}

3、但是问题依旧存在,只是又减少了该问题的发生,不能完全杜绝该问题的发生,具体分析下IntentService的特性:

3、 IntentService源码分析

1、首先看下IntentService的继承关系等声明信息:

public abstract class IntentService extends Service {

...

@WorkerThread

protected abstract void onHandleIntent(@Nullable Intent intent);

}

可以看出IntentService是继承自Service的抽象类,有个抽象方法onHandleIntent需要子类覆写,通过注解我们知道该方法的执行是在子线程中的。

2、其次看下IntentService中声明的字段

//volatile修饰,保证其可见性

//Service中子线程中的Looper对象

private volatile Looper mServiceLooper;

//与子线程中Looper关联的Hander对象

private volatile ServiceHandler mServiceHandler;

//与子线程HandlerThread相关的一个标识

private String mName;

//设置Service的标志位,根据它的值来设置onStartCommand的返回值

private boolean mRedelivery;

/**

* You should not override this method for your IntentService. Instead,

* override {@link #onHandleIntent}, which the system calls when the IntentService

* receives a start request.

* @see android.app.Service#onStartCommand

*/

@Override

public int onStartCommand(@Nullable Intent intent, int flags, int startId) {

onStart(intent, startId);

return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;

}

==mRedelivery是来处理onStartCommand返回值的一个标志位参数,着重看下onStartCommand的返回值在Service中定义的几个类型:==

public static final int START_CONTINUATION_MASK = 0xf;

public static final int START_STICKY_COMPATIBILITY = 0;

public static final int START_STICKY = 1;

public static final int START_NOT_STICKY = 2;

public static final int START_REDELIVER_INTENT = 3;

根据这几个类型的注释,可以翻译解释:

3、上边关于mRedelivery值控制onStartCommand的返回值的问题:

4、总结分析:

==从IntentService的源码分析看,导致android.app.RemoteServiceException Context.startForegroundService() did not then call Service.startForeground():异常发生的原因:==

二、JobIntentService替代IntentService方案

综合上面的分析,没有能完全解决上面的异常情况,该如何解决呢?

通过IntentService源码中针对IntentService的部分注释如下:

 * <p class="note"><b>Note:</b> IntentService is subject to all the

* <a href="https://segmentfault.com/preview/features/background.html">background execution limits</a>

* imposed with Android 8.0 (API level 26). In most cases, you are better off

* using {@link android.support.v4.app.JobIntentService}, which uses jobs

* instead of services when running on Android 8.0 or higher.

* </p>

翻译一下:

1、JobIntentService介绍

JobIntentService是Android 8.0 新加入的类,它也是继承自Service,根据官方的解释:

大概翻译一下:

2、JobIntentService使用

1、在Manifest中声名Permission:

<uses-permission android:name="android.permission.WAKE_LOCK" />

2、在Manifest中声名Service:

<service android:name=".InitIntentService" android:permission="android.permission.BIND_JOB_SERVICE" />

3、实现JobIntentService类:

public class InitIntentService extends JobIntentService {

public static final int JOB_ID = 1;

public static void enqueueWork(Context context, Intent work) {

enqueueWork(context, InitIntentService.class, JOB_ID, work);

}

@Override

protected void onHandleWork(@NonNull Intent intent) {

// 具体逻辑

}

}

4、调用启动实现JobIntentService

InitIntentService.enqueueWork(context, new Intent());

JobIntentService不需要关心JobIntentService的生命周期,不需要startService()方法,也就避免了开头中的crash问题,通过静态方法就可以启动,还是非常不错的。


关注我的技术公众号

【安卓】Android兼容性优化-8.0之后禁止在后台启动服务的兼容性优化

以上是 【安卓】Android兼容性优化-8.0之后禁止在后台启动服务的兼容性优化 的全部内容, 来源链接: utcz.com/a/104121.html

回到顶部