服务启动方式生命周期 通讯

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了服务启动方式生命周期 通讯相关的知识,希望对你有一定的参考价值。

基本概念

Service通常总是称之为"后台服务",其中"后台"一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件:
1、并不依赖于用户可视的UI界面(当然,这一条其实也不是绝对的,如前台Service就是与Notification界面结合使用的)
2、具有较长时间的运行特性

服务的两(三)种启动方式

1、startService方式启动服务

最核心的一句话:当Client采用startService方式启动一个Service后,Client就和Service没有任何关联了。
Started Service相对比较简单,通过context.startService启动Service,context.stopService停止此Service。
当然,在Service内部,也可以通过stopSelf(...)方式停止其本身。
onBind函数是Service基类中的唯一抽象方法,子类都必须实现。但此函数的返回值仅针对Bound Service类型的Service才有用,在Started Service类型中,此函数直接返回 null 即可。

1)startService生命周期

技术分享
  • 当Client调用startService(Intent serviceIntent)后,如果Service是第一次启动,首先会执行onCreate(),然后再执行onStartCommand。
  • Service启动后,当Client再次调用startService(Intent serviceIntent),将只执行onStartCommand(Intent intent, int flags, int startId)。
  • 使用startService方式启动Service不会回调onBind()方法。
  • 无论多少次的startService,只需要一次stopService()即可将此Service终止,此时Service会执行onDestroy()函数。
  • 如果多次调用stopService(),或如果Service没有启动时调用stopService(),不会出现任何报错或问题。
  • 当用户强制kill掉进程时,onDestroy()是不会执行的。
  • 完整生命周期:onCreate(仅首次startService时回调) > onStartCommand > onStartCommand - optional ... > onDestroy

2)startService相关进程

  • 对于同一类型的Service,Service实例一次永远只存在一个,而不管Client是否是相同的组件,也不管Client是否处于相同的进程中。
  • 通过startService(..)启动Service后,此时Service的生命周期与Client本身的什么周期是没有任何关系的,只有Client调用stopService(..)或Service本身调用stopSelf(..)才能停止此Service。当然,用户可以强制kill掉此Service,当系统因内存不足时也可能会kill掉此Service。
  • Client A 通过startService(..)启动Service后,也可以在其他ClientB通过调用stopService(..)结束此Service。
  • 当调用者Client A结束了自己的生命周期(比如Activity被销毁了),但只要没调用stopService,那么Service还是会继续运行。
  • Client调用stopService(..)时,如果当前Service没有启动,也不会出现任何报错或问题,也就是说,stopService(..)前无需判断当前Service是否有效。
  • 在低版本中,startService和stopService中的intent既可以是显式Intent,也可以是隐式Intent,当Client与Service同处于一个App时,一般推荐使用显示Intent;当处于不同App时,只能使用隐式Intent。但是,在高版本中只能采用显示方式startService或stopService,否则直接异常退出。
  • 如果Service需要运行在单独的进程中,androidManifest.xml声明时需要通过android:process指明此进程名称(详见下面的属性)。
  • 如果此Service需要对其他App开放,android:exported属性值需要设置为true(详见下面的属性)。

3)startService的通信

当Client调用startService启动Service时,Client可以将参数通过Intent直接传递给Service。
Service执行过程中,如果需要将参数传递给Client,一般可以通过借助于发送广播、EventBus、静态全局数据等方式通讯。

2、bindService方式启动服务

bindService()方式启动的service的生命周期和调用bindService()方法的Activity的生命周期是一致的,也就是说,如果Activity结束了,那么Service也就结束了。Service和调用bindService()方法的进程是同生共死的。
相对于Started Service,Bound Service具有更多的知识点。Bound Service的最重要的特性在于,通过Service中的Binder对象可以较为方便进行Client-Service通信

1)bindService典型过程

  1. 自定义Service继承Service,并重写onBind方法,此方法中需要返回具体的IBinder对象(一般将其定义为Service的内部类);
  2. 一般此IBinder是这样定义的 private class MyBinder extends Binder implements IBinderInterface 目的是可以通过自定义接口间接的访问Service;
  3. Client通过bindService(Intent, ServiceConnection, flag)方法将Service绑定到此Client上,绑定时需传递一个ServiceConnection实例;
  4. 一般让Client自身实现ServiceConnection接口,或在Client中定义一个内部类,如 private class MyServiceConnection implements ServiceConnection ;
  5. 在自定义ServiceConnection的回调方法onServiceConnected(ComponentName, IBinder)中,Client便可获取Service端IBinder实例;
  6. 获取到IBinder实例之后,Client端便可以通过自定义接口中的API间接调用Service端的方法,实现和Service的通信;
  7. 当Client在恰当的生命周期(如onDestroy)需要和Service解绑时,通过调用unbindService(ServiceConnection)即可。

2)bindService生命周期

技术分享
  • 当Client调用bindService后,如果Service没有启动,首先会执行onCreate()回调,然后再执行onBind。
  • bindService后,当Client再次调用bindService,不会回调任何方法。
  • 使用bindService方式启动Service不会回调onStartCommand ()方法。
  • 当Client调用unbindService()和Service解绑后,Service会回调onUnbind方法。如果再没有其他Client与Service绑定,那么Service会回调onDestory方法自行销毁
  • 如果对一个服务进行多次解绑,那么就会抛出服务没有注册的异常。
  • 完整生命周期:onCreate > onBind onUnbind > onDestroy(并非Client调用unbindService后就会回调)。

3)bindService方式的特点

  • bindService启动的服务在调用者和服务之间是典型的client-server模式,即调用者是客户端,service是服务端,service就一个,但是连接绑定到service上面的客户端client可以是一个或多个。这里特别要说明的是,这里所提到的client指的是组件,比如某个Activity。 
  • 客户端client可以通过IBinder接口获取到Service的实例,从而可以实现在client端直接调用Service中的方法以实现灵活的交互。另外还可借助IBinder实现跨进程的client-server的交互,这在纯startService启动的Service中是无法实现的。 
  • 不同于startService启动的服务默认是无限期执行的(当然,可以通过调用Context的stopService或Service的stopSelf方法停止服务),bindService启动的服务的生命周期与其绑定的client息息相关,当client销毁的时候,client会自动与Service解除绑定。client也可以通过明确调用Context的unbindService方法与Service解除绑定。
  • 当没有任何client与Service绑定的时候,Service会自行销毁(当然,通过startService启动的Service除外)。 
  • startService启动的服务会涉及Service的的onStartCommand回调方法,而通过bindService启动的服务会涉及Service的onBind、onUnbind回调方法。

4)bindService方法的参数

bindService(Intent, ServiceConnection, flag)
ServiceConnection对象的作用有两个:
  • 为Service的绑定者回调方法,即当Client和Service成功绑定后,Service可以通过回调ServiceConnection的onServiceConnected方法,Client需要的IBinder对象返回给Client。ServiceConnection可以理解为Client和Service之间的桥梁(连接器)
  • Client和Service解绑定时,也是将通过解除Client和Service之间的桥梁ServiceConnection来实现的

ServiceConnection接口有2个方法需要重写。一个是当Service成功绑定后会被回调的onServiceConnected()方法,另一个是当Service解绑定或者Service被关闭时被回调的onServiceDisconnected()。前者会被传入一个IBinder参数,这个IBinder就是在Service的生命周期的回调方法onBind中的返回值,它对Service的绑定式IPC起到非常重要的作用。 

bindService方法中最后一个参数flag则是表明绑定Service时的一些设置,一般情况下可以直接使用0,其标志位可以为以下几种:
技术分享
  • Context.BIND_AUTO_CREATE=1(常用)。表示收到绑定请求的时候,如果服务尚未创建,则即刻创建;在系统内存不足,需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁。
    Flag for bindService: automatically自动的 create the service as long as the binding exists.  Note注意 that while this will create the service,its onStartCommand method will still only be called due to由于 an explicit显示的 call to startService.  Even without that, though,this still provides you with access to the service object while the service is created.
  • Context.BIND_DEBUG_UNBIND=2。通常用于调试场景中,判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用。
  • Context.BIND_NOT_FOREGROUND=4。表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行,该标志位在Froyo中引入。

5)bind和unbind源码分析

  • 绑定服务,首先要做的事情就是先用Map记录当前绑定服务所需的一些信息, 然后启动服务。
  • 解绑服务,先从早前的Map集合中移除记录,然后停止服务。
  • 如果解绑后再次解绑,无非就是再到这个map集合中找找有没有这条记录,没有找到就抛出服务没有注册的异常,也就是早前根本没有注册过任何服务。

3、混合方式启动服务的生命周期

当bindService之前已通过startService开启了服务,则其生命周期方法回调顺序为
技术分享
这种方式其实就是上面那两种方式特性的综合,或者说,兼具两者的优点:
  • 由于是通过startService方式开启的服务,所以在client通过unbindService解绑后Service并不会销毁,并且client注销后Service也不会销毁
  • 由于又通过bindService方式绑定了服务,所以client同样可以方便的和Service进行通讯

onStartCommand方法详解

onStart方法已经被废弃掉了,但是调用onStartCommand时首先调用的还是onStart方法,只不过多了一个返回值
@Deprecated
public void onStart(Intent intent, int startId) {}
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
技术分享

参数(用途不大)

1、int startId
A unique integer representing代表 this specific request to start.  Use with stopSelfResult(int)
startId表示的是:对这个service请求的activity(或者其他实体)的编号。
每次startService()开启服务时,系统都会自动为开启的Service产生一个不同的startId,之前赋予它的startId(如果有)将会被覆盖,并且这个新产生的startId会成为这个Service的新的startId,无论Service是否正在运行

考虑如下情况:当多个组件启用了同一个Service,Service提供互斥的服务(使用synchronized关键字),且要保证在Service把所有工作完成之前不能自杀。
这个时候,startId就相当有用了,在Service onStart()/onStartCommand()时把startId保存起来,因为互斥的使用服务,则Service是按顺序提供服务的,则Service自杀的时候只用检查当前Service的startId与保存的这个startId是否相同,不同则说明Service之后还有任务,不能自杀,相同则说明正在运行的任务已经是最后一个任务了,运行完后就可以自杀(使用stopSelf(int startId)方法)

2、int flags
Additional data about this start request.  Currently either 0, START_FLAG_REDELIVERY, or START_FLAG_RETRY.
flags表示启动服务的方式,取值有:0或START_FLAG_REDELIVERY=1或START_FLAG_RETRY=2
你实现onStartCommand()来安排异步工作或者在另一个线程中工作,需要使用START_FLAG_REDELIVERY来让系统重新发送一个intent。这样如果你的服务在处理它的时候被Kill掉, Intent不会丢失.  
START_FLAG_RETRY:表示服务之前被设为START_STICKY,则会被传入这个标记。

返回值(用默认的就好)

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 START_CONTINUATION_MASK bits.
具体的可选值及含义如下:
  • START_STICKY_COMPATIBILITY=0此值一般不会使用,所以注意下面三种情形就好。
  • START_STICKY=1:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,当系统内存足够可用的情况下,系统将尝试重新创建此Service,一旦创建成功后将回调onStartCommand(...)方法,但其中的Intent将是null,pendingintent除外。
  • START_NOT_STICKY=2:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。除非程序中Client明确再次调用startService(...)启动此Service。
  • START_REDELIVER_INTENT=3:与START_STICKY唯一不同的是,回调onStartCommand(...)方法时,其中的Intent将是非空,将是最后一次调用startService(...)中的intent。
以上的描述中,"当Service因为内存不足而被系统kill后"这句话一定要非常注意,因为此函数的返回值设定只是针对此种情况才有意义的。
换言之,当人为的kill掉Service进程,此函数返回值无论怎么设定,未来的某个时间内,即使系统内存足够可用,Service也不会重启。

另外,需要注意的是,小米等国产手机针对此处可能做了一定的修改。
在"自启动管理"中有一个自启动应用列表,默认情况下,只有极少应用默认是可以自启动的,其他应用默认都是禁止的。
当然用户可以手动添加自启动应用,手动添加的应用中的Started Service,如果onStartCommand()的返回值是START_STICKY或START_REDELIVER_INTENT,那么当用户在小米手机上长按Home键结束App后,接下来未来的某个时间内,当系统内存足够可用时,Service依然可以按照上述规定重启。
当然,如果用户在"设置 > 应用 > 强制kill掉App进程",此时Service是不会重启的。






















































以上是关于服务启动方式生命周期 通讯的主要内容,如果未能解决你的问题,请参考以下文章

关于片段生命周期,何时调用片段的 onActivityResult?

Service学习笔记01-启动方式与生命周期

以编程方式嵌套的片段不反映导航上的父生命周期

Service学习笔记01-启动方式与生命周期

Service学习笔记01-启动方式与生命周期

关于Activity和Fragment生命周期的问题