Service与Android系统实现(1)

特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处。作者系LiAnLab.org资深Android技术顾问吴赫老师。本系列文章交流与讨论:@宋宝华Barry

共18次连载,讲述Android Service背后的实现原理,透析Binder相关的RPC。

1.     Service

Service 在Android应用程序里四大实体之一。Android的应用程序不光是需要有图形界面来进行交互,有时也会需要在没有交互的情况下进行的操作,比如下 载、更新、监听等。比如目前对我们网络生存影响如此之大的社交网络、或是更老一些聊天工具,总需要这类应用程序可以一直在后台运行,以等待可能过来的消 息。即使我们写一些非常简单的基于GPS来记录自己地址的一些小应用程序,我们可能都会有这种需求:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

这 个简单的小应用程序,几乎涉及到Android应用程序的四大组件:Activity、Service、BroadcastReceiver、 Content Provider。在这一应用程序里创建了一个BootReceiver的BroadcastReceiver,用于监听是否有启动完成的消息过来,从而 实现开机自动启动。这Boot Receiver此时是不应该触发Activity的,它只会启动一个Tracker Service,在后来开始监听GPS状态,或是在某些合适的时间点,比如提示用户到了某个地点时,打开一个Activity进行提示。

从 这个简单的应用程序设计里,可以看出Service所能完成,但Activity又做不到的事情。一是后台运行,有时我们并不希望有过多对话框来影响用户 体验,开机自动启动,便可默默地在后台运行。另一特性,就是不被Activity生命周期所管理,Activity处于完全活跃的周期是 onResume()与onPause()之间,如果这周期之外发生了事件,实际上Activity构成的执行部分也不会被执行到,从而无法响应处理,但 Service由于本身过于简单,则会通过一定的辅助手段来达到这个目标。如果对编写恶意软件或是安全软件感兴趣,则Service是必然的编程选择,因 为Service这种在后台执行,不受限于交互的特性。

Activity对应用程序来说是最重要的组件,但从Android系统设计的角度 来看,Service对系统层实现来说才最重要的。Service是构建系统的根本,支持整个系统运营的环境framework,本身就是由大量 Service来构成的。也就是说,Service反而是构建Activity的基础环境。

Android与其他系统设计最大的不同之处在 于,它并不是一种传统的系统环境,而是一种更加开放的系统。传统的图形操作系统里,会有基本环境,会有系统管理组件,应用程序只是作为补充性功能实现。但 Android并不如此,Android系统是没有应用程序的,达到了“无边界”系统设计的最高境界,“手里无剑,心中有剑”。整个Android系统的 设计使自己扮演着支撑系统的运行环境的角色,不再有基本系统的概念,而变成了一种“有或者无”的应用程序的支撑环境,没有系统组件的概念。而我们所谓的系 统应用程序,我们只能称它们为“内置”应用程序,只是服务于黑心移动运营商的一种方式而已。

这种设计的精髓在于,系统本身不会处理交互,而 只是提供交互的手段。从前面我们对于应用程序运行环境的分析中,我们可以看到,Android的Framework,提供一部分功能供应用程序调用,而除 了这些应用程序直接使用的API实现,其他代码逻辑就会全是由Service构成。当然作为系统实现角度的Service,与应用程序编程里实现的 Service是有差别的,更强调共享,但基本构架一样。在过渡到Android系统的解析之前,我们先从应用程序的Service概念入手。

1.1   本地简单Service

我 们先来在应用程序里写一个简单的Service。打开Eclipse,新建一个Android工程,然后再创建一个新的基于Service基类的类。与 Activity的编程方式类似,Service在编程上也是基于回调方式实现的,我们继承基类Service之后所需要做的,就是通过IoC模式替换原 来的Service回调的实现:

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

import android.util.Log;

public class LianlabServiceextends Service

{

    private staticfinal String TAG ="LianlabService";

   @Override

    public void onCreate() {

       super.onCreate();

       Log.v(TAG, "inonCreate()");

    }

   @Override

    public int onStartCommand(Intent intent,int flags,int startId) {

       super.onStartCommand(intent, flags, startId);

       Log.v(TAG, "inonStartCommand()");

       return START_STICKY;

    }

   @Override

    public void onDestroy()

    {

       Log.v(TAG, "inonDestroy().");

       super.onDestroy();

    }

}

有了Service的具体实现之后,系统并不会自动地识别到这一实现,在Android世界里,一切都通过AndroidManifest.xml来驱动,于是,我们还需要修改AndroidManifest.xml文件,加入Service的定义:

    <applicationandroid:label="@string/app_name">

       <serviceandroid:name=".LianLabService"/>

 </application> 

在 上面这种方式里实现的Service,可被执行的方式很有限,就是提供一个可执行的线程环境,可以被Intent所驱动,执行 onStartCommand()回调。功能有限并不代表无能,在Android系统里,我们可能还经常会碰到这样的需求:比如我们使用GPS里来记录我 们行动轨迹时,这时我们很可能需要通过后台的执行的代码来定时检查GPS的定位信息;杀毒或是监控软件可能希望驻留在后台,并可被Intent来驱动开始 进行杀毒;我们的聊天或是社交应用,需要在后台定时地与服务发送“心跳”(Heart beat),用来标识自己的在线状态等。这样的例子,大家可以回头到我们画的GPS轨迹跟踪的构成示意图,这样的跟踪软件,必须是通过一个接收启动完成信 息的Broadcast Receiver来监听自己是否应该被执行,而接收到到启动完成的Broadcast Intent之后,则必须触发一直在后台运行的TrackerService的执行。

既然我们在上述方式里实现的Service是由 Intent驱动的,于是我们的使用这一Service部分的代码也会很简单。在任何可被执行到的代码里使用startService(Intent)就 可以完成,我们可以给某个控件注册点击事件支持onClickListener对象,然后覆盖onClick()回调方法:

    public void onClick(Viewv) {

         Intent intent = new Intent(this,

         LianlabService.class);

       startService(intent);

   }  

我 们这里既然使用到了Intent,也就是说我们还可以通过extras这个Bundle对象给我们这里实现的LianLabService来传递运行的参 数。于是,这时我们的代码貌似有了pthread多线程执行效果,通过传参,然后我们会执行一个在另一线程里运行的函数,只是函数是固定的 onStartCommand()回调方法。但这只是貌似,并非实际情况,Service的执行与后台线程方式极大不同,Service只是一种代码逻辑 的抽象,实际上它还是运行在Activity同一线程上下文环境。

于是,我们并不能用Service来进行任何耗时操作,否则会阻塞主线程而造成应用程序的无法响应错误,也就是臭名昭著的ANR错误。Service仅能用于不需要界面交互的代码逻辑。

1.2   本地 Bounded Service

这 种使用Intent来驱动执行的Service,可用性有限,并不能完全满足我们对于后台服务的需求。对于后台执行的代码,我们更多的应用情境不光是希望 进行后台操作,我们可能还希望能进行交互,可以随时检查后台操作的结果,并能暂停或是重启后台执行的服务,可以在使用某一Service时保证它并不会退 出执行,甚至一些提交一些参数到后台来进行复杂的处理。这时,我们可以使用Service的另一个访问方式,使用Binder接口来访问。我们的 Service基类还提供这类应用的回调方式,onBind()、onUnbind()和onRebind()。使用Binder来访问Service的 方式比Intent驱动的应用情境更底层,onBind()回调主要用于返回一个IBinder对象,而这一IBinder对象是Service的引用, 将会被调用端用于直接调用这一Service里实现的某些方法。

同样的Service实现,如果通过IBinder来驱动,则会变成下面的样子:

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

import android.util.Log;

public class LianlabServiceextends Service

{

    private staticfinal String TAG ="LianlabService";

 

   @Override

    public void onCreate() {

       super.onCreate();

       Log.v(TAG, "inonCreate()");

    }

   @Override

    public intonStartCommand(Intent intent,int flags,int startId) {

       super.onStartCommand(intent, flags, startId);

       Log.v(TAG, "in onStartCommand()");

       return START_STICKY;

    }

   @Override

    public void onDestroy()

    {

       Log.v(TAG, "inonDestroy().");

      super.onDestroy();

}

   finalIService.Stub m_binder =newIService.Stub() {

        ...

   }

   @Override

    public IBinderonBind(Intent intent) {

       Log.v(TAG, "inonBind().");

       return mBinder;

    }

   @Override

    public booleanonUnbind(Intent intent) {

       Log.v(TAG, "inonUnbind().");      

       return mAllowRebind;

    }

   @Override

    public void onRebind(Intentintent) {

       Log.v(TAG, "inonRebind().");

    }

}

使 用IBinder对象来触发的Service,在访问时的代码实现则变得完全不样了。比如我们同样通过onClick()来操作后台的某些操作,但这时并 非通过Intent来完成,而是直接使用某个引用这一Service的IBinder对象来直接调用Service里实现的方法。

       bindService(intent, m_connection, );

       private ServiceConnection m_connection =new ServiceConnection() {

           private IService onServiceConnected(, IBinder service) {

                m_service =IService.Stub.asInterface(service);

            }

       }

如 果Service里实现了某些方法,比如kill(),在上述代码之后,我们对Service的驱动则会变成代码上的直接调用。在 onServiceConnected()回调方法被触发之后,我们始终都可以通过m_service.kill()来访问Service里的 kill()方法。而bindService()这方法的调用,则会触发onServiceConnected()事件。

       这样就要让人抓狂了,既然如此麻烦,何不直接调用呢?所以,事实上,这里列举的这种代码实现方式,在现实编程里确实不常用。一般而言,如果Service 通过IBinder对象来触发,那只会出于一个理由,提供一种可能性,将来可以更灵活地提供给另一进程来访问,这就是我们稍后会说明的Remote Service。

这两种不同的Service的实现方式,将决定Service的不同被调用方式,或者准确地说,将决定Service的不同生命周期。

如 图所示,Service的进程存活期是处理onCreate()与onDestroy()回调方法之间。onStartCommand()回调是不受控 的,每次Intent都将触发执行一次, onStartCommand()执行完则会退出;而使用onBind()来驱动的Service,其活跃区间是onBind()与onUnbind() 之间,其活跃周期始终在控制范围内。

1.3   基于IBinder的RPC

我们可以结合传统的RPC概念,并透过IBinder的RPC支持来看Android的跨进程调用。出于“沙盒”式的系统设计,Android系统更依赖于跨进程的交互。

如我们前面所说,如果只是提供后台服务,我们一般不会无事找抽地来通过IBinder来访问Service,那这种方式存在的意义何在?IBinder在我们后面的内容里会进一步说明,在这里,我们可认为Binder就是一种系统的IPC机制,可以在进程间传递数据。

       Binder所能完成的作用仅是IPC,虽然Binder本身具有强大的面向对象能力,可以在两个进程间传递对象,但通过Binder得到的,实际上还是 两个进程空间里的对象。也就是说,这时一个进程里修改了对象属性,并非可以改变另一个进程的对象,因为这时两个进程分别拥有自己独立的进程空间。如果需要 构建于IPC机制上的互相通信,这时还需要通过对象引用能访问到对象的公开出来的方法,并非可以通过对象的Getter/Setter方法来修改对象的属 性(出于面向对象的代码规范性,一般不直接修改对象属性,而通过getter/setter类型的方法来修改)。

       出于这种更加具有交互性的跨进程访问,实际上并非Android环境才需要,这是所有跨进程软件设计里的必须项。这种交互性的跨进程需求,跟我们传统的 C/S(客户端/服务器)构架类似,客户端使用IPC访问服务,而服务器端则实现具体的代码逻辑,通过IPC提供服务。唯一的区别是受限于调用时的行为模 式,一般,跨进程交互只提供串行访问。下面是一个典型的跨进程交互的实现:

       进程1提供客户端功能,而进程2提供服务器功能,在进程1里调用RPCFunc(1,2),实际上会触发到进程2里的 RPCFunc1Impl1(1,2)的执行。需要通过IPC机制在底层把这样的访问实现出来,这样在客户端进程空间里可以找到 RPCFunc1Stub()的定义,用于将函数调用解析为基于IPC的请求消息,而在服务器端则会RPCFunc1Skel()来监听所有的请求,然后 再具体调用请求转发到自己实现的RPCFunc1Impl()。当RPCFunc1Impl()执行完成之后,所返回的值则会经由IPC,再传回给客户 端,然后在客户端RPCFunc1()的return语言里返回。这样,从进程1的代码上来看,好像是完成了一次从RPCFunc1()到 RPCFunc1Impl()的远程过程调用(RPC),但在内容实现上,则是经历了1-6这样6个步骤的串行操作。之说以说是串行,因为这6个步骤会顺 序进行,进程1的执行RPCFunc1()这一函数时,直到第6步执行完成之前,都会被阻塞住。

通过通过串行实现后的这种特殊C/S框架, 因为跟我们传递的函数调用类似,只是提供了跨进程的函数调用,于是根据这样的行为特征,我们一般会叫它们为远程过程调用(Remote Procedure Call,简称RPC)。支撑起RPC环境的是IPC通信机制,我们也知道套接字(Socket)也是IPC机制一种,而TCP/IP是Socket机制 的一部分,于是很自然的,RPC也天生具备跨网络的能力,在实际的部署里,RPC一般会是网络透明的,通过RPC来进行访问的那端,并不会知道具体实现 RPC的会是本地资源或是网络上的资源。RPC是实现复杂功能的基础,特别是一些分布式系统设计里,比如我们Linux环境里的网络文件系统NFS、 AFS等,都是基于RPC,还有更高级一点,像我们的Corba环境、J2EE环境和WebService,都会使用RPC的变种。

拥有了 RPC通信能力之后,我们在编程上的局限性便大为减小,我们的代码可以很灵活地通过多进程的方式进行更安全的重构,而且还可以进行伸缩度非常良好的部署。 这点对于Android来说,尤为重要,因为我们的Android系统就是构建在基于多进程的“沙盒”模型之上的。在Android环境里,不存在基于网 络来进行RPC的需求(也不是没有,实际上Android有很多应用程序是基于社交网络API,或是Web Service来构建的,只是Android的基础系统是不需要进行跨网络交互),而是使用高性能的面向对象式的Binder,于是,我们的RPC,需要 通过RPC来构建。于是,简单的RPC通信流程,在Android系统里,则可以通过IBinder对象引用来完成,得到如下的实现逻辑:

我 们会通过IBinder,来实现从一个进程来访问另一个进程的对象,得到远程对象的引用之后,虽然在进程1里我们像是真正通过这一IBinder来访问远 程对象的某些方法,比如doXXX()方法,但实际上后续的执行逻辑则会被转到Binder IPC来发送访问进程,进程1在进入doXXX()方法之后,就会进入IBinder是否有返回的检测循环。当然此时由于IBinder设计上的精巧性, 此时进程实际上会休眠到/dev/binder设备的休眠队列里。而提供RPC的进程2则相反,它启动后会一直在IPC请求上进行循环监听,当有IPC请 求过来之后,则会将doXXX()的访问请求解析出来,访问这一方法,在访问完成之后,再将调用doXXX()的结果通过IBinder返回给发出调用请 求的进程1。这时,会唤醒进程1继续往下执行,从IPC上取回调用的返回值,然后再执行doXXX()之后的代码。

通过这种RPC机制,在 Android系统里,就可以更灵活地来设计交互过程,更方便地在多进程环境里进行低耦合化设计。在RPC交互的Server端的实现,可以灵活地根据自 己的实现或是调用上的需求,开放出来一部分的自己实现的接口,从而给多个Client端提供服务。

这些具体实现功能的部分,可以被称为 Server,但Server则容易让人联想到它将具备网络通信的能力,于是这种基于Binder能够提供RPC被调用能力的实现部分,在Android 里会被称为Service(在Android世界里,基本的功能组件的命名,Activity不等同于Frame、Windows,所以叫 Activity,Service也不等同于Server,于是被称为Service)。而根据Service是否提供RPC能力(方法级调用的能力), 又会被区分为本地Service与Remote Service。本地Service是基于Intent的,也可能在跨进程环境里被调用,但是在这种调用模型里,Service对象本身只在本地存在,不 会跨进程。

于是,Android里支持这种进程间的互相调用,剩下的问题就是支持基于Binder的这种RPC通信。见AIDL的设计

你可能感兴趣的:(android,service)