Android中Service的四种使用方式

Service是Android中的四大组件之一,用于在后台进行长时间作业,不提供用户界面。Service可以直接由其他的应用程序组件启动并在后台运行,也可以与其他组件绑定来实现进程间通信。本文介绍了创建Service的基本方法,并通过实例描述了Service的四种使用方式,包括Service的启动和绑定,以及IntentService和AIDL Service的使用。

0. 创建Service

  创建一个Service有两个步骤:实现Service子类,以及在manifest文件中声明该Service。

0.1. 实现Service子类

  与Activity类似,实现自己的Service子类时,需要重写一些回调方法,来管理Service的生命周期,如onStartCommand()onBind()onCreate()onDestroy()等。需要注意的是,onBind()是必须要重写的。举例来说,一个Service的简单实现如[代码0-1]所示,这个Service开启了一个线程来对 count 进行累加和打印。

0.2. 在manifest文件中声明

  在manifest文件中声明Service的方法也与Activity类似,只需将 <service.../> 插入到 <application> 元素下面即可,如[代码0-2]所示。

关于 <service> 下的更多配置可以参考这里。Google的教程中指出,从安全角度考虑,在启动服务或绑定服务时尽量显式地指出所要使用的Service,而不要通过向Service加入Intent Filters来指明可以启动该Service的Intent。

1. 启动Service

  Service的启动可以通过调用Context中的startService()实现,相对应的,stopService()用于停止Service。

  新建一个Activity,命名为MyActivity;再新建一个如[代码0-1]所示的MyService,并按照[代码0-2]的形式在AndroidManifest.xml文件中声明MyService。在MyActivity中加入两个按钮 btnStartService 和 btnStopService ,分别用于启动和停止服务,如[代码1-1]所示。

   运行MyActivity,点击按钮启动Service,然后再点击按钮停止Service,可以看到打印信息:

 可见Service能够正常地启动与停止。连续两次点击按钮启动Service,可以看到打印信息:

由此可以看出,startService()首先会通过Service的onCreate()来生成Service,然后通过onStartCommand()来启动Service。重复启动同一个Service,onStartCommand()会被重复调用,而onCreate()只会执行一次。

2. 绑定Service

  在上面的例子中,Activity仅仅是控制了Service的启动和停止,如果要实现与Service之间的数据交换,就需要将Service与Activity进行绑定,由Service需要向Activity提供访问其自身数据的方法。

  新建一个Service,命名为MyBindService,其内容与[代码0-1]所示的MyService基本一致,仅仅是更改了 TAG 并去掉了对 count 的打印,如[代码2-1]所示。

  为了让MyBindService向MyActivity提供访问其自身数据的方法,需要确实地实现MyBindService的onBind()方法,并在MyActivity中对MyBindService进行绑定。

2.1. 实现onBind()方法

  虽然[代码2-1]所示的MyBindService中也实现了onBind()方法,但只是返回了一个 null 值,没有实际意义。这里需要修改让onBind()返回一个IBinder对象,这个IBinder对象会在MyService(或其他组件)绑定MyBindService的时候,由MyBindService返回给MyService,MyService可以通过这个IBinder对象与MyBindService进行通信。

  实现IBinder时一般会直接继承Binder,它是IBinder的实现。这里实现的MyBinder如[代码2-2]所示,它提供了一个getCount()方法,返回了MyBindService中的 count 。

  接下来为MyBindService添加成员 private MyBinder binder = new MyBinder(); ,并修改MyBindService的onBind()方法返回 binder ,如[代码2-3]所示。

   到这里便完成了MyBindService,完整代码如[代码2-4]所示。

2.2. Service的绑定

   Service的绑定是通过bindService()实现的,其完整签名为:

 其中 service 指明所要绑定的Service, conn 是一个ServiceConnection对象,用于接收Service启动和停止的信息, flags 用于设置绑定的参数,比如是否在绑定时自动创建Service(0表示不自动创建, BIND_AUTO_CREATE 表示自动创建等,其他选项可以参考API文档)。

    为了完成绑定,需要实现bindService()参数中的ServiceConnection,它用于监听所绑定Service的连接状态:当连接成功时,将回调ServiceConnection对象的onServiceConnected()方法,Service中onBind()方法返回的IBinder会作为参数传递给onServiceConnected();当连接意外中断时,则回调onServiceDisconnected()。

    回到例子的具体实现上,首先为MyActivity添加成员 private MyBinder binder = new MyBinder(); 用于在本地保存MyBindService返回的IBinder,然后实现ServiceConnection如[代码2-5]所示。

 这里onServiceConnected()将作为参数传递过来的 iBinder 保存为本地的 binder ,onServiceDisconnected()只进行了打印。

    在MyActivity中加入三个按钮: btnBindService 用于绑定MyBindService, btnStatus 用于读取MyBindService中 count 的值, btnBindService 用于解绑MyBindService。最终结果如[代码2-6]所示。

其中第17行通过 bindService(bindServiceIntent, serviceConn, Service.BIND_AUTO_CREATE); ,使MyActivity与 bindServiceIntent 所指定的MyBindService进行了绑定,由 serviceConn 管理连接, Service.BIND_AUTO_CREATE 表示在绑定时自动创建Service。

   运行MyActivity,点击 btnBindService 绑定MyBindService后,点击 btnStatus 读取MyBindService中 count 的数值,再点击 btnBindService 解除绑定,得到打印信息:

可见,MyActivity可以获取到MyBindService中 count 的数值。在绑定服务时,首先执行的是Service中的onCreate()和onBind()方法,然后才会回调在绑定Service时所指定的ServiceConnection对象的onServiceConnected()方法。解除绑定时,onServiceDisconnected()方法没有执行,这是因为上面的例子是通过unBindService()来主动解除绑定的,onServiceConnected()只有在Service所在的宿主进程意外终止导致连接断开时才会被调用。

3. IntentService

   在前面的例子中,Activity所启动的Service与Activity在同一个进程中运行,而且Service也不会自动为自己的业务新建线程,如果在Service中直接处理耗时任务,就可能导致应用程序无响应(Application Not Responding,ANR )。所以在[代码0-1]和[代码2-4]中,都是在Service的onCreate()中手动新建了线程对 count 进行计数。

   IntentService是Service的子类,顾名思义,客户端使用startService()通过Intent请求启动IntentService,IntentService使用队列对客户端发来的Intent进行管理,并开启新线程来处理Intent。

   一个例子如[代码3-1]所示,IntentService为onBind()和onStartCommand()提供了默认实现,所以只重写了onHandleEvent()。MyIntentService进行了30秒的计时和打印。

  为了与IntentService对比,建立如[代码3-2]所示的普通Service,CounterService同样是进行30秒的计时和打印,但没有手动新建线程。

  执行MyIntentService,打印结果如下:

 可见MyIntentService顺利完成了计时。而执行CounterService时,打印结果如下:

 在CounterService开始执行后,应用便会失去响应,执行时间过长还会出现ANR。

4. AIDL Service

  如前所述,通过绑定Service可以实现Service与其他组件之间的数据交换,但Android系统中的进程之间一般无法直接交换数据,为了让Service能够跨进程与其他组件进行通信,就需要使用AIDL(Android Interface Definition Language)。

  下面依旧通过一个例子说明AIDL Service的使用。例子内容与前面类似,依旧是在Service中进行计数,由Activity读取Service中计数的值,不过这次Activity与Service不在处于同一进程,Service需要定义一个远程调用接口,并提供实现,Activity远程调用Service的接口,与Service进行通信。

4.1. 编写AIDL文件

  AIDL用于定义远程接口,其详细使用方法可以参考官方文档,这里只是通过例子给出一个简单的示范,建立一个MyAIDLInterface.aidl文件,如[代码4-1]所示。

可以看出,AIDL的语法与Java类似,上面的例子声明了一个getCount()方法,该方法返回一个int型的值。如果使用Eclipse,上面的AIDL文件保存之后,就可以在gen目录下找到一个自动生成的MyAidlInterface.java文件;如果使用的是Android Studio,则需要先“Make Project”,然后就可以在build/generated/source/aidl下找到自动生成的MyAidlInterface.java文件。MyAidlInterface包含一个内部类Stub,该内部类继承了Binder,并实现了IMyAidlInterface。由于Stub继承了Binder,可以将Stub的实例作为Service的onBind()方法的返回值,提供给远程组件;由于与Stub实现了IMyAidlInterface,使得Stub可以向远程组件提供MyAIDLInterface.aidl中定义的接口。

4.2. 建立Service

  MyAIDLInterface.aidl文件中声明了接口getCount(),接下来就要给出getCount()的实现,如代码[4-2]所示。

 声明MyAidlServiceBinder继承IMyAidlInterface.Stub,并实现getCount()返回 count ——Service中的计数值。

  然后为Service添加成员 private MyAidlServiceBinder binder; ,并通过onBind()返回 binder ,如[代码4-3]所示。

   最终得到的Service如[代码4-4]所示:

 在onCreate()中,通过 binder = new MyAidlServiceBinder(); 创建了MyAidlServiceBinder的实例,供onBind()作为返回值。此时可以先运行MyAidlService,供后面MyActivity进行远程调用。

4.3. 远程调用

   依旧使用MyActivity远程调用MyAidlService。为MyActivity添加成员 private IMyAidlInterface aidlService; ,添加按钮 btnBindAidlService  和 btnUnbindAidlService 分别用于绑定Service和解绑Service,如[代码4-5]所示。

可以看出,绑定和解绑AIDL Service的流程与绑定普通Service类似,第7行中绑定Service时使用的 aidlServiceConn 如[代码4-6]所示。

在onServiceConnected()的第4行,把通过参数传递过来的 iBinder 保存到本地。注意这里使用了 IMyAidlInterface.Stub.asInterface(iBinder) ,而不是直接像[代码2-6]中那样直接对 iBinder 进行类型转换,这是因为绑定远程Service的ServiceConnection不能直接获取Service中onBind()方法返回的对象,而只能通过 IMyAidlInterface.Stub.asInterface(iBinder) 获取onBind()返回对象的代理。

   为MyActivity添加按钮 btnStatusAidl 用于读取并打印MyAidlService中的 count 值,如代码[4-7]所示。

直接通过 aidlService.getCount() 就实现了远程调用,获取了MyAidlService中 count 的值,打印结果如下:

可见,MyActivity成功与MyAidlService进行了通信。