Binder机制

Binder机制

Binder概述

Binder在我们大Android中是无处不在的,无论是调用媒体服务,传感器,还有我们经常在用的startActivity ,startService等等都在使用着Bindder来完成相应的功能。整个Android系统就可以看成一个基于Binder的C/S架构,binder英文意思是粘合剂,Binder就是这个粘合剂,把各个组件系统粘合在一起。Binder这么重要,作为Android开发者我们也更有必要搞懂它。

下面开始学习Binder之旅吧。

Binder是用来做进程间通信的,Android系统是基于Linux的,在Linux中已经有一些进程间通信的解决方案了,比如管道,共享内存,socket等,为啥Android又弄了个Binder呢?那我们就需要了解一下他们的优缺点了

管道

就比如A到B之间有一个管道,A把数据拷贝到管道中,B从管道中读取数据,这个过程需要建立管道并需要两次数据的拷贝

而且管道是半双工的也就是数据只能往一个方向流动,如果想要双方通信,就需要建立两个管道

所以管道比较耗费性能

共享内存

多个进程之间共享一块内存区域,这个过程中无需拷贝,效率非常高,但是由于这块内存对所有进程都可见,不好管理而且安全方面也不好

Socket

Socket是一个通用的接口,主要用来进行网络之间的通信,虽然可以实现进程间通信,就是杀鸡用牛刀了,传输效率低,开销大,也需要两次的拷贝。

Binder

只需要一次数据拷贝,性能上仅次于共享内存。稳定性上Binder基于C/S架构模式,客户端有什么去求就丢给服务端去做,架构清晰职责明确。

安全方面,传统的进程间通信都没有做这一块,一个安卓系统中有那么多的APP存在,每个APP都运行在一个独立的进程中,我们不希望别的进程能够获取我们应用的信息。

Android为每个新安装的应用都分配了自己的UID,PID,这是通信时鉴别身份的重要凭证。

Binder中有4个比较重要的角色:

  • Server
  • Client
  • ServiceManager
  • Binder驱动

如上图所画

  1. 服务端通过Binder驱动在ServiceManager中注册我们的服务
  2. 客户端通过Bindr驱动查询ServiceManager中注册的服务
  3. ServiceManager通过Binder驱动返回服务端的代理对象
  4. 客户端拿到服务器端的代理对象就可以进行进程间通讯了。

Client和Server是开发者自己来实现,Binder驱动和ServiceManager是系统提供的。

Binder核心原理

  • Client端发送数据到内核缓存区也就是拷贝
  • 这个内核缓存区和Binder驱动中的数据缓存区存在映射关系
  • 服务端和Bindr的数据缓存区有直接的内存映射关系。
  • 这样就相当于把数据直接拷贝到了服务端

Binder源码(9.0)

下面的这些代码我自己也都是系统代码,我自己也云里雾里,不过我们也不需要深入了解,只需要通过这些地方来强化对其原理的理解就好了

1、打开Binder设备

源码位置:/frameworks/native/cmds/servicemanager/service_manager.c

在该文件中的main方法中有一句话 driver = "/dev/binder"; 这里就打开binder驱动设备

2、创建buffer用于进程间传递数据,开辟内存映射(128k)

第一步打开Binder驱动之后,紧接着一句代码bs = binder_open(driver, 128*1024);这里就是打开一个128k的内存映射

内存映射命令是mmap(),它在 /frameworks/native/cmds/servicemanager/binder.c文件中,进入可以看到 bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

它是在系统启动的时候就会调用,在9.0系统源码/device/google/cuttlefish_kernel/4.4-x86_64/System.map文件中的25306行可以看到下面的指令
ffffffff815dbf50 t binder_mmap来开启映射

3、ServiceManager启动

在系统源码位置 /system/core/rootdir/init.rc 文件中,可以找到start servicemanager指令

4、打包到Parcel中,数据写入binder设备copy_from_user

在系统源码:/frameworks/native/libs/binder/IServiceManager.cpp中可以看到parcel打包过程

1
2
3
4
5
6
7
8
9
10
11
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated, int dumpsysPriority) {
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
data.writeInt32(allowIsolated ? 1 : 0);
data.writeInt32(dumpsysPriority);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}

在系统文件 /frameworks/native/libs/binder/IPCThreadState.cpp中,找到
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);

将parcel中的信息封装成结构体并且写入到mOut中,并等待返回

5、服务注册,添加到链表svclist中

server向ServiceManager中注册

在系统代码:/frameworks/native/cmds/servicemanager/service_manager.c文件中

1
2
3
4
5
6
7
8
if (!svc_can_register(s, len, spid, uid)) {
ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n",
str8(s, len), handle, uid);
return -1;
}
//去链表链表svclist中查找,看服务是否注册过
si = find_svc(s, len);
....

定义主线程中的线程池

在系统源码/frameworks/native/libs/binder/IPCThreadState.cpp文件中可以找到joinThreadPool方法。

这里就是定义一个主线程的线程池,,不停的读写

循环从mln和mOut中取出读写请求 mIn.setDataCapacity(256); mOut.setDataCapacity(256);他们默认是256字节的大小。

在talkWithDriver方法中,判断是否可以读写,最终发送到binder设备中。

这些代码真是看的云里雾里,只需要通过他们加深对Binder的执行原理就行了。

手撸AIDL

直接操作Binder是比较麻烦的,Andorid中通过AIDL来简化我们使用Binder。

AIDL四个重要对象

  • IBinder: 一个接口,代表了一个跨进程通讯的能力
  • IInterance: 服务端进程有什么能力,可以提供哪些方法
  • Binder: Binder的本地对象 继承自IBinder
  • Stub: 继承自Binder 实现了IInterance,本地实现的服务端的能力

例子:使用AIDL实现一个第三方的登录,现在有一个A应用和一个B应用,A应用调用B应用来实现登录。

最终效果如下图:

A调用B的登录服务,B是服务端,我们先从B工程中创建一个aidl,直接在工程的main文件夹上右击鼠标创建即可,也可以创建到别的文件夹。

1
2
3
4
5
6
7
package com.chs.binderb;

interface ILoginInterface {
void login();

void loginCallBack(boolean isSuccess,String user);
}

创建两个方法一个登录方法,一个登录回调。

然后把这个AIDL的完整包名和文件都复制到A工程的相同位置。必须一模一样直接复制。

在B工程中创建一个LoginService来监听A工程发来的消息,跳转到第三方登录界面,注意跳转的时候需要设置Intent.FLAG_ACTIVITY_NEW_TASK这个flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LoginService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new ILoginInterface.Stub() {
@Override
public void login() throws RemoteException {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}

@Override
public void loginCallBack(boolean isSuccess, String user) throws RemoteException {

}
};
}
}

然后在AndroidMainfest.xml文件中注册服务

1
2
3
4
5
6
7
8
9
 <service android:name=".service.LoginService"
android:enabled="true"
android:exported="true"
android:process=":remote_server"
>
<intent-filter>
<action android:name="BinderB_Action"></action>
</intent-filter>
</service>

  • android:enabled=”true” 表示可以被实例化
  • android:exported=”true” 表示可以被别的应用隐式调用
  • android:process=”:remote_server” 表示开启一个新进程,进程名字是remote_server
  • action中的名字在 A应用隐式调用的时候使用

下面去A工程中写调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class MainActivity extends AppCompatActivity {
/**
* 是否绑定了远程服务
*/
private boolean isStartRemote;
private ILoginInterface mILoginInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initBinderService();
}

/**
* 通过隐示意图绑定B应用的service
*/
private void initBinderService() {
Intent intent = new Intent();
//设置action
intent.setAction("BinderB_Action");
//设置B应用的包名
intent.setPackage("com.chs.binderb");
//绑定服务
bindService(intent,cnn,BIND_AUTO_CREATE);
isStartRemote = true;
}

ServiceConnection cnn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//绑定成功,可以使用服务端的方法了
mILoginInterface = ILoginInterface.Stub.asInterface(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

public void startWeiXinLogin(View view) {
if(mILoginInterface!=null){
try {
mILoginInterface.login();
} catch (RemoteException e) {
e.printStackTrace();
Toast.makeText(this,"请先安装微信",Toast.LENGTH_SHORT).show();
}
}
}

@Override
protected void onDestroy() {
super.onDestroy();
if(isStartRemote){
unbindService(cnn);
}
}
}

布局样式就是前面gif图中的样式,微信图标的点击方法是startWeiXinLogin方法。里面调用了ILoginInterface的login方法

先说一下ILoginInterface

当我们创建好AIDL文件,重新Rebuild一下工程之后,系统会给我们生成一个ILoginInterface文件,位置在 app\build\generated\aidl_source_output_dir\debug\compileDebugAidl\out\com\chs\binderb\ILoginInterface.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\android\\A1\\BinderA\\app\\src\\main\\aidl\\com\\chs\\binderb\\ILoginInterface.aidl
*/
package com.chs.binderb;

public interface ILoginInterface extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.chs.binderb.ILoginInterface {
private static final java.lang.String DESCRIPTOR = "com.chs.binderb.ILoginInterface";

/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}

/**
* Cast an IBinder object into an com.chs.binderb.ILoginInterface interface,
* generating a proxy if needed.
*/
public static com.chs.binderb.ILoginInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.chs.binderb.ILoginInterface))) {
return ((com.chs.binderb.ILoginInterface) iin);
}
return new com.chs.binderb.ILoginInterface.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 {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_login: {
data.enforceInterface(descriptor);
this.login();
reply.writeNoException();
return true;
}
case TRANSACTION_loginCallBack: {
data.enforceInterface(descriptor);
boolean _arg0;
_arg0 = (0 != data.readInt());
java.lang.String _arg1;
_arg1 = data.readString();
this.loginCallBack(_arg0, _arg1);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}

private static class Proxy implements com.chs.binderb.ILoginInterface {
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;
}

@Override
public void login() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}

@Override
public void loginCallBack(boolean isSuccess, java.lang.String user) 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(((isSuccess) ? (1) : (0)));
_data.writeString(user);
mRemote.transact(Stub.TRANSACTION_loginCallBack, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}

static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_loginCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

public void login() throws android.os.RemoteException;

public void loginCallBack(boolean isSuccess, java.lang.String user) throws android.os.RemoteException;
}

它继承了IInterface接口,所有可以在Binder中传输的接口都需要继承IInterface接口。同时它自己也是一个接口。

它声明了两个方法login()和loginCallBack就是我们在AIDL文件中写的两个方法。同时声明了两个整形id:TRANSACTION_login和TRANSACTION_loginCallBack来标识这两个方法。在onTransact方法中通过这两个id来识别客户端请求的是哪个方法

它内部有一个内部类Stub,这个就是一个Binder,跨进程通信的过程就由它的内部代理Proxy完成,它里面有几个重要的方法

asInterface

用于将服务端的Binder对象转化成客户端可以使用的AIDL接口类型的对象。这个转化是分进程的,如果客户端和服务端在同一个进程中就返回Stub本身,如果是在不同的进程中,就返回Stub.Proxy(obj)代理对象

asBinder

返回当前的Binder对象

onTransact

这个方法时重写的Binder类中的onTransact方法。它运行在服务端的Binder线程池中,远程客户端发起请求时,请求会经过系统包装后交给该方法来处理。它通过不同的code来判断调用哪个方法。然后执行方法并写入返回值

Proxy#login

这个方法运行在客户端,前面的MainActivity中我们调用asInterface方法其实就是拿到了这个Proxy对象,所以我们就能调用它的login方法,当客户端调用该方法的时候创建输入的Parcel对象_data和输出的Parcel对象 _reply,然后调用transact方法来发起远程调用请求,然后当前线程挂起,之后服务端的onTransact方法会被调用,直到完成并返回结果

Proxy#loginCallBack

和上面的login方法一样。

OK 现在回到MainActivity中,在onCreate方法中通过隐式的调用绑定B应用中的服务。

这样点击按钮的时候,B应用中的LoginService的onBind方法就会调用,然后就会打开登录的Activity。

到这里其实A到B的跨进程通信就已经完成了,但是我们在B应用中点击输入用户名和密码如果成功或者失败,应该反馈给A应用啊,怎么反馈呢。

方法就是跟A找B通信时一样的道理,在A中也写一个Service,让B去绑定A中的Service,执行完登录之后,调用A的远程方法。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LoginService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new ILoginInterface.Stub() {
@Override
public void login() throws RemoteException {

}

@Override
public void loginCallBack(boolean isSuccess, String user) throws RemoteException {
Log.i("登录情况","状态:"+isSuccess+">>>>>user:"+user);
}
};
}
}

A中也写一个LoginService,在回调方法中打印一下回调状态和用户名,并在AndroidMasfet.xml中注册

B中模拟登录并调用A中服务的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class MainActivity extends AppCompatActivity {
private static final String NAME = "chs";
private static final String PWD = "123";


private EditText etName;
private EditText etPwd;
/**
* 是否绑定了远程服务
*/
private boolean isStartRemote;
private ILoginInterface mILoginInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etName = findViewById(R.id.et_name);
etPwd = findViewById(R.id.et_pwd);

initBinderService();

}

/**
* 通过隐示意图绑定A应用的service
*/
private void initBinderService() {
Intent intent = new Intent();
//设置action
intent.setAction("BinderA_Action");
//设置B应用的包名
intent.setPackage("com.chs.bindera");
//绑定服务
bindService(intent,cnn,BIND_AUTO_CREATE);
isStartRemote = true;
}
ServiceConnection cnn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//绑定成功,可以使用服务端的方法了
mILoginInterface = ILoginInterface.Stub.asInterface(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};
public void qqLogin(View view) {
final String name = etName.getText().toString();
final String pwd = etPwd.getText().toString();
//ProgressDialog dialog = new ProgressDialog(this);
//dialog.setTitle("正在登录");
new Thread(){
@Override
public void run() {
super.run();
SystemClock.sleep(1000);
runOnUiThread(new Runnable() {
@Override
public void run() {
boolean isSuccess = false;
if(name.equals(NAME)&&pwd.equals(PWD)){
isSuccess = true;
showToast("登录成功");
finish();
}else {
showToast("登录失败");
}
try {
mILoginInterface.loginCallBack(isSuccess,name);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}.start();
}

private void showToast(String text) {
Toast.makeText(this,text,Toast.LENGTH_SHORT).show();
}

@Override
protected void onDestroy() {
super.onDestroy();
if(isStartRemote){
unbindService(cnn);
}
}
}

OK代码完成,运行之后就是前面gif中的效果了。A中LoginService中的回调打印如下。

1
2
2019-07-10 21:51:27.225 10173-10191/com.chs.bindera:remote_a I/登录情况: 状态:false>>>>>user:
2019-07-10 21:51:35.343 10173-10191/com.chs.bindera:remote_a I/登录情况: 状态:true>>>>>user:chs
# 架构

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×