Android Binder机制
Binder简介
binder使用内存映射(
mmap
)来实现进程间传递数据,比较传统的进程间通信.binder只需要进行一次的数据拷贝(
copy_from_user()
).传统进程通信需要经过两次数据拷贝(
copy_from_user()
,copy_to_user()
)
Binder跨进程通信的调用过程
- client进程将数据写入
Parcelable
对象中 - client进程通过代理的Binder对象的
transact()
方法将数据传递给Binder驱动. - client进程中的调用线程被挂起(直到server进程返回结果)
- Binder驱动根据Binder代理对象找到该真正的Binder对象对应的server进程.
- Binder驱动把数据发送到server进程中,并通知server进程执行解包
- server进程从Binder线程池中取出目标线程.
- server进程通过
onTransact()
回调进行数据解包 - server进程执行目标方法
- server进程返回数据
- Binder驱动通知Binder代理对象返回结果
- client进程中的执行线程被唤醒
- client进程通过Binder代理对象接收到返回结果
说明 :
服务端的Binder指的是Binder的本地对象
客户端的Binder指的是Binder代理对象,它只是Binder本地对象的一个远程代理.
一个例子来解释上述的binder机制
通过aidl自动生成的java类来分析binder机制.
- 使用
Parcelable
来定义一个传输数据类.
package cn.jimmie.learn.art.ipc.aidl import android.os.Parcel import android.os.Parcelable class User : Parcelable { var id: Long = 0 var name: String? = "" var age = 0 constructor(parcel: Parcel) { id = parcel.readLong() name = parcel.readString() age = parcel.readInt() } constructor(id: Long, name: String, age: Int) { this.id = id this.name = name this.age = age } override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeLong(id) parcel.writeString(name) parcel.writeInt(age) } override fun describeContents(): Int = 0 companion object CREATOR : Parcelable.Creator<User> { override fun createFromParcel(parcel: Parcel): User = User(parcel) override fun newArray(size: Int): Array<User?> = arrayOfNulls(size) } }
- 使用 aidl文件声明 跨进程对象
// User.aidl package cn.jimmie.learn.art.ipc.aidl; parcelable User;
- 使用 aidl 编写数据传输接口.
// IUserManager.aidl package cn.jimmie.learn.art.ipc.aidl; import cn.jimmie.learn.art.ipc.aidl.User; // Declare any non-default types here with import statements interface IUserManager { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ User getUserById(long id); }
需要注意的是 非基本类型的数据,需要使用import导入,不管是否在同一个包中.
非基本类型的参数,需要使用
in
,out
,inout
来标记. 该标记表示数据的流向.
in
表示从客户端流向服务端的数据.
out
表示从服务端流向客户端的数据.您应该将方向限定为真正需要的方向,因为编组参数的开销极大。
到此,对 aidl进行编译,会得到一份自动生成的IUserManager.java
文件.
接下来分析,这份自动生成的文件内容.
自动生成的类方法剖析
服务端使用的类-->Stub:
Stub
是一个继承IBinder
和实现 数据接口的抽象方法.
服务端需要实现该方法,来创建一个真正的binder对象.
onTransact()
方法 :
该方法是在客户端和服务端不在同一个进程中,才会被调用.用于传递客户端定位方法和参数.
该方法是在binder驱动中binder线程池分配的一个线程中运行,服务端可以接收到客户端传来的方法参数和定位到调用的方法.
服务端在根据这些参数,来调用真实的服务端数据接口.
注意,客户端的调用线程会等待服务端调用结束,得到数据返回
客户端通过Stub的静态方法asInterface来获取数据接口:
Stub.asInterface()
:
- 该静态方法是提供给客户端调用,用来获取所需的数据接口对象.
- 如果客户端和服务端在同一个进程,那么客户端服务端共享一个binder,则直接返回 Stub本身
注意此时客户端和服务端操作的是同一个对象.
- 如果客户端和服务端在不同的进程中,那么返回一个服务端binder的代理对象.
注意此时客户端和服务端操作的不是同一个对象.
- 客户端通过代理对象把需要调用的方法和参数数据传递给binder驱动,binder驱动通知服务端调用真正的的binder方法.
- 然后将调用的返回值再传递给binder驱动,binder驱动又将数据的返回给代理对象.
- 这样客户端就能从代理对象中获取跨进程数据的返回
代理对象的数据接口实现:
getUserById(long id)
方法 :
- 跨进程过程中,客户端调用的数据接口方法
- 该方法将方法调用的参数 传递给binder驱动进而传递给远程服务的binder对象
- 远程服务器进而调用真正的数据接口方法,此时会阻塞当前线程,直到远程服务把返回值通过binder驱动写入到reply中
请看 完整的实现:
package cn.jimmie.learn.art.ipc.aidl; // Declare any non-default types here with import statements public interface IUserManager extends android.os.IInterface { /** * 服务端需要实现 Stub类,来创建一个真正的binder对象 */ public static abstract class Stub extends android.os.Binder implements cn.jimmie.learn.art.ipc.aidl.IUserManager { private static final java.lang.String DESCRIPTOR = "cn.jimmie.learn.art.ipc.aidl.IUserManager"; public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * 该静态方法是提供给客户端调用,用来获取所需的数据接口对象. * <p> * 如果客户端和服务端在同一个进程,那么客户端服务端共享一个binder,直接返回 共享的数据接口, * 注意此时客户端和服务端操作的是同一个对象. * <p> * 如果客户端和服务端在不同的进程中,那么返回一个服务端binder的代理对象. * 注意此时客户端和服务端操作的不是同一个对象. * <p> * 客户端通过代理对象把需要调用的方法和参数数据传递给binder驱动,binder驱动通知服务端调用真正的的binder方法. * 然后将调用的返回值再传递给binder驱动,binder驱动又将数据的返回给代理对象. * 这样客户端就能从代理对象中获取跨进程数据的返回 */ public static cn.jimmie.learn.art.ipc.aidl.IUserManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // 判断是同一进程,则直接返回服务端的调用接口 if (((iin != null) && (iin instanceof cn.jimmie.learn.art.ipc.aidl.IUserManager))) { return ((cn.jimmie.learn.art.ipc.aidl.IUserManager) iin); } // 使用代理返回调用接口 return new cn.jimmie.learn.art.ipc.aidl.IUserManager.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } /** * 如果客户端和服务端在同一个进程 , 将不会回调到该函数. * 因为 在同一个进程将不会调用 Proxy类,代理类中 调用 `transact()`函数, 服务端将回调此函数 */ @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { java.lang.String descriptor = DESCRIPTOR; // 根据code来判断远程调用的方法 switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } case TRANSACTION_getUserById: { // 该方法和 writeInterfaceToken()方法,配合进行数据有效性验证 data.enforceInterface(descriptor); // 获取传递的方法参数 long _arg0 = data.readLong(); // 调用服务端binder中真实有效的方法 cn.jimmie.learn.art.ipc.aidl.User _result = this.getUserById(_arg0); reply.writeNoException(); if ((_result != null)) { // 返回数据有效,写入标志位 reply.writeInt(1); // 将返回的数据写入到 reply中 _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { // 返回数据无效,写入标志位 reply.writeInt(0); } return true; } default: { return super.onTransact(code, data, reply, flags); } } } /** * 数据接口的代理对象,当需要进行跨进程通信时,客户端的调用对象 */ private static class Proxy implements cn.jimmie.learn.art.ipc.aidl.IUserManager { 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; } /** * 跨进程过程中,客户端调用的数据接口方法 * 该方法将方法调用的参数 传递给binder驱动进而传递给远程服务的binder对象 * 远程服务器进而调用真正的数据接口方法,此时会阻塞当前线程,直到远程服务把返回值通过binder驱动写入到reply中 */ @Override public cn.jimmie.learn.art.ipc.aidl.User getUserById(long id) throws android.os.RemoteException { // 方法参数 android.os.Parcel _data = android.os.Parcel.obtain(); // 方法返回值 android.os.Parcel _reply = android.os.Parcel.obtain(); // 返回的结果数据 cn.jimmie.learn.art.ipc.aidl.User _result; try { // 写入描述符,用于数据验证 _data.writeInterfaceToken(DESCRIPTOR); // 写入参数 _data.writeLong(id); // 传递参数数据和返回数据到远程,此时当前线程将被阻塞,等待远程调用结束 mRemote.transact(Stub.TRANSACTION_getUserById, _data, _reply, 0); _reply.readException(); if ((0 != _reply.readInt())) { // 数据正确返回,将其写入到_result中 _result = cn.jimmie.learn.art.ipc.aidl.User.CREATOR.createFromParcel(_reply); } else { _result = null; } } finally { // 数据回收 _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_getUserById = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ public cn.jimmie.learn.art.ipc.aidl.User getUserById(long id) throws android.os.RemoteException; }
至此, binder机制的运转流程已经很明了了.
可以看出, aidl 是非必须的, 只要我们自己编写
IUserManager
接口,也能过实现binder跨进程通信的功能.
参考
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
一文看懂ConstraintLayout的用法
ConstraintLayout 相对于 RelativeLayout来说性能更好,布局上也更加灵活。在最新的Google Android开发文档中是推荐使用 ConstraintLayout的,下面来看看具体用法。 0x00 相对位置(Relative positioning) 这个比较简单,看图解释,假设控件B要放在控件A的右侧,可以使用 layout_constraintLeft_toRightOf属性。 <Button android:id="@+id/buttonA" ... /> <Button android:id="@+id/buttonB" ... app:layout_constraintLeft_toRightOf="@+id/buttonA" /> 看图2可以了解控件约束属性代表的含义。 类似相对位置的约束属性有: layout_constraintLeft_toLeftOf layout_constraintLeft_toRightOf layout_constraintRight_toLeftOf layout_constraintR...
- 下一篇
Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功...
本模块共有六篇文章,参考郭神的《第一行代码》,对Material Design的学习做一个详细的笔记,大家可以一起交流一下: Material Design 实战 之第一弹——Toolbar(即本文) Material Design 实战 之第二弹——滑动菜单详解&实战 Material Design 实战 之第三弹—— 悬浮按钮和可交互提示(FloatingActionButton & Snackbar & CoordinatorLayout) Material Design 实战 之第四弹 —— 卡片布局以及灵动的标题栏(CardView & AppBarLayout) Material Design 实战 之第五弹 —— 下拉刷新(SwipeRefreshLayout) Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功能实现(充分利用系统状态栏空间) 引子: 文章提要与总结 1. CollapsingToolbarLayout 1.1 Collaps...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS8编译安装MySQL8.0.19
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS关闭SELinux安全模块
- Hadoop3单机部署,实现最简伪集群
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2更换Tomcat为Jetty,小型站点的福音