Binder在咱们大Android中是无处不在的,不管是调用媒体服务,传感器,还有咱们常常在用的startActivity ,startService等等都在使用着Bindder来完成相应的功能。整个Android系统就能够当作一个基于Binder的C/S架构,binder英文意思是粘合剂,Binder就是这个粘合剂,把各个组件系统粘合在一块儿。Binder这么重要,做为Android开发者咱们也更有必要搞懂它。java
下面开始学习Binder之旅吧。android
Binder是用来作进程间通讯的,Android系统是基于Linux的,在Linux中已经有一些进程间通讯的解决方案了,好比管道,共享内存,socket等,为啥Android又弄了个Binder呢?那咱们就须要了解一下他们的优缺点了c++
管道缓存
就好比A到B之间有一个管道,A把数据拷贝到管道中,B从管道中读取数据,这个过程须要创建管道并须要两次数据的拷贝安全
并且管道是半双工的也就是数据只能往一个方向流动,若是想要双方通讯,就须要创建两个管道bash
因此管道比较耗费性能服务器
共享内存微信
多个进程之间共享一块内存区域,这个过程当中无需拷贝,效率很是高,可是因为这块内存对全部进程均可见,很差管理并且安全方面也很差网络
Socket架构
Socket是一个通用的接口,主要用来进行网络之间的通讯,虽然能够实现进程间通讯,就是杀鸡用牛刀了,传输效率低,开销大,也须要两次的拷贝。
Binder
只须要一次数据拷贝,性能上仅次于共享内存。稳定性上Binder基于C/S架构模式,客户端有什么去求就丢给服务端去作,架构清晰职责明确。
安全方面,传统的进程间通讯都没有作这一块,一个安卓系统中有那么多的APP存在,每一个APP都运行在一个独立的进程中,咱们不但愿别的进程可以获取咱们应用的信息。
Android为每一个新安装的应用都分配了本身的UID,PID,这是通讯时鉴别身份的重要凭证。
Binder中有4个比较重要的角色:
如上图所画
Client和Server是开发者本身来实现,Binder驱动和ServiceManager是系统提供的。
Binder源码(9.0)
下面的这些代码我本身也都是系统代码,我本身也云里雾里,不过咱们也不须要深刻了解,只须要经过这些地方来强化对其原理的理解就行了
一、打开Binder设备
源码位置:/frameworks/native/cmds/servicemanager/service_manager.c
在该文件中的main方法中有一句话 driver = "/dev/binder";
这里就打开binder驱动设备
二、建立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
来开启映射
三、ServiceManager启动
在系统源码位置 /system/core/rootdir/init.rc 文件中,能够找到start servicemanager
指令
四、打包到Parcel中,数据写入binder设备copy_from_user
在系统源码:/frameworks/native/libs/binder/IServiceManager.cpp中能够看到parcel打包过程
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中,并等待返回
五、服务注册,添加到链表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应用来实现登陆。
最终效果以下图:
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
复制代码