
2.3 Service
Service服务组件是Android系统重要组件之一,类似于守护进程或Windows系统中的后台服务。Service并没有实际界面,而是一直在Android系统的后台运行。本节主要从3个方面进行介绍:Service的创建和生命周期、本地Service和远程Service。
2.3.1 Service的创建和生命周期
在Android应用程序开发中,一般使用Service为应用程序提供一些服务而不需要界面的功能,例如,从Internet下载文件、控制Video播放器等。同Activity一样,用户定义的每一个Service类都继承父类:Service。每个Service也有自己的生命周期,如图2-7所示。
图2-7 Service的生命周期
创建一个Service类比较简单,只要继承Service,实现其生命周期中的方法即可。一个定义好的Service必须在AndroidManifest.xml配置文件中通过<service>元素声明才能使用。
由图2-7可以看出,有两种启动Service的方法。
1.startService()方法启动
这种方法启动Service的生命周期顺序:onCreate()→onStartCommand(可多次调用)→Service running→onDestroy()。
onCreate()方法在服务被创建时调用,该方法只会被调用一次,无论调用多少次start-Service()方法,服务也只被创建一次。
onStartCommand()方法只有采用Context.startService()方法启动服务时才会回调该方法。该方法在服务开始运行时被调用。多次调用startService()方法尽管不会多次创建服务,但onStartCommand()方法会被多次调用。Service的活动生命周期是在onStartcommand()之后,这个方法会处理通过startService()方法传递来的Intent对象。音乐Service可以通过打开In-tent对象来找到要播放的音乐,然后开始后台播放。
onDestroy()方法在服务被终止时调用。
通过这种方法启动Service,启动后Service与调用者是没有关系的,如果调用者消亡了,Service依然可以继续运行。例如,通过浏览器下载数据就是此种应用。
2.bindService()方法启动
采用bindService()方法启动服务,与之相关的生命周期顺序:onCreate()→onBind()(不可多次绑定)→Service运行→onUnbind()→onDestroy()。若用bindService()方法启动Service,首先需要通过一个ServiceConnection对象绑定到指定的Service。若没有启动Serv-ice,则首先调用Service的OnCreate()方法来初始化启动Service。然后调用Service的onBind()方法初始化绑定。这种方法调用者和Service是绑定在一起的,如果调用者被销毁,则被绑定的Service也会被调用执行onUnbind()和onDestory()方法停止运行。onBind()方法只有采用bindService()方法启动服务时才会回调该方法。onBind()方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用bindService()方法并不会导致该方法被多次调用。
onUnbind()也是只有采用bindService()方法启动服务时才会回调该方法。该方法在调用者与服务解除绑定时被调用。
在理解Service运行过程中,要注意Service并不是一个独立的进程或者线程。实际上它和当前的应用程序是在一个进程中。如果需要Service做一些很耗时的操作,就必须新启动一个线程。
Service每一次开启至关闭过程中,以上方法中只有onStarCommand()方法可被多次调用(通过多次对startService()调用),其他方法,如onCreate()、onBind()、onUnbind()和onDestory(),在一个生命周期中只能被调用一次。
2.3.2 本地Service
本地Service用于应用程序内部,它可以启动并运行直至被停止或自己停止。在这种方式下,它调用startService()方法启动,调用stopService()方法结束。本地Service也可以调用Service.stopSelf()方法或Service.stopSelfResult()方法来自己停止。不论调用了多少次start-Service()方法,只需要调用一次stopService()方法即可停止服务。
本地服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源。本地服务一般用于实现应用程序中的一些耗时任务,比如查询升级信息,并不占用应用程序比如Ac-tivity所属线程,而是单开线程在后台执行,这样用户体验比较好。常见的应用有音乐播放服务等。
为了更好地理解本地Service的运行过程,请参见【例2-4】。
【例2-4】Example2-4 LocalService示例。
1)创建项目文件Example2-4LocalService,然后在Src中分别创建1个Activity和1个Service:LocalActivity.java、LocalService.java。在resource→layout中分别创建1个Activity对应的布局文件。
2)编写Activity和Service类文件,LocalActivity.java的主要代码如下。
【代码说明】
●第1123行实现开始服务和结束服务的监听器接口。
●第2427行代码通过Intent实现启动服务。
●第2831行代码表示停止服务,只有调用本方法,当前服务才会停止。这和调用本
Service的LocalActivity是否消亡无关。
LocalService.java的主要代码如下。
【代码说明】
●第0609行代码实现本地Service的创建,当创建Service对象时,首先调用这个方法。
●第1015行代码实现启动Service。
●第1619行代码实现销毁Service。
3)在本项目中,Activity组件的声明已经由系统自动完成,那么只需添加以下代码完成Service组件的声明。否则在启动服务时会提示错误,中断程序运行。
4)在虚拟机上运行应用程序,并在Activity中单击“启动服务”按钮,如图2-8所示。
运行结果如图2-9所示。
单击“关闭服务”按钮,相关生命周期函数运行信息如图2-10所示。
图2-8 启动Service
图2-9 启动Service生命周期函数运行信息
图2-10 关闭Service生命周期函数运行信息
由本项目可以看出,对于这类无须和Activity交互的本地服务,最好用startService()方法与stopService()方法。
运行发现第一次运行startService()方法时,会调用onCreate()方法与onStartCommand()方法,在没有调用stopService()方法前,无论通过单击调用多少次startService()方法,都只会调用onStartCommand()方法。而使用stopService()方法时则调用onDestroy()方法。再次使用startService()方法,会发现不会进入Service的生命周期,即不会再调用这些方法:on-Create()、onStartCommand()和onDestroy()。onBind()方法在使用startService()方法启动服务时没有被调用。
2.3.3 远程Service
远程Service用于Android系统内部的应用程序之间。远程服务为独立的进程,对应进程名格式为所在包名加上指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill时,该服务依然在运行而不受其他进程影响,这有利于为多个进程提供服务,具有较高的灵活性。由于该服务是独立进程,所以会占用一定资源,并且使用AIDL(接口定义语言)进行RPC(远程进程调用)。
AIDL简介:远程进程调用是指在一个进程里,调用另外一个进程里的服务。接口定义语言(AndroidInterface Definition Language,AIDL)是Android系统的一种接口描述语言,通过接口定义语言来生成两个进程间的访问代码,实现Android系统的进程间通信。Android编译器可以将AIDL文件编译成一段Java代码,生成相对的接口。
RPC简介:远程Service调用,是Android系统为了提供进程间通信而提供的轻量级实现方式,这种方式采用一种远程进程调用技术来实现,英文名全称是Remote Procedure Call,即RPC。
远程Service可以通过自己定义并暴露出来的接口进行程序操作。客户端建立一个到服务对象的连接,并通过那个连接来调用服务。连接以调用bindService()方法建立,以调用unbindService()方法关闭。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()方法会先加载它。被开放的服务可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的服务即可。
下面详细介绍如何使用AID实现RPC机制来达到远程进程调用的目的和步骤。
首先,要先弄清楚这是怎样的一个流程。比如程序A提供服务A,并且这种服务是对外开放的,也就是其他程序都可以通过某种方式来使用这种服务,程序B想要使用程序A的服务A,就以通过程序A提供的一种形式来使用它,如图2-11所示。
图2-11 程序B使用程序A提供的服务
也就是说,进程B可以通过进程A提供的某种形式来使用进程A的服务A。这个过程就是RPC。
了解了基本概念之后,可以通过实例项目来说明实现RPC的步骤。
【例2-5】Example2-5 RemoteService示例。
1)创建项目,然后在项目src包中创建AIDL文件。这个文件和普通的Java文件差不多,只不过扩展名是.aidl。这个文件相当于一个接口,里面要声明一些对外提供服务的方法,也就是要对外暴露的方法。注意,在这个文件里,除了Java基本类型及String、List、Map和Charquene类型不需要引入相应的包外,其他的都要引入包。例如,创建一个Ap-ple.aidl文件,里面只声明了一个方法getName(),代码如下。
创建完AIDL文件后,刷新项目,会发现在gen目录对应的目录下会生成一个Ap-ple.java接口文件,打开该文件,会发现在AIDL文件里面定义的getName()方法,但没有实现,如图2-12所示。
图2-12 在项目中创建AIDL文件
2)实现系统自动生成的Apple接口。注意,这里创建的类不能实现系统生成的Apple接口,而是继承该接口里面的Stub类。主要代码如下。
3)编写了AIDL文件,实现接口文件,接下来就是要将接口暴露给其他程序,让其他程序可以使用这个方法。通常的做法是,定义一个Service,在该Service的onBind()方法里返回这个接口。这样做的原因是在Android中调用远程服务的方法时,Service是最好的载体,因为它可以一直在后台运行。在Activity绑定到该服务时,就可以得到该接口,然后可以调用接口里面的方法。Service主要代码如下。
4)在客户端调用远程Service。在前面操作的基础上,还需要在客户端里调用已实现的接口。在Activity中,通过一个Button触发事件,调用远程服务接口。主要代码如下。
【代码说明】
●第03行代码表示定义远程接口。
●第04行代码表示自定义Action。
●第15行代码表示把MainActivity绑定到定义好的那个Service。
●第25行代码表示调用接口中定义好的方法。
5)最后还要编写Mainfest.xml文件,主要代码如下。
6)在AVD中运行项目,在Activity中单击“调用远程服务”按钮,如图2-13a所示,运行结果如图2-13b所示。
通过本示例,可知在同一个应用程序中可以使用远程Service的方式和自己定义的服务进行交互。如果是另外的应用程序使用远程Service,需要做的是复制上面的AIDL文件和相应的包结构到应用程序中,其他调用方式都基本相同。
如果需要编写传递复杂数据类型的远程Service,远程Service往往不只是传递Java基本数据类型。这时需要注意Android的一些限制和规定。
图2-13 远程服务运行结果图
a)调用远程服务 b)成功调用远程服务
1)Android支持String和CharSequence。
2)如需要在AIDL中使用其他AIDL接口,需要类型导入,即使在相同包结构下。
3)Android允许传递实现Parcelable接口的类,需要类型导入。
4)Android支持集合接口类型List和Map,但元素必须是基本型或者上述3种情况,不需要import集合接口类,但需要对元素涉及的类型进行导入。
5)非基本数据类型,且不是String和CharSequence类型,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout表示两者均可设置。