android 组件 Service 研究

Service组件概述

Service是android系统的主要组件之一,就像Activity一样,对于开发者来说几乎是必须掌握的。Service的职责是进行一些不需要UI存在操作的,比如网络请求、本地磁盘IO、之行定时任务等。Service是没有图形界面的,只会在后台默默地运行。

注意:Service只是在后台运行的一个组件,默认情况下,Service也是运行在app的主线程中的,它并不会开启新的线程或者进程(当然,这个可以做到)。所以,假如在Service中执行有耗时的操作的话,最好让Service运行在新的线程或者进程里面。

Service 与 线程

在android中,Service只是一个组件,用于满足一些特定的场景而设计的一个常用组件,和线程并不是一回事。如上所述,默认情况下,Service也是运行在app的主线程中的,就像其它组件一样。如果需要在Service中做耗时的或者阻塞的操作的话,最好在Service中开启子线程处理。Service更像是一个独立的服务,一个Service除了被自身的app组件所用外,还可以被其它的app调用(可以通过在 android:exported属性中设置是否允许其它app使用自己的Service),当然,可以是跨进城的调用。所以,在使用的时候不要把Service 和 线程混淆了。

Service 的两种启动形式

  • Started形式(可启动)。该类Service需要实现onStartCommand()方法。由一个其它的android组件来启动一个Service,可以调用startService()方法来实现。这种形式的Service的特点是,一旦Service启动完成后,该Service便会在后台独立地运行,不再受启动它的组件的生命周期的影响。通常来说,该类Service再被启动后做一些不用给启动者返回值的操作,因为该类Service的生命周期要比启动它的组件的要长,所以假如该类Service返回结果的话,无法确定它的启动者是否还活着。举例来说,该类Service就像是扫地机器人,主人把它打开后它就会负责再家中打扫卫生,不管主人是不是还在家,它也不需要报告主人打扫的怎么样了。当该类Service的工作完成后,它应该自己停止自己。当然,其它组件也可以在Service运行的时候选择停止它。
  • Bound形式(可绑定)。该类Service需要实现onBind()方法。一个android组件通过调用bindService()方法来启动一个Service,这个时候该启动者组件会与Service实例形成一个Client-Service结构。Service会提供服务端的接口以便Client端和它进行交互。调用者可以给该类Service发请求,获取结果,委派其完成任务等。该类Service只有在其它组件和其绑定的时候才会启动,而且允许多个不同的组件和它进行绑定。当一个该类的Service没有在运行的和其绑定的组件的时候,该Service实例就会被销毁。

以上两种形式的Service也可以同时使用,只要把onStartCommand() 和 onBind()两个方法同时实现就可以了。这样的话,得到的Service将会具有以上两者的特性。

问题思考:同一个Service的类在android中可以有多个实例吗?
答:不可以。Service的启动模式决定它不会在系统中存在多个实例,当有新的Intent传递过来的时候,android系统会直接使用已经存在的Service实例而不会重新创建,所以在android系统中的Service都是单例的。

Service 生命周期管理

android官方提供了Service的生命周期图:Service 生命周期

开发者在开发的时候,要注意的是Service一旦用过之后一定要注意stop掉Service,如果是bind的Service一定要手动调用unBind方法,这样不光是从自己的Service的安全性考虑,而且这样会更加节省系统资源和电量消耗。

IntentService

简介

IntentService 是Service的一个子类,属于上述的可启动的类型的Service。是官方封装的一个易于使用的Service,它的特点是同步地接受多个start请求,一个接一个地处理它们。并且IntentService里面处理start请求的是在一个独立的线程里进行的,所以可以用来执行耗时的操作而不影响主线程中UI的绘制。使用的时候只需要实现onHandleIntent()方法来接收其它组件传递过来的Intent对象就可以了。IntentService可以满足平常开发中大部分需要使用Service的场景了。

IntentService详情

IntentService做的具体工作由以下这些

  • 创建一个子线程,在子线程里面把Intent实例分发到onStartCommand()方法
  • 创建一个队列,一个接一个地把Intent实例传递到onHandleIntent()方法里面,所以使用的时候不需要担心多线程同步的问题,因为IntentService发送Intent本身就是同步的。
  • 在处理完所有的start请求之后,IntentService会自动停止,所以使用者不用主动去停止。
  • 提供一个对onBind()方法的实现,返回null
  • 提供一个对onStartCommand()方法的实现,并且在里面实现Intent实例发送到上述的Intent处理队列中。

注意:在继承IntentService之后,如果重写了Service生命周期的回调方法,像onCreate,onStartCommand,onDestroy()等,应该在其中调用super方法来保证IntentService能正确的被使用(但是onBind方法中不必要调用spuer,因为如上所述,IntentService中默认的onBind实现是一个返回null的操作,并没有做什么具体的工作)。例如:

1
2
3
4
5
6
>@Override
>public int onStartCommand(Intent >intent, int flags, int startId) {
> Toast.makeText(this, "service >starting", >Toast.LENGTH_SHORT).show();
> return >super.onStartCommand(intent,flags,>startId);
>}
>

Service的回收与重启

android系统的设计原则之一是:保证用户的app尽可能久地运行。这事与IOS完全相反的设计哲学,孰优孰略,暂且不谈,现在讨论下android系统中对Service实例的回收问题。

android系统中,当剩余内存紧急的时候,系统为了保证用户正在进行的工作正常进行以及系统关键的功能能够正常工作,会对已经使用的内存进行部分回收,会把那些不再重要的进程、后台Task、Activity实例、Service实例等进行销毁。所以用户的Service是随时可能被回收的。以上提到,android的设计哲学是让app尽可能久的运行,所以,当机器的内存危机过去之后,android系统会再尝试重建曾经被销毁的实例,比如Service对象就可以被系统再重新创建,并继续之前的工作。

Service 的 onStartCommand()方法的返回值是一个整数,取值必须是这几个中的一个Service.START_NOT_STICKY、Service.START_STICKY、Service.START_REDELIVER_INTENT。这几个返回值会影响到android系统对Service的重建。当然,这里的Service指的是“可启动”的Service。这几个值对Service重建的影响如下:
返回值 | 系统的行为 | 描述
—|—|—
Service.START_NOT_STICKY | 如果系统在Service的onStarnCommand方法回调过之后销毁了该Service,系统不会主动重建该Service,直到有新的组件来启动它。|系统对待该Service的态度是,销毁就销毁了,无所谓
Service.START_STICKY | 如果系统在Service的onStarnCommand方法回调过之后销毁了该Service,系统会主动重新创建该Service的对象,会重新调用该Service的OnStartCommand方法,但是传递进去的Intent对象是一个null | 系统对待该Service的态度是,等内存不那么紧张的时候重建该Service,但是并不会给它传递Intent对象,而是传递null
Service.START_REDELIVER_INTENT | 如果系统在Service的onStarnCommand方法回调过之后销毁了该Service,系统会主动重新创建该Service的对象,会重新调用该Service的OnStartCommand方法,并且把上销毁前的最后一个Intent 对象传递进去,主要用在需要恢复现场的地方,比如下载文件 | 系统对待该Service的态度是,等内存不那么紧张的时候重建该Service,并且会把销毁前最后一个Intent对象重新传递进去

注意:以上所提到的Service的重建的情况都是发生在Service的onStartCommand方法被调用过之后,假如Service的onStartCommand方法没有被调用过就被销毁了,那么就没机会被重建了。

Bound Service的创建

Bound Service的创建需要实现onBind()方法,并返回一个IBinder类型的实例。Bound Service和它的Client通信使用的是androi的IPC技术(interprocess communication,跨进程通信)。具体的做法相对较为复杂,这里不再赘述,建议参考官方文档:Bound Service的创建

Service的类型与优先级

android 中的Service在被启动的时候有两种形式:

  1. background 形式,默认的形式
  2. foreground 形式,通过调用Service的方法startForeground()来使Service以forground的形式运行。相比backgroud形式的Service,系统会认为foreground的Service是用户正在和其进行交互的、活跃的Service,系统在回收内存的时候foreground的Service的优先级较高。

注意:foreground的Service是用来让某些对用户关键的Service不那么容易被系统回收从而保证使用体验的,所以开发者们一定不要用来做些“流氓”的行为。

参考资料:
https://developer.android.com/guide/components/services.html#Notifications

https://developer.android.com/guide/components/bound-services.html

https://developer.android.com/guide/topics/manifest/service-element.html#exported

坚持原创技术分享,您的支持将鼓励我继续创作!