热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

初探Android中的binder机制

Binder机制是Android系统中最主要的进程间通信机制。虽然Android底层使用的是linux内核,但是除了匿名管道,socket外,Android并没有使用linux中的

Binder机制是Android系统中最主要的进程间通信机制。虽然Android底层使用的是linux内核,但是除了匿名管道,socket外,Android并没有使用linux中的命名管道,信号量,消息队列等传统的IPC通信方式。Binder犹如一张大网,将Android整个系统中的组件,跨进程的组织在一起。淡化进程,强化组件是Android的一个设计理念。在这个过程中,Binder发挥了巨大作用。

binder 的作用

binder的作用可以概括为两点:

  1. IPC:是一种跨进程通信手段,但是传输数据的大小受限制,不建议传输较大数据。

  2. RPC:是一种远程过程调用手段,即一个进程中可以透明的调用另外一个进程中的方法。

两者经常相互伴随,例如跨进程调用的时候,参数的传递以及结果的传递等。

binder机制简述

binder实体对象: 就是binder服务的提供者,一个提供binder服务的类必须继承BBinder类(native层),因此binder实体对象又叫做BBinder对象。

binder引用对象: 是binder服务提供者在客户端进程的代表,每个引用对象类型必须继承BpBinder类(native层),因此binder引用对象有叫做BpBinder对象。

binder代理对象: 代理对象简单理解为内聚了一个binder引用对象,因此可以通过这个内聚的引用对象发起RPC调用。可以有多个不同的代理对象,但却内聚了同一个引用对象。

IBinder对象: BBinder和BpBinder都是继承自IBinder.因此binder实体对象和binder引用对象都可以称为IBinder对象。可以通过IBinder.queryLocalInterface()方法来判断到底是binder实体对象还是binder引用对象。

binder跨进程传输的数据类型是Parcel。

以RPC为例的示意图如下:

《初探Android中的binder机制》 android_binder-1.png

通过一个运行在内核空间的binder驱动进程,将两个用户空间的进程联系了起来。为解决由于用户空间进程之间虚拟地址相互独立而引起的无法跨进程调用别的进程中的对象方法的难题带来了曙光。

通过BInder引用对象发起RPC调用

假设App中的MainActivity中以startActivityForResult()方法启动了该App中的Main2Activity,那么Main2Activity可以通过AMS这个binder服务中的getCallingActivity()方法查询是谁启动了自己。

AMS.getCallingActivity():

public ComponentName getCallingActivity(IBinder token)

去参数是Activity.mToken. 可以通过反射从客户端Activity组件中获取。

1). 获得AMS的引用binder

AMS作为一个系统服务,在Android系统启动过程中,会将自己注册到ServiceManager中(注册过程以后在分析)。现在只要知道客户端可以通过ServiceManager来获得AMS的引用binder即可。

2). 使用引用binder进行RPC调用时,需要直到要调用的方法的编号,这个编号可以从Android源码中获取。

在Android 6.0 中该方法编号如下:

int GET_CALLING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21;

服务端根据这个编号,执行相应的逻辑。

3). 方法参数的传递

binder中唯一能的传递的数据结构就是Parcel,所以必须将方法的参数打包到Parcel中。针对不同的数据类型Parcle提供了不同的方法,来将这些对应的数据打入Parcel中。

另外还要准备一个Parcel用于接收服务端的返回数据。

4). 发起RPC调用

通过引用对象的transact()方法,发起RPC调用,绝大多数情况下,此调用是一个同步调用,也就是说会一直阻塞到服务端将数据返回为止。

但是当transact()方法中传入的flag为FLAG_ONEWAY时,方法会立即返回,不会等到服务端返回数据。

该方法会最终将上述信息传入binder驱动中去。

5). 客户端从返回的Parcel中读取数据

服务端中将请求的方法执行完毕之后,将方法返回值打入到parcel中,binder驱动将其返回到客户端组件中,然后客户端组件按照调用方法的返回值类型,从返回的parcel中读取即可。


public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
// 拿到AMS的引用对象
IBinder sm = (IBinder) Reflect.on("android.os.ServiceManager").call("getService","activity").get();
// 拿到方法编号
int funCode = IBinder.FIRST_CALL_TRANSACTION+21;
// 准备方法参数数据
Parcel data = Parcel.obtain();
// 首先要写入的数据必须是binder服务端的descriptor
data.writeInterfaceToken("android.app.IActivityManager");
// 接下来是方法的参数
data.writeStrongBinder((IBinder)Reflect.on(this).field("mToken").get());
// 用于接受返回数据
Parcel reply = Parcel.obtain();
// 发起RPC调用,同步调用,直到调用结束,期间一直阻塞
try {
sm.transact(funCode,data,reply,0);
} catch (RemoteException e) {
e.printStackTrace();
}
// 读取返回数据
reply.readException();
// 解析返回数据
ComponentName res = ComponentName.readFromParcel(reply);
// 回收parcle
data.recycle();
reply.recycle();
Log.i("shajia","calling Activity name: "+res.getClassName());
}
}

通过binder代理对象发起RPC操作

对于前面例子中获取调用者的情况,实际开发中都是通过Activiy.getCallingActivity()来获取的:

public ComponentName getCallingActivity() {
try {
return ActivityManagerNative.getDefault().getCallingActivity(mToken);
} catch (RemoteException e) {
return null;
}
}

其中ActivityManagerNative.getDefault()返回的是ActivityManagerProxy对象。

ActivityManagerProxy是AMS的binder代理类。

binder代理对象通过内聚的binder引用对象间接发起RPC操作。对于系统服务来说,它的binder代理对象都是事先定义好的。binder代理对象还要实现服务接口,实际上就是对binder引用对象发起RPC操作的二次封装。

class ActivityManagerProxy implements IActivityManager
{
...............
public ComponentName getCallingActivity(IBinder token)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
mRemote.transact(GET_CALLING_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
ComponentName res = ComponentName.readFromParcel(reply);
data.recycle();
reply.recycle();
return res;
}
...............

可以看到代理类中已经帮我们封装好了getCallingActivity()的操作。因为直接通过binder引用发起RPC操作的话,需要开发者知道方法的编号,而方法的编号又是随着Android版本的变化而可能发生改变的。所以一般来说都会为binder服务封装一个binder代理类。这样做还有一个好处是通过一个binder引用对象,可以创建多个binder代理对象。

binder服务分类

分为两大类:

向ServiceManager注册的binder服务

Android系统中自带的绝大多数服务,例如AMS,PMS等都会向ServiceManager注册,注册时会传入一个service名字,例如AMS注册是传入的是“activity”。

客户端可以通过名字向ServiceManager查询对象的binder服务,ServiceManager会返回一个IBinder对象。

至于说返回的IBinder对象究竟是实体bidner呢还是引用binder,按照下面的规则决定:

  1. 当binder服务端同进程请求该服务时,返回的是binder实体对象。

  2. 当请求者与binder服务端在一个进程时,返回的是引用对象。

没有向ServiceManager注册的的bidner服务,又被称为匿名binder服务

典型代表就是App中通过aidl实现的service组件。

aidl实际上帮我们完整实现了服务代理类,以及是是实现了binder服务类中与语义处理相关的所有操作,例如方法编号的分配,方法参数从parcel中的提取,以及返回值打入parcel中等所有的操作。开发者只需要实现服务接口即可。

因为app是没有权限向ServiceManager注册服务的,那么怎么获取app中的额service实体的引用binder呢???

那就是直接传递binder实体,将binder实体对象打入到Parcel中,跨进程传入到AMS中去。

Binder实体在Binder驱动中的传输,会被特殊处理,最终返回到AMS中的是一个binder引用对象。(详细过程后续在分析喽!!!)

其他App进程中的组件便可以通过AMS拿到要请求的service服务的binder引用对象了。要注意的是,此过程中是AMS将所请求的binder服务的引用对象打入Parcel,然后通过Binder驱动传递到请求者进程中。

简单的说就是Binder实体对象的传递过程,伴随着binder服务在Binder驱动中相关数据结构初始化以及binder引用对象的创建过程。

不通过aidl实现一个service,来熟悉一下整个过程:

1. 首先顶定义一个服务接口,即服务要对外提供哪些方法。

IBinderService.java:

public interface IBinderService extends IInterface {
String getMessage();
/**
* 服务的描述,客户端在使用parcel跨进程传输数据的时候
* 必须首先写入服务的描述,即该数据是发给哪个binder的。
* 将来服务端收到数据后会检查这个服务描述是否和自己的一致,不一致就不做处理了
*/
String DESCRIPITON = "MyBinderService";
// 定义方法编号
int GET_MESSAGE = IBinder.FIRST_CALL_TRANSACTION+0;
}

binder服务接口必须继承自IInterface接口,该接口中只有一个方法:

public IBinder asBinder()

binder服务接口一般要包含三部分内容:

首先是该binder服务的描述DESCRIPITON,发送数据时必须先发送该描述,接收数据时必须先对该描述进行检查,和自己不匹配的话那就不用继续进行了。

然后是方法编号,这个编号实际含义要在binder实体对象中的onTransact()方法中才能体现出来。可以理解为onTransact()根据这个编号调用不同的处理分支。

最后就是该服务对外提供的具体方法声明了。

binder实体端和代理对象端必须都继承这个服务接口,并实现其中的方法。另外服务端还要重载binder的onTransact()方法。

2. 实现bidner服务端

binder服务端的重点在于实现onTransact()方法,该方法中会依据客户端传入的方法编号,调用恰当的分支进行处理。

处理过程也是很简单的,就是从parcel中解析参数,调用对应的方法执行,将执行结果打入parcel中。

public class BinderServiceStub extends Binder implements IBinderService {
public BinderServiceStub(){
// 调用该方法后binder实体端的binder.queryLocalInterface()
// 返回就不会为null。
attachInterface(this,DESCRIPITON);
}
// 实现服务接口方法
public String getMessage() {
return " i am from Message!!!!!";
}
/**
* 顾名思义,将自身转换为一个IBinder对象,
* 因为Binder继承子Binder,Binder继承自IBinder
*/
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code){
case GET_MESSAGE:{
// 客户端先发送的是服务描述,所以这里先接收服务描述并判断是否和自己一致
data.enforceInterface(DESCRIPITON);
// 开始执行客户端请求的服务端的方法
String msg = getMessage();
// 将结果打入Parcel
reply.writeNoException();
reply.writeString(msg);
return true;
}
}
/**
* 必须调用父类onTransact处理其他code
*/
return super.onTransact(code, data, reply, flags);
}
}

实现binder代理端

前面介绍了,binder代理对象类会内聚一个binder引用对象,该引用对象通过构造方法传入即可:

public class BinderServiceProxy implements IBinderService {
/**
* 内聚的binder引用对象
*/
private IBinder remote;
public BinderServiceProxy(IBinder binder){
if(binder.queryLocalInterface(DESCRIPITON) == null )
remote = binder;
else
throw new RuntimeException(" this is not a BpBinder.");
}
@Override
public String getMessage() {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(DESCRIPITON);
try {
remote.transact(GET_MESSAGE,data,reply,0);
} catch (RemoteException e) {
e.printStackTrace();
}
reply.readException();
String msg = reply.readString();
data.recycle();
data.recycle();
return msg;
}
@Override
public IBinder asBinder() {
return null;
}
}

这里要特别注意的是,因为binder代理对象类内聚的是一个binder引用对象,所以要对构造方法中传入的Ibinder对象进行检查,保证其是binder引用对象。

然后就是实现服务接口方法,这里很简单了,就是组装Parcel数据,然后利用引用binder对象发起RPC调用。

4. 获取binder引用对象

这就要借助Android中的service组件了,并且以bindService()方法启动该service。

首先定义service组件:

public class MyBinderService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new BinderServiceStub();
}
}

然后在清单文件中,声明该service组件,并且设置process属性,保证该service运行在另外一个进程中:

android:process=":S0"/>

最后以bindService()方式绑定该service:

Intent intent = new Intent();
intent.setClass(this,MyBinderService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if(service.queryLocalInterface(IBinderService.DESCRIPITON)==null){
// 得到binder代理对象
BinderServiceProxy proxy = new BinderServiceProxy(service);
// 开始执行方法
Log.i("shajia","message: "+proxy.getMessage());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
},BIND_AUTO_CREATE);

binder 安全基础

binder服务端可以通过binder提供的两个api拿到客户端的uid和pid,从而决定是否要对这次请求进行处理:


getCallingUid();
getCallingPid();

binder 死亡通知机制

当端服务进程挂掉的时候,客户端是有必要知道的,可以通过binder引用对象的linkToDeath()方法来设置binder服务死亡监听机制。

如下代码所示:

public BinderServiceProxy(IBinder binder){
if(binder.queryLocalInterface(DESCRIPITON) == null ){
remote = binder;
try {
binder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.i("shajia"," binder server is deaded.");
}
},0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
else
throw new RuntimeException(" this is not a BpBinder.");
}

可以在死亡通知处理中做一些资源回收的操作,或者再次重启服务等操作。

到现在为止,已经对binder的表象有了一个大概的了解了,注意只是表象。


推荐阅读
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文详细介绍了cisco路由器IOS损坏时的恢复方法,包括进入ROMMON模式、设置IP地址、子网掩码、默认网关以及使用TFTP服务器传输IOS文件的步骤。 ... [详细]
author-avatar
许心怡917
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有