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个比较重要的角色:
如上图所画
Client和Server是开发者自己来实现,Binder驱动和ServiceManager是系统提供的。
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打包过程
virtual status_t addService(const String16& name, const sp& 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文件中
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的执行原理就行了。
直接操作Binder是比较麻烦的,Andorid中通过AIDL来简化我们使用Binder。
AIDL四个重要对象
例子:使用AIDL实现一个第三方的登录,现在有一个A应用和一个B应用,A应用调用B应用来实现登录。
最终效果如下图:
A调用B的登录服务,B是服务端,我们先从B工程中创建一个aidl,直接在工程的main文件夹上右击鼠标创建即可,也可以创建到别的文件夹。
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
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文件中注册服务
<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>
下面去A工程中写调用的方法
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
/*
* 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的远程方法。
代码如下
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中服务的方法
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中的回调打印如下。
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