Android进程间Binder通信机制

本文首先概述了Android的进程间通信的Binder机制,然后结合一个AIDL的例子,对Binder机制进行了解析。

概述

        我们知道,在Android app中的众多activity,service等组件可以运行在同一进程中,也可以运行在不同进程中。当组件运行在同一进程中进行通信就显得比较简单,在之前的Android线程间通信机制中已经讲过了;而当它们运行在不同的进程中时,就需要使用我们本文中所要介绍的Binder机制了。

        Binder作为一种进程间通信机制,负责提供远程调用的功能(RPC),它的系统组件主要包括四种:Client, Server, ServiceManager, Binder Driver. 它们之间的关系如下图所示:

 

        从图中我们可以看出,Client, Server, ServiceManager运行在系统的用户态,而Binder Driver运行在内核态。为了完成Client端到Server端的通信任务,用户空间的需要操作Binder Driver提供的/dev/binder文件来完成交互。那么ServiceManager的工作是什么呢?ServiceManager负责管理Server并向Client端提供一个Server的代理接口(proxy)。通过代理接口中定义的方法,Client端就可以使用Server端提供的服务了。整个过程如下:

    • Client端调用代理接口的方法,将Client的参数打包为parcel对象发送给内核空间中BinderDriver;
    • Server端读取到BinderDriver中的请求数据,将parcel对象解包并处理;
    • 处理好后,将处理结果打包返回给BinderDriver,再交给Client端。

       另外,Client端与Server端的调用过程是同步的,即在Server返回结果之前,Client端是阻塞的。调用过程如下所示:

 

        OK,下面我们通过AIDL的一个例子来分析一下以上过程时如何进行的。

AIDL小例子

       首先,创建一个aidl文件“ICalculateAIDL.aidl”,这个接口里面定义了要对外提供的服务,我们这里定义了计算加法和减法的函数:

1 package com.cqumonk.calculate.aidl;
2 
3 interface ICalculateAIDL {
4     int add(int a,int b);
5     int minus(int a,int b);
6 }

       创建好后,rebuild一下我们的项目,可以生成同名的java文件。这是一个接口,里面包含了一个名为Stub的静态抽象类,以及我们在aidl文件中定义的加减法函数。里面详细代码在后面分析,接口文件结构如下:

image

        然后我们需要实现服务端方面的功能,创建一个service,我们顺手把生命周期中各方法都打印出来,并完成加法和减法函数的实现:

 1 public class CalculateService extends Service {
 2     private static final String TAG="SERVER";
 3     public CalculateService() {
 4     }
 5 
 6     @Override
 7     public void onCreate() {
 8         super.onCreate();
 9         Log.e(TAG,"OnCreate");
10     }
11 
12     @Override
13     public int onStartCommand(Intent intent, int flags, int startId) {
14         Log.e(TAG,"onStartCommand");
15         return super.onStartCommand(intent, flags, startId);
16     }
17 
18     @Override
19     public IBinder onBind(Intent intent) {
20         Log.e(TAG,"onBind");
21         return mBinder;
22     }
23 
24 
25     @Override
26     public void onDestroy() {
27         super.onDestroy();
28         Log.e(TAG,"onDestroy");
29     }
30 
31     @Override
32     public boolean onUnbind(Intent intent) {
33         Log.e(TAG,"onUnbind");
34         return super.onUnbind(intent);
35     }
36 
37     @Override
38     public void onRebind(Intent intent) {
39         Log.e(TAG,"onRebind");
40         super.onRebind(intent);
41     }
42 
43     private final ICalculateAIDL.Stub mBinder=new ICalculateAIDL.Stub(){
44         @Override
45         public int add(int a, int b) throws RemoteException {
46             return a+b;
47         }
48 
49         @Override
50         public int minus(int a, int b) throws RemoteException {
51             return a-b;
52         }
53     };
54 }

       在service中,我们使用刚刚生成的ICalculateAIDL.Stub静态抽象类创建了一个Binder对象,实现了其中有关计算的业务方法,并在onBind方法中返回它。另外我们还需要在manifest文件中对该服务进行注册:

1 <service
2             android:name=".CalculateService"
3             android:enabled="true"
4             android:exported="true" >
5             <intent-filter>
6                 <action android:name="com.cqumonk.adil.calculate"/>
7                 <category android:name="android.intent.category.DEFAULT"/>
8             </intent-filter>
9         </service>

        这时候,我们的服务端工作就完成了。我们开始编写客户端的代码来调用这个服务。首先,我们定义一个activity,包含四个按钮:

image

        然后我们可以在activity中启动之前定义好的service并调用它所提供的服务:

 1 public class MainActivity extends Activity implements View.OnClickListener {
 2 
 3     Button mBind;
 4     Button mUnbind;
 5     Button mAdd;
 6     Button mMinus;
 7     TextView txt_res;
 8     private static final String TAG="CLIENT";
 9     private ICalculateAIDL mCalculateAIDL;
10     private boolean binded=false;
11     @Override
12     protected void onCreate(Bundle savedInstanceState) {
13         super.onCreate(savedInstanceState);
14         setContentView(R.layout.activity_main);
15         mBind= (Button) findViewById(R.id.btn_bind);
16         mUnbind= (Button) findViewById(R.id.btn_unbind);
17         mAdd= (Button) findViewById(R.id.btn_add);
18         mMinus= (Button) findViewById(R.id.btn_minus);
19         txt_res= (TextView) findViewById(R.id.txt_res);
20 
21         mBind.setOnClickListener(this);
22         mUnbind.setOnClickListener(this);
23         mAdd.setOnClickListener(this);
24         mMinus.setOnClickListener(this);
25     }
26 
27     @Override
28     protected void onStop() {
29 
30         super.onStop();
31         unbind();
32     }
33 
34     private void unbind(){
35         if (binded){
36             unbindService(mConnection);
37             binded=false;
38         }
39     }
40 
41     @Override
42     public void onClick(View v) {
43         int id=v.getId();
44         switch (id){
45             case R.id.btn_bind:
46                 Intent intent=new Intent();
47                 intent.setAction("com.cqumonk.adil.calculate");
48                 bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
49                 break;
50             case R.id.btn_unbind:
51                 unbind();
52                 break;
53             case R.id.btn_add:
54                 if(mCalculateAIDL!=null){
55                     try {
56                         int res=mCalculateAIDL.add(3,3);
57                         txt_res.setText(res+"");
58                     } catch (RemoteException e) {
59                         e.printStackTrace();
60                     }
61                 }else{
62                     Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show();
63                 }
64                 break;
65             case R.id.btn_minus:
66                 if(mCalculateAIDL!=null){
67                     try {
68                         int res=mCalculateAIDL.minus(9,4);
69                         txt_res.setText(res+"");
70                     } catch (RemoteException e) {
71                         e.printStackTrace();
72                     }
73                 }else{
74                     Toast.makeText(this,"please rebind",Toast.LENGTH_SHORT).show();
75                 }
76                 break;
77         }
78     }
79     private ServiceConnection mConnection=new ServiceConnection() {
80         @Override
81         public void onServiceConnected(ComponentName name, IBinder service) {
82             Log.e(TAG,"connect");
83             binded=true;
84             mCalculateAIDL=ICalculateAIDL.Stub.asInterface(service);
85 
86         }
87 
88         @Override
89         public void onServiceDisconnected(ComponentName name) {
90             Log.e(TAG,"disconnect");
91             mCalculateAIDL=null;
92             binded=false;
93         }
94     };
95 }

        当我们点击绑定按钮,观察日志:

image

        点击加法和减法按钮,都可以完成计算并返回值。然后点击解绑按钮:

image

        我们并未发现有连接断开的日志打印,这时候,我们继续点击加减法按钮,发现仍然可以完成计算功能,这又是为毛呢?我们看到unbind和destroy的日志打印,说明连接已经断开,service已经被销毁,但是我们返回的stub对象仍然是可以继续使用的。而并不是说service仍然在运行。

过程分析

        首先,我们调用bindservice方法来启动了service:

        一方面连接成功时调用了serviceConnection的onServiceConnected方法。我们从该方法中获取到一个Binder对象(注意,这个binder并不是我们在service中实现的那个哦。当service时本apk中的service时,这里返回的是同一个binder),我们通过此binder来与server进行通信。为了区分,我们称为clientBinder。

1 public void onServiceConnected(ComponentName name, IBinder service) {
2             Log.e(TAG,"connect");
3             binded=true;
4             mCalculateAIDL= ICalculateAIDL.Stub.asInterface(service);       
5 }

        另一方面,onBind方法返回了我们实现的Stub对象,其实也是一个Binder,用于和Client进行通信。(之前我们定义了一个aidl接口文件,并根据它生成了ICalculateAIDL接口。这个接口中有我们定义的两个方法以及一个静态抽象内部类Stub,它是一个Binder的子类。)我们来看一下Stub是如何定义的:

1 public static abstract class Stub extends android.os.Binder implements com.cqumonk.calculate.aidl.ICalculateAIDL

       它继承了Binder类也实现了ICalculateAIDL接口,我们实现了定义的add和minus方法。我们仍然要强调一下它并不是clientBinder,在负责与clientBinder进行通信交互的同时,它也维护了service描述符与服务端service的映射。

 

        我们在Client中的onServiceConnected里调用了stub对象的asInterface方法,并将之前得到的clientBinder传入:

 1 public static com.cqumonk.calculate.aidl.ICalculateAIDL asInterface(android.os.IBinder obj) {
 2             if ((obj == null)) {
 3                 return null;
 4             }
 5             android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//根据包名获取本地实现的一个接口的实例,如果是本地service则可以获取到
 6             if (((iin != null) && (iin instanceof com.cqumonk.calculate.aidl.ICalculateAIDL))) {
 7                 return ((com.cqumonk.calculate.aidl.ICalculateAIDL) iin);  //如果得到的实例是ICalculateAIDL的对象,则返回
 8             }
 9             return new com.cqumonk.calculate.aidl.ICalculateAIDL.Stub.Proxy(obj);//如果无法得到本地实现的对象则会返回一个代理对象
10 }

        在这个方法中,首先在系统中查找注册的的service,如果没有找到,那么一定是别的apk实现的service,于是返回一个此service的静态代理类对象供Client调用。我们来看一下这个代理,创建时我们将clientBinder传入了,同时也它实现了ICalculateAIDL接口:

 1 private static class Proxy implements com.cqumonk.calculate.aidl.ICalculateAIDL {
 2             private android.os.IBinder mRemote;
 3 
 4             Proxy(android.os.IBinder remote) {
 5                 mRemote = remote;
 6             }
 7 
 8             @Override
 9             public android.os.IBinder asBinder() {
10                 return mRemote;
11             }
12 
13             public java.lang.String getInterfaceDescriptor() {
14                 return DESCRIPTOR;
15             }
16 
17             @Override
18             public int add(int a, int b) throws android.os.RemoteException {
19                 android.os.Parcel _data = android.os.Parcel.obtain();
20                 android.os.Parcel _reply = android.os.Parcel.obtain();
21                 int _result;
22                 try {
23                     _data.writeInterfaceToken(DESCRIPTOR);
24                     _data.writeInt(a);  //将参数打包
25                     _data.writeInt(b);
26                     mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);  //调用binderDriver的提供的方法将参数发给服务端
27                     _reply.readException();
28                     _result = _reply.readInt();  //读取到返回结果
29                 } finally {
30                     _reply.recycle();
31                     _data.recycle();
32                 }
33                 return _result;
34             }
35 
36             @Override
37             public int minus(int a, int b) throws android.os.RemoteException {
38                 android.os.Parcel _data = android.os.Parcel.obtain();
39                 android.os.Parcel _reply = android.os.Parcel.obtain();
40                 int _result;
41                 try {
42                     _data.writeInterfaceToken(DESCRIPTOR);
43                     _data.writeInt(a);
44                     _data.writeInt(b);
45                     mRemote.transact(Stub.TRANSACTION_minus, _data, _reply, 0);
46                     _reply.readException();
47                     _result = _reply.readInt();
48                 } finally {
49                     _reply.recycle();
50                     _data.recycle();
51                 }
52                 return _result;
53             }
54         }

       代理中也实现了ICalculateAIDL接口定义的方法,我们以add方法为例,里面将参数打包发送给Server端。在Server端收到请求后,会调用service中我们实现的那个stub对象(mBinder)的onTransact方法:

 1 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
 2             switch (code) {
 3                 case INTERFACE_TRANSACTION: {
 4                     reply.writeString(DESCRIPTOR);
 5                     return true;
 6                 }
 7                 case TRANSACTION_add: {
 8                     data.enforceInterface(DESCRIPTOR);
 9                     int _arg0;
10                     _arg0 = data.readInt();
11                     int _arg1;
12                     _arg1 = data.readInt();
13                     int _result = this.add(_arg0, _arg1); //调用我们实现好的方法
14                     reply.writeNoException();
15                     reply.writeInt(_result); //把结果返回
16                     return true;
17                 }
18                 case TRANSACTION_minus: {
19                     data.enforceInterface(DESCRIPTOR);
20                     int _arg0;
21                     _arg0 = data.readInt();
22                     int _arg1;
23                     _arg1 = data.readInt();
24                     int _result = this.minus(_arg0, _arg1);
25                     reply.writeNoException();
26                     reply.writeInt(_result);
27                     return true;
28                 }
29             }
30             return super.onTransact(code, data, reply, flags);
31         }

调用完成后把结果打包返回给Poxy处理,最后返回给客户端。

总结

        由上面的例子我们可以看出,在跨进程通信的时候,Client端使用的Poxy里面封装了一个binder与Server端的stub(也是一个binder对象)进行交互,两个binder作为接口调用BinderDriver的transact来发送数据包,以及onTransact接收处理数据包。

posted @ 2015-08-29 21:17  -小城-  阅读(204)  评论(0编辑  收藏  举报