前言

==本次次要内容包含:==

1、Android8.0之后IntentService启动异样跟踪

2、JobIntentService代替IntentService计划

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

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

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

# main(2)android.app.RemoteServiceExceptionContext.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的应用场景:

a、Service是运行在主线程的,如果咱们须要执行耗时操作,也是在Service创立Thread执行。

b、如果耗时操作无奈在以后Activity生命周期执行实现的工作就须要在Service执行,比方异步下载等。

c、须要长时间在后盾运行,追随APP生命周期的工作须要在Service执行,比方Socket长连贯。

d、Service是所有服务的基类,咱们通常都是继承该类实现服务,如果应用该类,咱们须要对Service的生命周期进行治理,在适合的中央进行Service。

IntentService特点

a、IntentService继承于Service,通过源码能够看到,是在Service的根底上减少了Handler、Looper、HandlerThread的反对;

b、只须要重写onHandleIntent(Intentintent)实现异步工作,这个办法曾经是非UI线程,能够执行耗时操作;

c、一旦这个办法执行结束,就会立即执行stopSelf()进行服务,无需手动进行服务。

==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

@Overridepublic 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中执行

@Overrideprotected void onHandleIntent(@Nullable Intent intent) {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {        startForeground(1,getNotification());    }    initThirdSDK();}

3、然而问题仍旧存在,只是又缩小了该问题的产生,不能齐全杜绝该问题的产生,具体分析下IntentService的个性:

a、IntentService如果第一次启动后,onhandleIntent没解决完,持续startService,不会再从新实例化这个Service了,而是将申请放到申请队列里,期待第一个解决完再解决第二个。这种状况下,只有一个线程在运行

b、、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 */@Overridepublic 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;

依据这几个类型的正文,能够翻译解释:

START_STICKY_COMPATIBILITY:兼容模式,如果Service在创立后,被零碎杀死,此时不能保障onStartCommand办法会被执行(可能会被执行,也可能不会被执行);

START_STICKY:如果Service过程被kill掉,保留Service的状态为开始状态,但不保留递送的intent对象。随后零碎会尝试从新创立Service,因为服务状态为开始状态,所以创立服务后肯定会调用onStartCommand(Intent,int,int)办法。如果在此期间没有任何启动命令被传递到Service,那么参数Intent将为null;

START_NOT_STICKY:如果Service在启动后(从onStartCommand返回了)被零碎杀掉了,在下一次调用Context.startService()之前,不会再创立Service。期间,也不承受空Intent参数的onStartCommand办法调用,因为空的Intent无奈进行Service的创立;

START_REDELIVER_INTENT:在Service启动后,被零碎杀掉了,将会重传最近传入的Intent到onStartCommand办法中对Service进行重建;

3、上边对于mRedelivery值管制onStartCommand的返回值的问题:

1、如果为true,则返回START_REDELIVER_INTENT,示意如果Service被零碎杀死,能够进行重建并重传最近传入的Intent;

2、如果为false,则返回START_NOT_STICKY,示意如果Service被零碎杀死,除非再次调用Context.startService(),不会对Servcie进行重建;

3、在Service被销毁的时候,会进行子线程的音讯队列;

4、ntentService中还有一个设置mRedelivery的setter办法

4、总结剖析:

==从IntentService的源码剖析看,导致android.app.RemoteServiceException Context.startForegroundService() did not then call Service.startForeground():异样产生的起因:==

1、onStartCommand返回值的一个标记位参数默认是START_NOT_STICKY

2、如果Service在启动后(从onStartCommand返回了)被零碎杀掉了,在下一次调用Context.startService()之前,不会再创立Service。期间,也不承受空Intent参数的onStartCommand办法调用,因为空的Intent无奈进行Service的创立;

3、导致Context.startForegroundService() did not then call Service.startForeground()的起因可能是被零碎杀掉了,未执行IntentService的onCreate、onStart、onHandleIntent办法中的startForeground办法

二、JobIntentService代替IntentService计划

综合下面的剖析,没有能齐全解决下面的异常情况,该如何解决呢?

通过IntentService源码中针对IntentService的局部正文如下:

 * <p class="note"><b>Note:</b> IntentService is subject to all the * <a href="/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>

翻译一下:

IntentService受Android 8.0(API级别26)的所有后盾执行限度的束缚。在大多数状况下,在Android 8.0或更高版本上运行时您最好应用android.support.v4.app.JobIntentService而不是服务。
1、JobIntentService介绍

JobIntentService是Android 8.0 新退出的类,它也是继承自Service,依据官网的解释:

Helper for processing work that has been enqueued for a job/service. When running on Android O or later, the work will be dispatched as a job via JobScheduler.enqueue. When running on older versions of the platform, it will use Context.startService.

大略翻译一下:

JobIntentService用于执行退出到队列中的工作。对Android 8.0及以上的零碎,JobIntentService的工作将被散发到JobScheduler.enqueue执行,对于8.0以下的零碎,工作仍旧会应用Context.startService执行。
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问题,通过静态方法就能够启动,还是十分不错的。


关注我的技术公众号