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

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


1.1实现Remote Service

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

private finalITaskService.Stub mTaskServiceBinder =new ITaskService.Stub() {

public int getPid() {

return 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实现如下:

package org.lianlab.services;

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

import android.os.Process;

public class TaskService extends Service {1

@Override

public IBinderonBind(Intent intent) {

if (ITaskService.class.getName().equals(intent.getAction())) {2

return mTaskServiceBinder;

}

return null;

}

private finalITaskService.Stub mTaskServiceBinder =new ITaskService.Stub() { 3

public int getPid() { 4

return Process.myPid();

}

};

}

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

<application

<service android:name=".TaskService">

<intent-filter>

<action android:name="org.lianlab.services.ITaskService"/>

</intent-filter>

</service>

</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文件,我们再来看看客户端的写法。

1.2访问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来完成这样的操 作,如下代码所示:

package org.lianlab.hello;

import android.os.Bundle;

import android.os.IBinder;

import android.os.RemoteException;

import android.app.Activity;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.content.ServiceConnection;

import android.util.Log;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.TextView;

import org.lianlab.services.ITaskService;1

import org.lianlab.hello.R;

public class Helloworld extends Activity

{

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

ITaskService mTaskService = null; 2

@Override

public voidonCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

bindService(new Intent(ITaskService.class.getName()),mTaskConnection,

Context.BIND_AUTO_CREATE); 3

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

new OnClickListener() {

@Override

public void onClick(Viewv) {

if (mTaskService !=null) {

try { 4

int mPid = -1;

mPid = mTaskService.getPid();

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

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

} catch(RemoteException e) {

e.printStackTrace();

}

}

else {

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

}

}

});

}

privateServiceConnectionmTaskConnection =new ServiceConnection() { 5

public void onServiceConnected(ComponentName className, IBinder service) {

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

}

public void onServiceDisconnected(ComponentName className) {

mTaskService = null;

}

};

@Override

public void onDestroy()

{

super.onDestroy();

if (mTaskService !=null) {

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帮我们完成了什么。

更多相关文章

  1. Android(安卓)P解决Socket通信Tcp粘包问题
  2. Android(安卓)App组件之Fragment说明和示例
  3. Android(安卓)性能优化之使用MAT分析内存泄露问题
  4. 9月26号 Android(安卓)SQLiteDatabase 的相关学习记录
  5. 20172321 2017-2018-2 《程序设计与数据结构》第11周学习总结
  6. Android(安卓)Fragment---创建Fragment
  7. Android(安卓)通过NTP服务器自动获取时间的方法
  8. Andorid第三方字体库导入
  9. RxJava使用(一)基本使用

随机推荐

  1. Android IPC原理分析小结
  2. Android java与html js交互 html视频播放
  3. android的图形系统
  4. 我的android第一课
  5. 4.0、Android(安卓)Studio配置你的构建
  6. 如何搭建简易蓝牙定位系统
  7. Android资源String中html标签的使用
  8. 在Android上调用OpenCV 2.4.10库函数
  9. 又一个博客园Android客户端(附APK下载)
  10. [Hi3751V811][Android8.0]系统按键的转换