本文主要从以下方面展开讨论

  •  1. onStartCommand返回值探究
  •  2. Runging a service in foreground前台服务相关
  •  3. 由前台服务联想到的怎么样让服务更长久的在后台运行而不被系统给干掉

正文

- onStartCommand()方法的返回值

1.返回值

 当其它组件(下文默认都是由Activity开启服务)调用startService()的开启服务的时候,最终会调用到服务里重写的onStartCommand方法

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: 服务开启咯");
        return super.onStartCommand(intent, flags, startId);
    }

由自己定义的服务类里面,可以看出该方法返回一个int类型的值,默认返回了父类方法的onStartCommand()的返回值,然后紧接着查看其父类的onStartCommand()方法,如下:

/* @return The return value indicates what semantics the system should
     * use for the service's current started state.  It may be one of the
     * constants associated with the {@link #START_CONTINUATION_MASK} bits.
     * 
     * @see #stopSelfResult(int)
     */
    public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
        onStart(intent, startId);
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
    }

START_STICKY_COMPATIBILITY,否则返回START_STICKY。此时看到这,懵逼的我还是一脸懵逼不知道兼容启动模式是什么鬼,这两个返回值又是什么鬼。上面官方给的方法注释里给出的解释。也看的云里雾里,甚至这段方法注释也翻译不出来个啥鬼。总之是抓瞎。  于是乎又去官方看了看API,不懂的地方又网上找博文各种看。直接结果总结如下:
onStartCommand()方法有四个返回值,分别是:

  1. START_STICKY
  2. START_NOT_STICKY
  3. START_REDELIVER_INTENT
  4. START_STICKY_COMPATIBILITY 实践出真知,还是敲代码测试印证官方API给的解释(后面会给出,先通过代码印证,这样学习记忆才够深刻)
先指定START_STICKY为返回值
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: intent = " + intent);
        Log.i(TAG, "onStartCommand: startId = " + startId);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy: ");
        super.onDestroy();
    }

点击开启服务按钮startService();log如下: 可以看到服务成功开启了

02-08 07:01:04.705 13967-13967/com.example.yanchunguo.bindservice I/LocalService: onCreate:服务创建成功
02-08 07:01:04.706 13967-13967/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: intent = Intent { cmp=com.example.yanchunguo.bindservice/.LocalService }
02-08 07:01:04.706 13967-13967/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: startId = 1

然后直接杀死当前进程(如下图操作,可以杀死当前进程)   

Android8 start 后台服务_Android8 start 后台服务

Android8 start 后台服务_应用程序_02

然后看到的log如下:

02-08 07:11:09.030 23489-23489/? I/LocalService: onCreate:服务创建成功
02-08 07:11:09.030 23489-23489/? I/LocalService: onStartCommand: intent = null
02-08 07:11:09.030 23489-23489/? I/LocalService: onStartCommand: startId = 2
  • 实践出真知
  1. 由log可以看出,程序被异常杀死之后,服务被重启了——因为log中重新调用了onCreate()方法。
  2. 但是intent对象却被清除了,因为log中打印出的intent = null
    这两点就印证了官方给出的关于START_STICKY的解释:

如果onStartCommand指定了返回值为START_STICKY,如果在onStartCommand()返回之后服务所在的进程(service's precess)被系统杀死了也就是应用程序被杀死了,但是服务没被杀死(从log中可以看出,因为onDestroy()方法没被调用),那么服务将会保留在started state(官方给出的是这个英语描述,我暂且翻译成开启状态).但是不会保存(retain )之前传递过来的intent(如log中所见,intent = null).随后,系统会尝试重建(re-create)这个服务(log中又调用了onCreate这个方法,可以验证这一点),然后调用onStartCommand()方法。如果期间没有其他的启动命令——比如其它组件调用startService(),onStartCommand()方法第一个参数intent就是null

  • START_STICKY
     一句话概括,就是,指定了该值作为方法返回值,如果服务被异常杀死了。系统会尝试重建服务并启动,如果没有新的组件显示调用startService(),onStartCommand第一个参数intent将会为null;
现在指定为START_NOT_STICKY为返回值
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: intent = " + intent);
        Log.i(TAG, "onStartCommand: startId = " + startId);
        return START_NOT_STICKY;
    }

调用startService()开启服务,log:

02-08 07:47:53.825 23593-23593/com.example.yanchunguo.bindservice I/LocalService: onCreate:服务创建成功
02-08 07:47:53.825 23593-23593/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: intent = Intent { cmp=com.example.yanchunguo.bindservice/.LocalService }
02-08 07:47:53.825 23593-23593/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: startId = 1

然后重复上面途中杀死进程的方法,会发现没有log输出。所以得知

  • 如果START_NOT_STICKY做为返回值,在服务所在的进程被杀死之后,也就是应用程序被杀死之后。系统不会在去重新创建(re-create)你的服务。除非在此调用startService()
在来试试这个START_REDELIVER_INTENT返回值

代码不在贴出,。正常第一次启动log如下

02-08 08:01:17.463 1260-1260/com.example.yanchunguo.bindservice I/LocalService: onCreate:服务创建成功
02-08 08:01:17.464 1260-1260/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: intent = Intent { cmp=com.example.yanchunguo.bindservice/.LocalService }
02-08 08:01:17.464 1260-1260/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: startId = 1

然后在异常杀死该应用程序,看log,(这个log信息间隔了几秒出现)

02-08 08:04:44.785 4088-4088/? I/LocalService: onCreate:服务创建成功
02-08 08:04:44.785 4088-4088/? I/LocalService: onStartCommand: intent = Intent { cmp=com.example.yanchunguo.bindservice/.LocalService }
02-08 08:04:44.785 4088-4088/? I/LocalService: onStartCommand: startId = 1
  • 从log中可以看出,START_REDELIVER_INTENT作为返回值和START_STICKY打印出来的log最重要不同的一点是intent 不为null,从而得出系统在进程被杀死的情况下,也会重新创建服务,并且保留了最后一次调用startService()传过来的intent,这适用于那些应该立即恢复正在执行的工作的服务,如下载文件。
最后看看START_STICKY_COMPATIBILITY这个作为返回情况

直接上异常关闭进程之后的log,因为正常情况下log和上面一样

02-08 08:13:09.684 11077-11077/com.example.yanchunguo.bindservice I/LocalService: onCreate:服务创建成功
02-08 08:13:09.684 11077-11077/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: intent = null
02-08 08:13:09.684 11077-11077/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: startId = 4

由log上看出,这个START_STICKY_COMPATIBILITY的log和START_STICKY一样。但是官方给的解释是

START_STICKY_COMPATIBILITY模式,在程序被异常杀死之后,会服务会重建(re-create),单不保证一定会调用onStartCommand,而START_STICKY则一定会调用onStartCommand();

返回值总结

  1. 四个返回值都是处理服务所在的进程被异常杀死后,系统如何对服务如何处理的
  2. 其中START_NOT_STICKY,在进程被杀死后,系统不会重新创建服务,其他三个会重新创建服务
  3. START_STICKY,系统不保存intent的状态,也就是intent为null,而且系统会保证调用onStartCommand()方法
  4. START_REDELIVER_INTENT,系统保存最近依次传递给onStartCommand()的intent,而且系统也会保证调用onStartCommand()方法
  5. START_STICKY_COMPATIBILITY,这个系统也会创建服务,单不保证一定会调用onStartCommand()方法

onStartCommand()参数研究

onStartCommand有三个参数,intent,flags,startId,见下图(markdown用的不熟,表格不知道怎么调整)

参数

解释

intent

通过其他组件通过调用startService(intent)开启服务的时候传入

flags

额外启动服务的数据,正常情况下是0,其它情况下为START_FLAG_REDELIVERY(值为1) START_FLAG_RETRY(值为2).

startId

传给stopSelfResult(int startId)用

这里重点讲述下flags这个参数,(stopSelfResult暂时没研究,在这个系列主题内容写完之前会另开一贴,或者之后在此贴更新)
 由第一篇讨论的服务的启动方式可知道,其它组件在调用startService()开启服务的时候,其它组件并没有直接调用onStartCommand(),而是由系统自己调用onStartCommand()的。且看以下正常第一次开启服务的log

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: flags = "+flags);
        Log.i(TAG, "onStartCommand: intent = " + intent);
        Log.i(TAG, "onStartCommand: startId = " + startId);
        //super.onStartCommand()
        return START_REDELIVER_INTENT;
    }
02-09 01:49:06.473 28429-28429/com.example.yanchunguo.bindservice I/LocalService: onCreate:服务创建成功
02-09 01:49:06.473 28429-28429/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: flags = 0
02-09 01:49:06.473 28429-28429/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: intent = Intent { cmp=com.example.yanchunguo.bindservice/.LocalService }
02-09 01:49:06.473 28429-28429/com.example.yanchunguo.bindservice I/LocalService: onStartCommand: startId = 1

此时可以看出flags打印出的值是0,多次调用startService返回值依旧为0,然后异常结束到应用程序,由于返回值是START_REDELIVER_INTENT,所以service肯定会重启,看看log变化

02-09 01:56:14.919 3227-3227/? I/LocalService: onCreate:服务创建成功
02-09 01:56:14.919 3227-3227/? I/LocalService: onStartCommand: flags = 1
02-09 01:56:14.919 3227-3227/? I/LocalService: onStartCommand: intent = Intent { cmp=com.example.yanchunguo.bindservice/.LocalService }
02-09 01:56:14.920 3227-3227/? I/LocalService: onStartCommand: startId = 1

多次重复上演,依旧是没看到flags = 2,也就是START_FLAG_RETRY的情况,看了下官方解释

This flag is set in onStartCommand(Intent, int, int) if the Intent is a retry because the original attempt never got to or returned from onStartCommand(Intent, int, int). 解释下来就是如果在onStartCommand()返回之前异常处理掉应用程序。那么这个flags就是这个值START_FLAG_RETRY

由此,改写一下onStartCommand方法,如下:

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: flags = "+flags);
        Log.i(TAG, "onStartCommand: intent = " + intent);
        Log.i(TAG, "onStartCommand: startId = " + startId);
        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //super.onStartCommand()
        return START_REDELIVER_INTENT;
    }

在onStartCommand()方法返回之前,休眠1分钟,这样就有足够的时间异常终止进程了。然后看下log输出:

02-09 02:32:54.040 5621-5621/? I/LocalService: onCreate:服务创建成功
02-09 02:32:54.040 5621-5621/? I/LocalService: onStartCommand: flags = 2
02-09 02:32:54.040 5621-5621/? I/LocalService: onStartCommand: intent = Intent { cmp=com.example.yanchunguo.bindservice/.LocalService }
02-09 02:32:54.040 5621-5621/? I/LocalService: onStartCommand: startId = 1

果然,在onStartCommand()迟迟不返回的情况下,flags = 2.

  • log结论验证了理论,但是具体这个flag到底是怎么用呢,onStartCommand()的四个返回值已经可以按照可预定的方式是否重新噢拿过去服务了?网上查了半天都是一句话翻译官方解释