Binder机制学习
Binder机制是Android进行IPC(进程间通信)的主要方式 Binder跨进程通信机制:基于C/S架构,由Client、Server、ServerManager和Binder驱动组成。 进程空间分为用户空间和内核空间。用户空间不可以进行数据交互;内核空间可以进行数据交互,所有进程共用 一个内核空间 Client、Server、ServiceManager均在用户空间中实现,而Binder驱动程序则是在内核空间中实现的;
·为何新增Binder来作为主要的IPC方式
Android也是基于Linux内核,Linux现有的进程通信手段有管道/消息队列/共享内存/套接字/信号量。
既然有现有的IPC方式,为什么重新设计一套Binder机制呢?
主要是出于以上三个方面的考量:
1、效率:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度 分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷 贝到接收方的缓存区,一共两次拷贝。 一次数据传递需要经历:用户空间 –> 内核缓存区 –> 用户空间,需要2次数据拷贝,这样效率不高。 而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同 一块物理地址的,节省了一次数据拷贝的过程 : 共享内存不需要拷贝,Binder的性能仅次于共享内存。
2、稳定性:上面说到共享内存的性能优于Binder,那为什么不采用共享内存呢,因为共享内存需要处理并发同 步问题,容易出现死锁和资源竞争,稳定性较差。 Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较 好。
3、安全性:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制 为每个进程分配了UID/PID,且在Binder通信时会根据UID/PID进行有效性检测。
2. binder是什么?
从进程间通信的角度看,Binder 是一种进程间通信的机制;
从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象(Binder类 IBinder);
从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理
从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会自动完成代理对象和本地对象之间 的转换。 从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁
3.Binder 跨进程通信机制 模型
·模型原理图:
Binder
跨进程通信机制 模型 基于Client - Server
模式· 模型组成角色说明
4.Binder驱动的作用 & 原理:
模型工作原理:
模型原理步骤说明
注意:
Client进程、Server进程 & Service Manager 进程之间的交互 都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互 原因:Client进程、Server进程 & Service Manager进程属于进程空间的用户空间,不可进行进程间交互 Binder驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互
Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现) 所以,在进行跨进程通信时,开发者只需自定义Client & Server 进程 并 显式使用上述3个步骤,最终借助 Android的基本架构功能就可完成进程间通信
# Binder请求的线程管理 Server进程会创建很多线程来处理Binder请求 Binder模型的线程管理 采用Binder驱动的线程池,并由Binder驱动自身进行管理,而不是由Server进程来管理的 一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。 所以,在进程间通信时处理并发问题时,如使用ContentProvider时,它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程同时工作
利用binder进行通信的实现代码Demo:
1. 直接利用Binder的transact实现:
server端:(注意,要在注册清单中注册,并且标注进程号(在其他进程中运行))
public class IPCService extends Service { private static final String DESCRIPTOR = "IPCService"; private final String[] names = {"B神","艹神","基神","J神","翔神"}; private MyBinder mBinder = new MyBinder(); private class MyBinder extends Binder { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code){ case 0x001: { Log.d("TAG", "MyBinder Switch块 -----" + android.os.Process.myPid()); data.enforceInterface( "IPCService"); int num = data.readInt(); int num1 = data.readInt(); int num2 = data.readInt(); String test = data.readString(); reply.writeNoException(); reply.writeString(names[num] + " " + android.os.Process.myPid() + " " + num1 + " " + num2 + " " + test); reply.writeInt(1); reply.writeString("收到"); return true; } } Log.d("TAG", "MyBinder OnTransact块 ----- " + android.os.Process.myPid()); return super.onTransact(code, data, reply, flags); } } @Override public IBinder onBind(Intent intent) { return mBinder; } } /** * AndroidManifest.xml <service android:name="net.binderlearning.IPCService" android:process=".myservice"/> */
client:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private EditText edit_num; private Button btn_query; private TextView txt_result; private IBinder mIBinder; private ServiceConnection PersonConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { mIBinder = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mIBinder = service; Log.d("TAG", "客户端-----" + android.os.Process.myPid()); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); //绑定远程Service Intent service = new Intent(this,IPCService.class); bindService(service, PersonConnection, BIND_AUTO_CREATE); btn_query.setOnClickListener(this); } private void bindViews() { edit_num = (EditText) findViewById(R.id.edit_num); btn_query = (Button) findViewById(R.id.btn_query); txt_result = (TextView) findViewById(R.id.txt_result); } @Override public void onClick(View v) { int num = Integer.parseInt(edit_num.getText().toString()); if (mIBinder == null) { Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show(); } else { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); String _result = null; try{ _data.writeInterfaceToken("IPCService"); _data.writeInt(num); _data.writeInt(4); mIBinder.transact(0x001, _data, _reply, 0); _reply.readException(); //读取异常 _result = _reply.readString(); String test = _reply.readString(); int test1 = _reply.readInt(); Toast.makeText(this, "我收到的内容是:" + test + " " + test1, Toast.LENGTH_SHORT).show(); txt_result.setText(_result); edit_num.setText(""); Log.d("TAG", "客户端-----" + android.os.Process.myPid()); }catch (RemoteException e) { e.printStackTrace(); } finally { _reply.recycle(); _data.recycle(); } } } }
运行结果:
方法说明及其注意点:
1. Parcel的读写顺序要一致。 比如写的时候先 writeInt ,然后再writeString。 那么读的时候也是要先readInt 然后再writeString。 (原因应该是跟Parcelable差不多,调用的是native层的序列化写入,用的是c/c++的指针顺序写入,如果没有按顺序读取,读取地址的时候读到的内容就会很奇怪了)
例如,服务端写入顺序:
客户端读取顺序:
结果:
收到的数据不理想
2. Parcel的writeInterfaceToken(接口名)以及enforceInterface(接口名)
·writeInterfaceToken以及writeInterfaceToken的接口名要一致(类似于验证要访问的接口是否一致)。
不一致,如果找不到binder找不到要调用的接口,就会报异常
java.lang.SecurityException: Binder invocation to an incorrect interface
例如:服务端
客户端:
报错:
·writeInterfaceToken以及writeInterfaceToken的调用时期必须在读写数据前调用,否则会报错:
java.lang.SecurityException: Binder invocation to an incorrect interface
例如:在写入数据后才调用时:
结果报错:
源码对这俩个函数的描述:
/** * Store or read an IBinder interface token in the parcel at the current * {@link #dataPosition}. This is used to validate that the marshalled * transaction is intended for the target interface. This is typically written * at the beginning of transactions as a header. * 翻译: 在包中当前数据位置存储或读取IBinder接口令牌。这用于验证编组的事务是否用于目标接口。这通常在事务开始时作为标头写入。 */ public final void writeInterfaceToken(@NonNull String interfaceName) { nativeWriteInterfaceToken(mNativePtr, interfaceName); }
/** * Read the header written by writeInterfaceToken and verify it matches * the interface name in question. If the wrong interface type is present, * {@link SecurityException} is thrown. When used over binder, this exception * should propagate to the caller. * 翻译: 读取writeInterfaceToken所写的报头,并验证它是否与所讨论的接口名称匹配。如果存在错误的接口类型,则抛出SecurityException。在绑定器上使用时,此异常应传播给调用方。 */ public final void enforceInterface(@NonNull String interfaceName) { nativeEnforceInterface(mNativePtr, interfaceName); }
3.Parcel的readException()
作用是读取异常,如果读写的时候有异常,那么就能获取到改异常(获取异常不catch就会导致程序奔溃)。 上述代码可以选择不加这个,不加的话如果有异常就获取不到,也不会导致程序奔溃,但读不到数据
4. transact和onTransact
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
参数说明: 1.code,服务端和客户端约定的标识码,这样在服务端中就可以根据改标识码来处理不同的事件
2.data, 用于读取和写入 要通信的数据
3.reply,用于读取和写入 返回的数据 4. flags 可以不管
transact在客户端中调用,ontransact在服务端中调用。 (当然一个进程可以同时为服务端和客户端。 也就是binder机制支持"递归化调用",比如A跟B通信,A可以调用B提供的Ibinder对象的transact跟B通信,同时B也可以调用A提供的IBinder对象的transact跟A通信。 然后他们在各自的onTransact方法中处理即可)
上面的Demo只是验证了通信,并没有真正意义上的调用另一个进程的接口方法,因此再测试了一个Demo调用另外一个进程的方法:
定义接口方法:一定要继承IInterface
import android.os.IInterface; public interface IPlus extends IInterface { int add(int a, int b); }
服务端代码:
public class IPCService extends Service { private static final String DESCRIPTOR = "add two int"; private final String[] names = {"B神","艹神","基神","J神","翔神"}; private MyBinder mBinder = new MyBinder(); private IInterface plus = new IPlus() { @Override public int add(int a, int b) { return a + b; } @Override public IBinder asBinder() { return null; } }; public IPCService(){ mBinder.attachInterface(plus,"add two int"); } private class MyBinder extends Binder { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code){ case 0x001: { Log.d("TAG", "MyBinder Switch块 -----" + android.os.Process.myPid()); data.enforceInterface(DESCRIPTOR); int a = data.readInt(); int b = data.readInt(); int result = ((IPlus)this.queryLocalInterface("add two int")).add(a,b); reply.writeNoException(); reply.writeInt(result); return true; } } Log.d("TAG", "MyBinder OnTransact块 ----- " + android.os.Process.myPid()); return super.onTransact(code, data, reply, flags); } } @Override public IBinder onBind(Intent intent) { return mBinder; } }
客户端代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private EditText edt_arg1; private EditText edt_arg2; private Button add; private TextView addResult; private IBinder mIBinder; private ServiceConnection PersonConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { mIBinder = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mIBinder = service; Log.d("TAG", "客户端-----" + android.os.Process.myPid()); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); //绑定远程Service Intent service = new Intent(this,IPCService.class); bindService(service, PersonConnection, BIND_AUTO_CREATE); add.setOnClickListener(this); } private void bindViews() { edt_arg1 = (EditText) findViewById(R.id.arg1); edt_arg2 = (EditText) findViewById(R.id.arg2); add = (Button) findViewById(R.id.add); addResult = (TextView) findViewById(R.id.result); } @Override public void onClick(View v) { int arg1 = Integer.parseInt(edt_arg1.getText().toString()); int arg2 = Integer.parseInt(edt_arg2.getText().toString()); if (mIBinder == null) { Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show(); } else { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result = -1; try{ _data.writeInterfaceToken("add two int"); _data.writeInt(arg1); _data.writeInt(arg2); mIBinder.transact(0x001, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); addResult.setText(""+_result ); }catch (RemoteException e) { e.printStackTrace(); } finally { _reply.recycle(); _data.recycle(); } } } }
运行结果:
区别第一个Demo:
这里多使用了IInterface,即IPlus
并且在服务端的代码里多写了:
public IPCService(){ mBinder.attachInterface(plus,"add two int"); } /** * void attachInterface(IInterface plus, String descriptor); // 作用: // 1. 将(descriptor,plus)作为(key,value)对存入到Binder对象中的一个Map<String,IInterface>对象中 // 2. 之后,Binder对象 可根据descriptor通过queryLocalIInterface()获得对应IInterface对象(即plus)的引用,可依靠该引用完成对请求方法的调用 * */
data.enforceInterface(DESCRIPTOR); int a = data.readInt(); int b = data.readInt(); int result = ((IPlus)this.queryLocalInterface("add two int")).add(a,b); /** * IInterface queryLocalInterface(Stringdescriptor) ; 作用:根据 参数 descriptor 查找相应的IInterface对象(即plus引用) **/
Binder跨进程的通信AIDL方案:
AIDL使用步骤:
- 创建AIDL文件
- 写上服务端需要提供的接口方法
- 重新build一下模块,就会自动生成aidl文件对应的java文件
测试Demo1:(同一模块下时)
AIDL文件代码:
interface IMyAidlInterface { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); //默认生成,可以删除 //以下俩个是自定义方法 void say(String word); int tell(String word,int age); }
服务端代码:
public class MyAidlServer extends Service { private String TAG = "MyAidlService"; //使用生成的java文件中的stub作为binder对象 private IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { Log.d(TAG, android.os.Process.myPid() + " basicTypes: " + anInt + " " + aLong + " " + aBoolean + " " + aFloat + " " + aDouble + " " + aString); } @Override public void say(String word) throws RemoteException { Log.d(TAG, android.os.Process.myPid() + " say: " + word); } @Override public int tell(String word, int age) throws RemoteException { Log.d(TAG, android.os.Process.myPid() + " tell: " + word + " " + age); return 100; } }; @Nullable @Override public IBinder onBind(Intent intent) { return stub; } }
客户端代码:
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; TextView bindService; ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service); //获取到服务端的binder代理对象,也就是上面stub try { iMyAidlInterface.say("I am handsome boy"); int result = iMyAidlInterface.tell("我是个靓仔", 20); Log.d(TAG, android.os.Process.myPid() +" " + "onServiceConnected: " + result); } catch (RemoteException e) { throw new RuntimeException(e); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindService = findViewById(R.id.bind_service); bindService.setOnClickListener((v) -> { Intent intent = new Intent(MainActivity.this, MyAidlServer.class); bindService(intent,serviceConnection,BIND_AUTO_CREATE); //绑定服务 }); } }
结果:不同进程的日志需要指令抓取才可以显示出来
客户端进程:
服务端进程:
测试Demo2:(同一模块)
服务端aidl:
interface IServiceAidlInterface { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void setData(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString,byte anByte); //aidl不支持short类型,但可以考虑把short转换成int解决 void saySomething(String word); String saySomethingAndRespon(String word); }
服务端Service:
public class AidlService extends Service { private String TAG = "AidlService"; private IServiceAidlInterface.Stub stub = new IServiceAidlInterface.Stub() { @Override public void setData(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString, byte anByte) throws RemoteException { Log.d(TAG,android.os.Process.myPid() + " 服务端收到客户端调用setData传入的数据为: " + anInt + " " + aLong + " " + aBoolean + " " + aFloat + " " + aDouble + " " + aString + " " + (int) anByte); } @Override public void saySomething(String word) throws RemoteException { Log.d(TAG, android.os.Process.myPid() + " 服务端收到客户端调用saySomethingAndRespon传入的数据为: " + word); } @Override public String saySomethingAndRespon(String word) throws RemoteException { Log.d(TAG, android.os.Process.myPid() + " 服务端收到客户端调用saySomethingAndRespon传入的数据为: " + word); return "我是服务端,我已经接收到你发送过来的数据了。 ---> " + word; } }; @Nullable @Override public IBinder onBind(Intent intent) { return stub; } @Override public boolean onUnbind(Intent intent) { Log.d(TAG, "onUnbind: "); return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestory: "); } }
服务端的注册清单: 此处只为注册service。 且exported属性为true是一定要的
<service android:name="net.aidl_server.AidlService" android:exported="true"> <intent-filter> <action android:name="android.intent.action.AIDLService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
客户端:aidl文件直接从服务端复制过来(自建 包名会跟着客户端)
绑定服务的代码:
public class MainActivity extends AppCompatActivity { private final String TAG = "ClientMainActivity"; Button bind_service; Intent intent; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IServiceAidlInterface aidlInterface = IServiceAidlInterface.Stub.asInterface(service); try { aidlInterface.setData(10,100L,true,50.01f,79.0,"XIAO JIAN", (byte) 90); aidlInterface.saySomething("服务端你好呀"); String respon = aidlInterface.saySomethingAndRespon("你是个靓仔"); Log.d(TAG, android.os.Process.myPid()+" 我是客户端,收到服务端传送过来的消息为: " + respon); } catch (RemoteException e) { throw new RuntimeException(e); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bind_service = findViewById(R.id.bind_service); bind_service.setOnClickListener((v)->{ intent = new Intent("android.intent.action.AIDLService"); intent.setPackage("net.aidl_server"); bindService(intent,connection,BIND_AUTO_CREATE); }); } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy: " ); } }
运行结果:
服务端
客户端:
Aidl传输复杂数据注意事项:
数据需要实现Parceable接口,并且格式要跟Parceable实现格式一样,可以把鼠标指在Parceable那,然后Alt + 回车
需要在aidl文件中加入Parceable实现类
定义接口参数需要加上in
在AIDL中,in、out和inout是定向标记,用于指示数据在跨进程通信中的流向。其中, in表示数据只能从客户端流向服务端, out表示数据只能从服务端流向客户端, inout表示数据可以在服务端和客户端之间双向流动 需要注意的是,in 和 out 的作用只是为了告诉系统参数的传递方向,实际上在 AIDL 中,所有的参数都是通过值传递的,即传递的是参数的副本,而不是参数本身。因此,如果需要修改参数的值,需要使用 inout 或者返回值的方式来实现。
引用和参考
Carson带你学Android–Android跨进程通信:图文详解 Binder机制 原理