Android之Service的细枝末节

Posted 王三的猫阿德

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android之Service的细枝末节相关的知识,希望对你有一定的参考价值。

转载注明出处:http://blog.csdn.net/xiaohanluo/article/details/53675312

1. 简介

与前一篇Android之Activity的细枝末节是同一系列的文章,是自己在学习和研发过程中,对Service的一些知识点的总结,汇总得到这篇文章。

这篇文章会从Service的一些小知识点,延伸到android中几种常用进程间通信方法。

2. 进程

       Service是一种不提供用户交互页面但是可以在后台长时间运行的组件,可以通过在AndroidManifest.xml设置Service的android:process=":remote"属性,让Service运行另一个进程中,也就是说,虽然你是在当前应用启动的这个Service,但是这个Service和这个应用并不是同一个进程。

四大组件都支持android:process=":remote"这个属性。

因为Service可以运行在不同的进程,这里说一下Android中几种进程的优先级,当系统内存不足时候,系统会从优先级低的进程开始回收,下面根据优先级由高到低列出Android中几种进程。

  • 前台进程,当前用户操作所需要的进程

    • 用户正在交互的Activity(Activity执行了onResume方法)
    • 与正在交互的Activity绑定的Service
    • 设置为前台权限的Service(Service调用startForeground()方法)
    • 正在执行某些生命周期回调的Service,onCreate()、onStart()、onDestroy()
    • 正在执行onReceive()的BroadcastReceiver

    这种进程基本不会被回收,只有当内存不足以支持前台进程同时运行时候,系统才回回收它们,主要关注前三个。

  • 可见进程,没有与用户交互所必须的组件,但是在屏幕上仍然可见其内容的进程

    • 调用了onPause()方法但仍对用户可见的Activity
    • 与上面这种Activity绑定的Service
  • 服务进程,使用startService()启动的Service且不属于上面两种类别进程的进程,虽然这个进程与用户交互没有直接关系,但是一般会在后台执行一些耗时操作,所以,只有当内存不足以维持所有前台进程和可见进程同时运行,系统才回回收这个类别的进程。

  • 后台进程,对用户不可见的Activity进程,已调用了onStop()方法的Activity

  • 空进程,不包含任何活动应用组件的进程,保留这种进程唯一目的是作为缓存,缩短引用组件下次启动时间。通常系统会最优先回收这类进程。

此外,一个进程的级别可能会因为其他进程对它的依赖而有所提高,即进程A服务于进程B(B依赖A),那么A的进程级别至少是和B一样高的。

3. Service配置

和其他组件(Activity/ContentProvider/BroadcastReceiver)一样,Service需要在Androidmanifest.xml中声明。

<manifest ... >
  ...
  <application ... >
      <service android:name=".DemoService" />
      ...
  </application>
</manifest>

Service是运行在主线程中的,如果有什么耗时的操作,建议新建子线程去处理,避免阻塞主线程,降低ANR的风险。

       在另外一篇文章中Intent以及IntentFilter详解提到过,为了确保应用的安全,不要为Service设置intent-filter,因为如果使用隐式Intent去启动Service时候,手机里面那么多应用,并不能确定哪一个Service响应了这个Intent,所以在项目中尽量使用显式Intent去启动Service。在Android 5.0(API LEVEL 21)版本后的,如果传入隐式Intent去调用bindService()方法,系统会抛出异常。

可以通过设置android:exported=false来确保这个Service仅能在本应用中使用。

4. 服务启动方式

服务可以由其他组件启动,而且如果用户切换到其他应用,这个服务可能会继续在后台执行。到目前为止,Android中Service总共有三种启动方式。

  • Scheduled,可定时执行的Service,是Android 5.0(API LEVEL 21)版本中新添加的一个Service,名为JobService,继承Service类,使用JobScheduler类调度它并且设置JobService运行的一些配置。具体文档可以参考JobScheduler,如果你的应用最低支持版本是21,官方建议使用JobService。
  • Started,通过startService()启动的Service。通过这种方式启动的Service会独立的运行在后台,即使启动它的组件已经销毁了。例如Activity A使用startService()启动了Service B,过了会儿,Activity A执行onDestroy()被销毁了,如果Service B任务没有执行完毕,它仍然会在后台执行。这种启动方式启动的Service需要主动调用StopService()停止服务。
  • Bound,通过bindService()启动的Service。通过这种方式启动Service时候,会返回一个客户端交互接口,用户可以通过这个接口与服务进行交互,如果这个服务是在另一个进程中,那么就实现了进程间通信,也就是Messenger和AIDL,这个会是下篇文章的重点。多个组件可以同时绑定同一个Service,如果所有的组件都调用unbindService()解绑后,Service会被销毁。

startService和bindService可以同时使用

5. 主要方法

Service是一个抽象类,使用需要我们去实现它的抽象方法onBind(),Service有且仅有这一个抽象方法,还有一些其他的生命周期回调方法需要复写帮助我们实现具体的功能。

  • onCreate(),在创建服务时候,可以在这个方法中执行一些的初始化操作,它在onStartCommand()onBind()之前被调用。如果服务已经存在,调用startService()启动服务时候这个方法不会调用,只会调用onStartCommand()方法。
  • onStartCommand(),其他组件通过startService()启动服务时候会回调这个方法,这个方法执行后,服务会启动被在后台运行,需要调用stopSelf()或者stopService()停止服务。
  • onBind(),其他组件通过bindService()绑定服务时候会回调的方法,这是Service的一个抽象方法,如果客户端需要与服务交互,需要在这个方法中返回一个IBinder实现类实例化对象,如果不想其他客户端与服务绑定,直接返回null。
  • onDestroy(),当服务不在还是用且即将被销毁时,会回调这个方法,可以在这个方法中做一些释放资源操作,这是服务生命周期的最后一个回调。

如果组件仅通过startService()启动服务,不论服务是否已经启动,都会回调onStartCommand()方法,而且服务会一直运行,需要调用stopSelfstopService方法关闭服务。

如果组件仅通过bindService()绑定服务,则服务只有在与组件绑定时候运行,一旦所有的客户端全部取消绑定unbindService,系统才会销毁该服务。

多次启动同一个服务,只有在服务初次启动时候会回调onCreate方法,但是每次都会回调onStartCommand,可以利用这个向服务传递一些信息。

onStartCommand()的回调是在UI主线程,如果有什么耗时的操作,建议新启线程去处理。

6. 启动和关闭服务

启动服务:

  • JobScheduler.schedule()
  • startService(Intent)
  • bindService(Intent service, ServiceConnection conn, int flags)

关闭服务:

  • JobScheduler.cancel()或者JobScheduler.cancelAll(),对应JobScheduler.schedule()
  • Service自身的stopSelf()方法,组件的stopService(Intent)方法,对应startService启动方法
  • unbindService(ServiceConnection conn),对应bindService

示例:

// 启动服务
Intent intent = new Intent(this, DemoService.class);
startService(intent);

// 停止服务
stopService(intent)

// 绑定服务
ServiceConnection mConnection = ServiceConnection() { ... };
Intent intent = new Intent(this, DemoService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

// 解除绑定
unbindService(mConnection);

绑定服务bindService()第三个参数数值:

  • 0,如果不想设置任何值,就设置成0
  • Context.BIND_AUTO_CREATE,绑定服务时候,如果服务尚未创建,服务会自动创建,在API LEVEL 14以前的版本不支持这个标志,使用Context.BIND_WAIVE_PRIORITY可以达到同样效果
  • Context.BIND_DEBUG_UNBIND,通常用于Debug,在unbindService时候,会将服务信息保存并打印出来,这个标记很容易造成内存泄漏。
  • Context.BIND_NOT_FOREGROUND,不会将被绑定的服务提升到前台优先级,但是这个服务也至少会和客户端在内存中优先级是相同的。
  • Context.BIND_ABOVE_CLIENT,设置服务的进程优先级高于客户端的优先级,只有当需要服务晚于客户端被销毁这种情况才这样设置。
  • Context.BIND_ALLOW_OOM_MANAGEMENT,保持服务受默认的服务管理器管理,当内存不足时候,会销毁服务
  • Context.BIND_WAIVE_PRIORITY,不会影响服务的进程优先级,像通用的应用进程一样将服务放在一个LRU表中
  • Context.BIND_IMPORTANT,标识服务对客户端是非常重要的,会将服务提升至前台进程优先级,通常情况下,即时客户端是前台优先级,服务最多也只能被提升至可见进程优先级,
  • BIND_ADJUST_WITH_ACTIVITY,如果客户端是Activity,服务优先级的提高取决于Activity的进程优先级,使用这个标识后,会无视其他标识。

7. onStartCommand()返回值

onStartCommand()方法有一个int的返回值,这个返回值标识服务关闭后系统的后续操作。

返回值有以下几种:

  • Service.START_STICKY,启动后的服务被杀死,系统会自动重建服务并调用on onStartCommand(),但是不会传入最后一个Intent(Service可能多次执行onStartCommand),会传入一个空的Intent,使用这个标记要注意对Intent的判空处理。这个标记适用于太依靠外界数据Intent,在特定的时间,有明确的启动和关闭的服务,例如后台运行的音乐播放。
  • Service.START_NOT_STICKY,启动后的服务被杀死,系统不会自动重新创建服务。这个标记是最安全的,适用于依赖外界数据Intent的服务,需要完全执行的服务。
  • Service.START_REDELIVER_INTENT,启动后的服务被杀死,系统会重新创建服务并调用onStartCommand(),同时会传入最后一个Intent。这个标记适用于可恢复继续执行的任务,比如说下载文件。
  • Service.START_STICKY_COMPATIBILITY,启动后的服务被杀死,不能保证系统一定会重新创建Service。

8. Service生命周期

Service生命周期(从创建到销毁)跟它被启动的方式有关系,这里只介绍startServicebindService两种启动方法时候Service的生命周期。

  • startService启动方式,其他组件用这种方式启动服务,服务会在后台一直运行,只有服务调用本身的stopSelf()方法或者其他组件调用stopService()才能停止服务。
  • bindService启动方式,其他组件用这种方法绑定服务,服务通过IBinder与客户端通信,客户端通过unbindService接触对服务的绑定,当没有客户端绑定到服务,服务会被系统销毁。

这两种生命周期不是独立的,组件可以同时用startService启动服务同时用bindService绑定服务,例如跨页面的音乐播放器,就可以在多个页面同时绑定同一个服务,这种情况下需要调用stopService()或者服务本身的stopSelf()并且没有客户端绑定到服务,服务才会被销毁。