Service与Android系统实现(1)-- 应用程序里的Service(二)

来源:互联网 发布:js判断ie9以下版本 编辑:程序博客网 时间:2024/05/29 12:52

Remote Service

得益于Android的进程间模型,无论是系统实现端的开发,还是应用程序的开发者,可认为自己的代码都将在一种安全的环境下执行。但对于在需要共享的场合,又带来了不方便之处,我们不再可以在一个进程里很方便地调用到另一进程里的实现。在上面两种Service实现里,基本上只能自已服务自己,而不能将功能共享给更多地使用者。于是,Android在设计初期,便引入了另一个概念,Remote Service。

Remote Service定义了如何从一个进程,直接访问到另一个进程里实现的方法,这样的机制在通用编程领域被称为RPC,Remote Procedure Call,在Android环境里,因为这样的RPC与Service的概念揉合到了一起,所以被称为Remote Service。

如下图所示,Remote Service基本上算是在Bounded Service实现上的一种拓展。调用端(一般是与用户交互的Activity)会通过bindService()发一个Intent到Service进程,而Service进程会在onBind()回调方法里返回自己的Service处理接口。当调用端得到相应的处理接口,也就是下图所示的Stub.Proxy对象之后,就可以调用远端Stub对象里实现的方法。


当然,由于这种方式需要在每一次调用时都进行一次远程调用,于是实现起来并不简单,甚至可以说是会很麻烦。在上图逻辑里要实现一个Remote Service,在这一Remote Service里实现一个方法,就需要加入多个对象。于是,在Android里引入了AIDL,可以通过AIDL来简化通用代码的处理,在上图中Stub对象、Stub.Proxy对象都将由aidl自动管理。

实现一个Remote Service,基本上可以分三步完成:

  1.  定义aidl
  2.  实现被调用的Remote Service(提供可实例化的Stub对象)
  3.  调用Remote Service

定义AIDL

引入了AIDL文件之后,在编码时就会更加简洁,比如加入一个接口类ITaskService,而在ITaskService里提供一个getPid()方法:
[java] view plaincopy
  1. packageorg.lianlab.service;  
  2. interface ITaskService {  
  3.     int getPid ( );  
  4. }  

实现Remote Service

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

[java] view plaincopy
  1. private finalITaskService.Stub mTaskServiceBinder =new ITaskService.Stub() {  
  2.    public int getPid() {  
  3.        return Process.myPid();  
  4.    }  
  5. };  

于是,当应用程序通过某种方式可以取得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实现如下:

[java] view plaincopy
  1. package org.lianlab.services;  
  2. import android.app.Service;  
  3. import android.content.Intent;  
  4. import android.os.IBinder;  
  5. import android.os.Process;  
  6. public class TaskService extends Service {1  
  7.     @Override  
  8.     public IBinderonBind(Intent intent) {  
  9.        if (ITaskService.class.getName().equals(intent.getAction())) {2  
  10.            return mTaskServiceBinder;  
  11.        }  
  12.         return null;  
  13.     }  
  14.    
  15.     private finalITaskService.Stub mTaskServiceBinder =new ITaskService.Stub() {  3  
  16.        public int getPid() { 4  
  17.            return Process.myPid();  
  18.        }  
  19.     };  
  20. }  

  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声明出来,让系统里其他部分可以调用到:

[html] view plaincopy
  1. <application  
  2.       …  
  3.        <service android:name=".TaskService">  
  4.            <intent-filter>  
  5.                 <action android:name="org.lianlab.services.ITaskService"/>  
  6.           </intent-filter>  
  7.        </service>  
  8. </application>  

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

访问Remote Service

在客户端进行访问时,由于它必须也使用同一接口类的定义,于是我们可以直接将同一.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来完成这样的操作,如下代码所示:

[java] view plaincopy
  1. package org.lianlab.hello;  
  2. import android.os.Bundle;  
  3. import android.os.IBinder;  
  4. import android.os.RemoteException;  
  5. import android.app.Activity;  
  6. import android.content.ComponentName;  
  7. import android.content.Context;  
  8. import android.content.Intent;  
  9. import android.content.ServiceConnection;  
  10. import android.util.Log;  
  11. import android.view.View;  
  12. import android.view.View.OnClickListener;  
  13.    
  14. import android.widget.TextView;  
  15.    
  16. import org.lianlab.services.ITaskService;1  
  17. import org.lianlab.hello.R;  
  18.    
  19. public class Helloworld extends Activity  
  20. {  
  21.     /** Called when the activity is first created.*/  
  22.    
  23.     ITaskService mTaskService = null2  
  24.    
  25.     @Override  
  26.     public voidonCreate(Bundle savedInstanceState)  
  27.     {  
  28.        super.onCreate(savedInstanceState);  
  29.        setContentView(R.layout.main);  
  30.    
  31.            bindService(new Intent(ITaskService.class.getName()),mTaskConnection,  
  32.                     Context.BIND_AUTO_CREATE);   3  
  33.          
  34.        ((TextView) findViewById(R.id.textView1)).setOnClickListener(  
  35.                 new OnClickListener() {  
  36.                    @Override  
  37.                    public void onClick(Viewv) {  
  38.                        if (mTaskService !=null) {  
  39.                            try {      4  
  40.                                int mPid = -1;  
  41.                                mPid = mTaskService.getPid();    
  42.                                Log.v("get Pid "," = " +mPid);  
  43.                                ((TextView)findViewById(R.id.textView1)).setText("Servicepid is" + mPid);  
  44.                            } catch(RemoteException e) {  
  45.                               e.printStackTrace();  
  46.                           }  
  47.                        }  
  48.                        else {  
  49.                            ((TextView)findViewById(R.id.textView1)).setText("Noservice connected");  
  50.                        }  
  51.                    }  
  52.           });  
  53.    
  54.     }  
  55.    
  56.     privateServiceConnectionmTaskConnection = new ServiceConnection() {  5  
  57.        public void onServiceConnected(ComponentName className, IBinder service) {  
  58.            mTaskService = ITaskService.Stub.asInterface(service);  6  
  59.        }  
  60.    
  61.        public void onServiceDisconnected(ComponentName className) {  
  62.            mTaskService = null;  
  63.        }  
  64. };  
  65.    
  66.    @Override  
  67.     public void onDestroy()  
  68.     {  
  69.        super.onDestroy();  
  70.        if (mTaskService !=null) {  
  71.            unbindService(mTaskConnection);  7  
  72.        }  
  73.     }  
  74.      
  75. }  

  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调用。