3.3 服务组件Service解析
如果说界面组件是红花,将应用的美丽展示在用户面前,那么服务组件就是绿叶,它并没有华丽的交互界面,只是在后台默默地提供功能,进行调度和统筹。
Android的服务组件派生自android.app.Service类。在功能概念方面,Android的服务组件和Windows等传统平台的服务比较接近,但是在运行模式和使用方式上却有很大的不同,这就使得服务组件成为Android中最容易被误用的组件。
首先从运行模式上来看,Android的服务组件没有运行在独立的进程或线程中。默认情况下,服务组件构造于应用进程中[1],并且和所有其他的Android组件一样,都在进程的主线程(即UI线程)中运行。这就意味着,如果直接在服务组件中同步执行耗时的操作,将会导致主线程阻塞或界面假死,从而无法响应用户的操作。
另外,从使用方式来看,Android的服务组件不仅可以与前端界面组件建立双向连接,提供数据和功能支持,也可以单向接受Intent对象的请求,进行数据的分析处理和功能调度。在不同的使用方式下,服务组件扮演的角色和开发模式完全不同。这种设计,也为理解服务组件带来了一定的难度。
3.3.1 服务组件的功能和特征
Android的界面组件,意在帮助应用与用户进行交互,而服务组件,则用于辅助应用与系统中的其他组件或系统服务进行沟通。
以闹钟应用为例,它需要在设备开机时启动,读取设定好的闹钟信息,然后在系统的定时服务中注册,以便到时能够提醒。同时,闹钟应用还要监听一些和时间变化相关的事件,比如时间或时区改变,使其能够立刻检查所设的提醒事件是否已经到期需要提醒用户。整个流程如图3-6所示,闹钟应用可以为每个需要监听的事件配置触发器组件(Broadcast Receiver,下一节会对该组件进行具体介绍)。当发生相应事件的时候,触发器组件的BroadcastReceiver.onReceive函数将被调用。触发器组件本身不能缓存数据或执行过于耗时的操作[2],因此往往需要通过Context.startService函数将事件转发给对应的服务组件来处理。
在这种模式下,服务组件扮演的角色是功能调度者。它从事件触发器对象那里收集各类事件信息,进一步分析和处理,然后更新界面、修改数据抑或进行其他相关的操作,调度整个应用使其保持正确的状态。
除了作为调度者,服务组件还可以扮演另一个很重要的角色—界面组件的功能提供者。界面组件是Android中功能复用的基本单元,但这种复用的粒度太大,包含了整个界面、数据和功能。而在有的场景下,应用需要停留在自己的交互界面与用户交流。此时,它不需要复用第三方界面,而只需要获得一些功能和状态数据即可,这样的支持就是通过服务组件来提供的。
图 3-6 服务组件处理事件流程
Android的输入法框架是一个基于服务组件进行功能复用的例子。Android设备中可能会安装多个输入法应用,每个输入法应用都会有自己独特的交互界面和字典数据。它们不需要界面复用,只需要后台提供控制输入法的消隐和绑定等操作的支持即可。
如图3-7所示,输入法界面组件通过调用Context.bindService函数发起连接请求。经由组件管理服务的调度,输入法服务(android.inputmethodservice.InputMethodService)的onBind方法会被调用并构造一个IBinder对象返还给输入法界面组件,从而与其建立一个IPC连接,界面组件可以通过远程方法调用来进行输入法相关操作。界面组件结束其服务后,应该调用Context.unbindService函数终止连接。
图 3-7 服务组件绑定连接流程
和界面组件类似,不论是服务的绑定还是连接,服务组件的定位和消息的传递都是通过Android的意图机制来实现的[3]。这使得服务的使用者和提供者彼此透明,服务组件可以动态地变更、增删和替换,大大增强了系统的灵活性。
[1]可以通过配置服务组件的进程参数process使其在特定的进程中运行,详情请参见第5章关于组件运行模型的内容。
[2]关于触发器组件的特征,将在下一节详细讨论。
[3]第4章会详细介绍Android的意图解析机制。