乐趣区

关于android:Android兼容性优化80之后禁止在后台启动服务的兼容性优化

前言

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

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.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 的应用场景:

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

@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 的个性:

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

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

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 问题,通过静态方法就能够启动,还是十分不错的。


关注我的技术公众号

退出移动版