天天看点

Service与Android系统设计

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

有了AIDL定义之后,我们便可实现这这一接口类的定义,主要是提供一个Stub类的实现,在这个Stub对象里,提供getPid(void)的具体实现。

    <b>private final</b>ITaskService.Stub mTaskServiceBinder =<b>new</b> ITaskService.Stub() {

       <b>public int</b> getPid() {

           <b>return</b> Process.myPid();

       }

    };

于 是,当应用程序通过某种方式可以取得ITaskServiceStub所对应的IBinder对象之后,就可以在自己的进程里调用IBinder对象的 getPid(),但这一方法实际上会是在别一个进程里执行。虽然我们对于执行IBinder的Stub端代码的执行环境并没有严格要求,可以在一个运行 中的进程或是线程里创建一个Stub对象就可以了,但出于通用性设计的角度考虑,实际上我们会使用Service类来承载这个Stub对象,通过这样的方 式,Stub的运行周期便被Service的生存周期所管理起来。这样实现的原因,我们可以回顾我们前面所描述的功耗控制部分。当然,使用Service 带来的另一个好处是代码的通用性更好,我们在后面将感受到这种统一化设计的好处。

于是,对应于.aidl的具体实现,我们一般会使用一个 Service对象把这个Stub对象的存活周期“包”起来。我们可以通过在Service里使用一个 final类型的Stub对象,也可以通过在onBind()接口里进行创建。这两种不同实现的结果是,使final类型的Stub对象,类似于我们的 Singleton设计模式,客户端访问过来的总会是由同一个Stub对象来处理;而在onBind()接口里创建新的Stub,可以让我们对每个客户端 的访问都新建一个Stub对象。出于简化设计的考虑,我们一般会使用final的唯一Stub对象,于是我们得到的完整的Service实现如下:

<b>package</b> org.lianlab.services;

<b>import</b> android.app.Service;

<b>import</b> android.content.Intent;

<b>import</b> android.os.IBinder;

<b>import</b> android.os.Process;

<b>public class</b> TaskService <b>extends</b> Service {1

    @Override

    <b>public</b> IBinderonBind(Intent intent) {

       <b>if</b> (ITaskService.<b>class</b>.getName().equals(intent.getAction())) {2

           <b>return</b> mTaskServiceBinder;

        <b>return null</b>;

    }

    <b>private final</b>ITaskService.Stub mTaskServiceBinder =<b>new</b> ITaskService.Stub() {  3

       <b>public int</b> getPid() { 4

}

1 由于实现上的灵活性,我们一般使用Service来承载一个AIDL的Stub对象,于是这个Stub的存活周期,会由Service的编写方式决定。当 我们的Stub对象是在onBind()里返回时,Stub对象的存活周期是Service处于Bounded的周期内;如果使用final限定,则 Stub对象的存活周期是Service在onCreate()到onDestroy()之间

2 用于处理bindService()发出Intent请求时的Action匹配,这一行一般会在AndroidManifest.xml文件里对 Service对Intent filter设置时使用。我们这种写法,则使这一Service只会对Intent里Action属性是 “org.lianlab.services.ITaskService”的bindService()请求作出响应。这一部分我们在后面的使用这一 Service的Activity里可以看到

3 创建ITaskService.Stub对象,并实现Stub对象所要求的方法,也就是AIDL的实现。Stub对象可以像我们这样静态创建,也可以在 onBind()里动态创建,但必须保证创建在onBind()返回之前完成。在onBind()回调之后,实际上在客户端则已经发生了 onServiceConnected()回调,会造成执行出错。

4 实现,这时我们最终给客户端提供的远程调用,就可以在Stub对象里实现。我们可以实现超出AIDL定义的部分,但只有AIDL里定义过的方法才会通过Binder暴露出来,而AIDL里定义的接口方法,则必须完整实现。

       有了这样的定义之后,我们还需要在AndroidManifest.xml文件里将Service声明出来,让系统里其他部分可以调用到:

    &lt;application

      …

       &lt;service android:name=".TaskService"&gt;

           &lt;intent-filter&gt;

                &lt;action android:name="org.lianlab.services.ITaskService"/&gt;

          &lt;/intent-filter&gt;

       &lt;/service&gt;

&lt;/application&gt;

在 AndroidManifest.xml文件里,会在&lt;application&gt;标签里通过&lt;service&gt;标签来申明这一应 用程序存在某个Service实现,在service命名里,如果service的名称与application名称的包名不符,我们还可以使用完整的 “包名+类名”这样命名方式来向系统里注册特殊的Service。在&lt;service&gt;标签里,我们也可以注册&lt;intent- filter&gt;来标明自己仅接收某一类的Intent请求,比如我们例子里的action会匹配 “org.lianlab.services.ITaskService”,如果没有这样的intent-filter,则任何以 TaskService(ComponentName的值是”org.lianlab.services.ITaskService”)为目标的 Intent请求会触发TaskService的onBind()回调。当然,我们在&lt;service&gt;标签内还可以定义一些权限,像我们例 子里的这个TaskService是没有任何权限限制的。有了这个AndroidManifest.xml文件,我们再来看看客户端的写法。

在 客户端进行访问时,由于它必须也使用同一接口类的定义,于是我们可以直接将同一.aidl文件拷贝到客户端应用程序的源代码里,让这些接口类的定义在客户 端代码里也可以被自动生成,然后客户端便可以自动得到Proxy端的代码。因为我们这里使用了Service,又是通过onBind()返回 IBinder的对象引用,这时客户端在使用IBinder之前,需要通过bindService()来触发Service端的onBind()回调事 件,这时会通过客户端的onServiceConnected()回调将Stub所对应的Binder对象返回。我们在稍后看AIDL的底层实现时会发 现,在此时客户端的Binder对象只是底层Binder IPC的引用,此时我们还需要创建一个基于这一Stub接口的Proxy,于是在客户端会需要调用asInterface()创建Proxy对象,这一 Proxy对象被转义成具体的Service,在我们的例子里,客户端此时就得到了ITaskService对象。从这时开始,在客户端里通过 ITaskService.getPid()的调用,都会通过Binder IPC将操作请求发送到Service端的Stub实现。于是,我们可以得到客户端的代码,在Android里我们一般用Activity来完成这样的操 作,如下代码所示:

<b>package</b> org.lianlab.hello;

<b>import</b> android.os.Bundle;

<b>import</b> android.os.RemoteException;

<b>import</b> android.app.Activity;

<b>import</b> android.content.ComponentName;

<b>import</b> android.content.Context;

<b>import</b> android.content.ServiceConnection;

<b>import</b> android.util.Log;

<b>import</b> android.view.View;

<b>import</b> android.view.View.OnClickListener;

<b>import</b> android.widget.TextView;

<b>import</b> org.lianlab.services.ITaskService;1

<b>import</b> org.lianlab.hello.R;

<b>public class</b> Helloworld <b>extends</b> Activity

{

    /** Called when the activity is first created.*/

    ITaskService mTaskService = <b>null</b>; 2

    <b>public void</b>onCreate(Bundle savedInstanceState)

    {

       <b>super</b>.onCreate(savedInstanceState);

       setContentView(R.layout.main);

           bindService(<b>new</b> Intent(ITaskService.<b>class</b>.getName()),mTaskConnection,

                    Context.BIND_AUTO_CREATE);   3

       ((TextView) findViewById(R.id.textView1)).setOnClickListener(

                <b>new</b> OnClickListener() {

                   @Override

                   <b>public void</b> onClick(Viewv) {

                       <b>if</b> (mTaskService !=<b>null</b>) {

                           <b>try</b> {      4

                               <b>int</b> mPid = -1;

                               mPid = mTaskService.getPid();  

                               Log.v("get Pid "," = " +mPid);

                               ((TextView)findViewById(R.id.textView1)).setText("Servicepid is" + mPid);

                           } <b>catch</b>(RemoteException e) {

                              e.printStackTrace();

                          }

                       }

                       <b>else</b> {

                           ((TextView)findViewById(R.id.textView1)).setText("Noservice connected");

                   }

          });

    <b>private</b>ServiceConnectionmTaskConnection =<b>new</b> ServiceConnection() {  5

       <b>public void</b> onServiceConnected(ComponentName className, IBinder service) {

           mTaskService = ITaskService.Stub.asInterface(service);  6

       <b>public void</b> onServiceDisconnected(ComponentName className) {

           mTaskService = <b>null</b>;

};

   @Override

    <b>public void</b> onDestroy()

       <b>super</b>.onDestroy();

       <b>if</b> (mTaskService !=<b>null</b>) {

           unbindService(mTaskConnection);  7

1 必须导入远端接口类的定义。我们必须先导入类或者函数定义,然后才能使用,对于任何编程语言都是这样。但我们在AIDL编程的环境下,实际这一步骤变得更 加简单,我们并非需要将Service实现的源文件拷贝到应用程序源代码里,也是只需要一个AIDL文件即可,这一文件会自动生成我们所需要的接口类定 义。所以可以注意,我们导入的并非Service实现的”org.lianlab.services.TaskService”,而AIDL接口类 的”org.lianlab.services.ITaskService”。这种方式更灵活,同时我们在AIDL环境下还得到了另一个好处,那就是可以 隐藏实现。

2 对于客户端来说,它并不知道TaskService的实现,于是我们统一使用ITaskService来处理对远程对象的引用。跟步骤1对应,这时我们会使用ITaskService来访问远程对象,就是我们的mTaskService。

3 我们必须先创建对象,才能调用对象里的方法,对于AIDL编程而言,所谓的创建对象,就是通过bindService()来触发另一个进程空间的Stub 对象被创建。bindService()的第一参数是一个Intent,这一Intent里可以通过ComponentName来指定使用哪个 Service,但此时我们会需要Service的定义,于是在AIDL编程里这一Intent会变通为使用ITaskService作为Action 值,这种小技巧将使bindService()操作会通过IntentFilter,帮我们找到合适的目标Service并将其绑定。 bindService()的第二个参数的类型是ServiceConnection对象,bindService()成功将使用这样一个 ServiceConnection对象来管理onServiceConnected()与onServiceDisconnected()两个回调,于 是一般我们会定义一个私有的ServiceConnection对象来作为这一参数,见5。最后的第三个参数是一个整形的标志,说明如何处理 bindService()请求,我们这里使用Context.BIND_AUTO_CREATE,则发生bindService()操作时,如果目标 Service不存在,会触发Service的onCreate()方法创建。

4 我们这里使用onClickListener对象来触发远程操作,当用户点击时,就会尝试去执行mTaskService.getPid()方法。正如我 们看到的,getPid()是一个RPC方法,会在另一个进程里执行,而Exception是无法跨进程捕捉的,如果我们希望在进行方法调用时捕捉执行过 程里的异常,我们就可以通过一个RemoteException来完成。RemoteException实际上跟方法的执行上下文没有关系,也并非完整的 Exception栈,但还是能帮助我们分析出错现场,所以一般在进行远端调用的部分,我们都会try来执行远程方法然后捕捉 RemoteException。

5 如3所述,bindService()会使用一个ServiceConnection对象来判断和处理是否连接正常,于是我们创建这么一个对象。因为这个 私有ServiceConnection对象是作为属性存在的,所以实际上在HelloActivity对象的初始化方法里便会被创建。

6 在onServiceConnected()这一回调方法里,将返回引用到远程对象的IBinder引用。在Android官方的介绍里,说是这一 IBinder对象需要通过asInterface()来进行类型转换,将IBinder再转换成ITaskService。但在实现上并非如此,我们的 Proxy在内部是被拆分成Proxy实现与Stub实现的,这两个实现都使用同一IBinder接口,我们在onServiceConnected() 里取回的就是这一对象IBinder引用,asInterface()实际上的操作是通过IBinder对象,得到其对应的Proxy实现。

7 通过bindService()方法来访问Service,则Service的生存周期位于bindService()与unbindService() 之间的Bounded区域,所以在bindService()之后,如果不调用unbindService()则会造成内存泄漏,Binder相关的资源 无法得到回收。所以在合适的点调用unbindService()是一种好习惯。

通过这样的方式,我们就得到了耦合度很低的Service 方案,我们的这个Activity它即可以与Service处于同一应用程序进程,也可以从另一个进程进行跨进程调用来访问这一Service里暴露出来 的方法。而在这种执行环境的变动过程中,代码完全不需要作Service处理上的变动,或者说Activity本身并不知道AIDL实现上的细节,在同一 个进程里还是不在同一进程里。

这种方式很简单易行,实际上在编程上,AIDL在编程上带来的额外编码上的开销非常小,但得到了灵活性设计的RPC调用。

接下来,我们看一下,AIDL帮我们完成了什么。

 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/1031112,如需转载请自行联系原作者

继续阅读