初学者必知的AIDL在应用层上的Binder机制
事先说明:
本人也是个初学者,所以本文是从初学者的角度入手,如果有不妥的地方请留言教导我,谢谢。
本篇文章主要针对讲解AIDL的使用和AIDL在应用层上原理解析,其中也会提到在写AIDL服务中的一些错误。
首先得理解几个概念:
IPC:Inter-Process Communication,进程间的通信或跨进程通信。简单点理解,一个应用可以存在多个进程,但需要数据交换就必须用IPC;或者是二个应用之间的数据交换。
Binder:Binder是Android的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式。通过这个Binder对象,客户端就可以获取服务端提供的服务或数据,这里的服务包括普通服务和基于AIDL的服务。
AIDL:Android Interface Definition language,它是一种Android内部进程通信接口的描述语言。
欢迎关注我的CSDN博客,Hensen_的博客,http://blog.csdn.net/qq_30379689
步骤一:AIDL的使用(Android Studio中)
首先我们来看一个我画的图:
服务端:
创建一个服务端工程,在工程中点击右键New->AIDL->AIDL File,默认直接点确定,这时会在工程中出现一个aidl文件:
我们打开这个aidl文件,我们创建一个我们需要测试的方法:
由于Android Studio是要手动编译才能生成对应AIDL的java文件,既然aidl文件是个接口,那就必须存在着实现这个接口的类,点击编译,系统自动生成一个java类,该java类的代码就是整个Binder机制的原理所在(会在下面第二步骤介绍原理):
既然是个服务端,那么我们就要开始写服务了,创建一个类,继承Service:
import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.support.annotation.Nullable; import android.util.Log; import com.handsome.boke.IMyAidlInterface; public class MyService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return myS; } private IBinder myS = new IMyAidlInterface.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public int add(int num1, int num2) throws RemoteException { Log.i("Hensen", "从客户端发来的AIDL请求:num1->" + num1 + "::num2->" + num2); return num1 + num2; } }; }
既然是个服务,就必须在manifests文件中配置:
<!--exported:允许外界访问该服务,AIDL必备条件--> <service android:name=".Aidl.MyService" android:exported="true"/>到现在服务端写好了,开启模拟器启动这个程序,记得在代码中开启服务:
public class LoginActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); startService(new Intent(this, MyService.class)); } }
在工程中点击右键New->Module,按默认确定,finish:
关键的一步来了,复制服务端的aidl整个文件夹(包括里面的包、aidl文件、完整无缺)粘贴到客户端对应放aidl的地方:
不要忘了,客户端还要手动编译:
好了我们来写客户端的代码(我们在MainActivity中放一个”AIDL“的按钮,先绑定服务,然后点击按钮调用):
public class MainActivity extends AppCompatActivity { IMyAidlInterface iMyAidlInterface; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //绑定服务 Intent intent = new Intent(); intent.setComponent(new ComponentName("com.handsome.boke", "com.handsome.boke.Aidl.MyService")); bindService(intent, conn, BIND_AUTO_CREATE); } /** * 点击“AIDL”按钮事件 * * @param view */ public void add(View view) { try { int res = iMyAidlInterface.add(1, 2); Log.i("Hensen", "从服务端调用成功的结果:" + res); } catch (RemoteException e) { e.printStackTrace(); } } /** * 服务回调方法 */ private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { iMyAidlInterface = null; } }; @Override protected void onDestroy() { super.onDestroy(); //解绑服务,回收资源 unbindService(conn); } }
08-19 10:59:34.548 6311-6328/com.handsome.boke I/Hensen: 从客户端发来的AIDL请求:num1->1::num2->2
08-19 10:59:34.550 7052-7052/com.handsome.app2 I/Hensen: 从服务端调用成功的结果:3
步骤二:AIDL的Binder机制原理分析
还是来看一下我画的图:
分析原理:
/* * This file is auto-generated. DO NOT MODIFY. * Original file: D:\\workspace5\\Boke\\app\\src\\main\\aidl\\com\\handsome\\boke\\IMyAidlInterface.aidl */ package com.handsome.boke; // Declare any non-default types here with import statements public interface IMyAidlInterface extends android.os.IInterface { /** * Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.handsome.boke.IMyAidlInterface { private static final java.lang.String DESCRIPTOR = "com.handsome.boke.IMyAidlInterface"; /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.handsome.boke.IMyAidlInterface interface, * generating a proxy if needed. */ public static com.handsome.boke.IMyAidlInterface asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.handsome.boke.IMyAidlInterface))) { return ((com.handsome.boke.IMyAidlInterface) iin); } return new com.handsome.boke.IMyAidlInterface.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_basicTypes: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); long _arg1; _arg1 = data.readLong(); boolean _arg2; _arg2 = (0 != data.readInt()); float _arg3; _arg3 = data.readFloat(); double _arg4; _arg4 = data.readDouble(); java.lang.String _arg5; _arg5 = data.readString(); this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5); reply.writeNoException(); return true; } case TRANSACTION_add: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.handsome.boke.IMyAidlInterface { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(anInt); _data.writeLong(aLong); _data.writeInt(((aBoolean) ? (1) : (0))); _data.writeFloat(aFloat); _data.writeDouble(aDouble); _data.writeString(aString); mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public int add(int num1, int num2) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(num1); _data.writeInt(num2); mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException; public int add(int num1, int num2) throws android.os.RemoteException; }
欢迎关注我的CSDN博客,Hensen_的博客,http://blog.csdn.net/qq_30379689
首先本身继承Iinterface,所以他也是个接口,接口中必须有方法,代码定位到结尾有2个方法。
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException; public int add(int num1, int num2) throws android.os.RemoteException;这两个方法就是basicTypes和add,就是我们服务端的2个方法。
接着发现该接口中有1个内部类Stub,继承自本身(IMyAidlInterface)接口,代码定位到Stub类。
这个Stub有个构造方法、asInterface、asBinder、onTransact(先不介绍)。
接着发现该内部类Stub还有一个内部类,代码定位到Proxy(我们把它称为代理)类,也是继承自本身(IMyAidlInterface)接口,所以实现该接口的两个方法。
/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(anInt); _data.writeLong(aLong); _data.writeInt(((aBoolean) ? (1) : (0))); _data.writeFloat(aFloat); _data.writeDouble(aDouble); _data.writeString(aString); mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public int add(int num1, int num2) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(num1); _data.writeInt(num2); mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } }在这个类里面我们会发现有2个标识:用来区分两个方法,到底你远程请求哪个方法的唯一标识,代码定位到代理类的结尾。
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);回过头来,还记得我们客户端做了什么吗?答案:绑定一个服务,在回调方法获取一个接口(iMyAidlInterface),它是直接静态使用IMyAidlInterface里面的静态类Stub的asInterface的方法:(好了我们去跟踪到Stub类asInterface这个方法)
/** * 服务回调方法 */ private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { iMyAidlInterface = null; } };代码定位到Stub类asInterface方法:
public static com.handsome.boke.IMyAidlInterface asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.handsome.boke.IMyAidlInterface))) { return ((com.handsome.boke.IMyAidlInterface) iin); } return new com.handsome.boke.IMyAidlInterface.Stub.Proxy(obj); }前面只是做一些判断、看一下最后一句话:我们将传过来的obj还是传给了它的代理类来处理,返回的是代理类的对象
return new com.handsome.boke.IMyAidlInterface.Stub.Proxy(obj);所以在客户端的iMyAidlInterface = ……,则是拿到它的代理类,好了,这个时候就看客户端调用代理类干嘛了:
int res = iMyAidlInterface.add(1, 2);他调用了代理类的add方法,代码定位到代理类的add方法:
@Override public int add(int num1, int num2) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(num1); _data.writeInt(num2); mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; }你会发现,它把数据写进了_data里面,最后调用transact方法,传入_data数据,唯一标识Stub.TRANSACTION_add。
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);然后这个transact方法就是通过底层了,通过底层结束后,这些参数送到哪了?答案:底层会走到stub类中的onTransact方法,通过判断唯一标识,确定方法:
case TRANSACTION_add: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; }在这个地方将传过来的参数解包,readInt方法。然后调用this.add方法,this指的就是服务端,调用服务端的add的方法:
int _result = this.add(_arg0, _arg1);将得到的结果,写入reply:
reply.writeNoException(); reply.writeInt(_result);最后一句话,最后返回系统的ontransact方法,传入结果reply:
return super.onTransact(code, data, reply, flags);所以我们在上面获得的结果就是reply(答案:3):
int res = iMyAidlInterface.add(1, 2);最后还是看我的画图,加深一下理解:欢迎关注我的CSDN博客,Hensen_的博客,http://blog.csdn.net/qq_30379689
步骤三:AIDL编写时候的一些错误
错误一:
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'int com.handsome.boke.IMyAidlInterface.add(int, int)' on a null object reference这个错误很有可能是你写的服务端,忘记返回myS了,返回的是个null
@Override public IBinder onBind(Intent intent) { return null; }错误二:
Caused by: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.handsome.boke/.Aidl.MyService }这个错误很有可能是的服务端在manifests文件中少了exported="true"的属性
<!--exported:允许外界访问该服务,AIDL必备条件--> <service android:name=".Aidl.MyService"/>