Android兼容性优化-8.0之后启动后台服务报错异常

前言
本次主要内容包括:

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 . h a n d l e M e s s a g e ( A c t i v i t y T h r e a d . j a v a : 2056 ) 2 a n d r o i d . o s . H a n d l e r . d i s p a t c h M e s s a g e ( H a n d l e r . j a v a : 106 ) 3 a n d r o i d . o s . L o o p e r . l o o p ( L o o p e r . j a v a : 192 ) 4 a n d r o i d . a p p . A c t i v i t y T h r e a d . m a i n ( A c t i v i t y T h r e a d . j a v a : 6959 ) 5 j a v a . l a n g . r e f l e c t . M e t h o d . i n v o k e ( N a t i v e M e t h o d ) 6 c o m . a n d r o i d . i n t e r n a l . o s . R u n t i m e I n i t 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 H.handleMessage(ActivityThread.java:2056)2android.os.Handler.dispatchMessage(Handler.java:106)3android.os.Looper.loop(Looper.java:192)4android.app.ActivityThread.main(ActivityThread.java:6959)5java.lang.reflect.Method.invoke(NativeMethod)6com.android.internal.os.RuntimeInitMethodAndArgsCaller.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;
1
2
3
4
5
根据这几个类型的注释,可以翻译解释:

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的部分注释如下:

  • Note: IntentService is subject to all the

  • background execution limits
  • 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.

翻译一下:

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:

1 2、在Manifest中声名Service: 1 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());
1
JobIntentService不需要关心JobIntentService的生命周期,不需要startService()方法,也就避免了开头中的crash问题,通过静态方法就可以启动,还是非常不错的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果您在运行该命令时遇到错误,请根据您的错误消息尝试以下解决方案: 1. 错误消息:E: Unable to locate package mysql-server-8.0 这意味着您的系统中没有安装MySQL服务8.0版本。请尝试使用以下命令安装MySQL服务器: sudo apt-get update sudo apt-get install mysql-server 2. 错误消息:E: Unable to locate package mysql-server-8.0 这意味着您的系统中没有安装MySQL服务8.0版本。请尝试使用以下命令安装MySQL服务器: sudo apt-get update sudo apt-get install mysql-server 3. 错误消息:Package 'mysql-server-8.0' is not installed, so not removed 这意味着MySQL服务8.0版本未安装。因此,不需要删除该软件包。如果您想删除MySQL服务器,请尝试使用以下命令: sudo apt-get remove --purge mysql-server 4. 错误消息:E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem. 这意味着在安装或卸载软件包时发生了错误,并且dpkg工具无法自动修复问题。请尝试使用以下命令修复dpkg: sudo dpkg --configure -a 然后再次运行您的原始命令。 5. 错误消息:E: Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable) 这意味着另一个进程正在使用dpkg工具,并且您无法同时运行两个dpkg进程。请等待另一个进程完成或尝试使用以下命令杀死该进程: sudo killall dpkg 然后再次运行您的原始命令。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值