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对象里实现的方法。

android removeview失败 android remote service_Android

当然,由于这种方式需要在每一次调用时都进行一次远程调用,于是实现起来并不简单,甚至可以说是会很麻烦。在上图逻辑里要实现一个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 plain copy


1. packageorg.lianlab.service;  
2. interface
3. int
4. }


实现Remote Service

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


[java] view plain copy

1. private finalITaskService.Stub mTaskServiceBinder =new
2. public int
3. return
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 plain copy

1. package
2. import
3. import
4. import
5. import
6. public class TaskService extends Service {1
7. @Override
8. public
9. if (ITaskService.class.getName().equals(intent.getAction())) {2
10. return
11.        }  
12. return null;  
13.     }  
14.    
15. private finalITaskService.Stub mTaskServiceBinder =new ITaskService.Stub() {  3
16. public int getPid() { 4
17. return
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 plain copy

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 plain copy


1. package
2. import
3. import
4. import
5. import
6. import
7. import
8. import
9. import
10. import
11. import
12. import
13.    
14. import
15.    
16. import org.lianlab.services.ITaskService;1
17. import
18.    
19. public class Helloworld extends
20. {  
21. /** Called when the activity is first created.*/
22.    
23. null; 2
24.    
25. @Override
26. public
27.     {  
28. super.onCreate(savedInstanceState);  
29.        setContentView(R.layout.main);  
30.    
31. new Intent(ITaskService.class.getName()),mTaskConnection,  
32. 3
33.          
34.        ((TextView) findViewById(R.id.textView1)).setOnClickListener(  
35. new
36. @Override
37. public void
38. if (mTaskService !=null) {  
39. try {      4
40. int mPid = -1;  
41.                                mPid = mTaskService.getPid();    
42. "get Pid "," = "
43. "Servicepid is"
44. catch(RemoteException e) {  
45.                               e.printStackTrace();  
46.                           }  
47.                        }  
48. else
49. "Noservice connected");  
50.                        }  
51.                    }  
52.           });  
53.    
54.     }  
55.    
56. new ServiceConnection() {  5
57. public void
58. 6
59.        }  
60.    
61. public void
62. null;  
63.        }  
64. };  
65.    
66. @Override
67. public void
68.     {  
69. super.onDestroy();  
70. if (mTaskService !=null) {  
71. 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调用。