前言
我们知道,Android的底层是使用Linux内核运行的,而Linux为了保证系统的稳定性,使用的是进程隔离的机制,也就是让不同的进程运行在不同的虚拟空间,使得不同进程之间无法共享数据,防止数据的篡改。但是,有时候我们也会遇到不同进程间需要通信的情况,那么,这时候就需要使用Android系统进程间通信IPC(Inter-Process Communication)。
IPC进程间通信方式
*RPC(Remote Procedure Call的缩写) 是远程进程调用的意思。
*Binder线程池最大数为16,超过的请求会被阻塞。在进程间通信时处理并发时,如ContentProvider的CRUD(增删改查)操作方法最多有16个线程同时工作。
一、Binder的产生
在Linux中规定一个进程分为用户空间和内核空间。区别是,用户空间的数据不可在进程间共享,为了保证数据安全性 、独立性,即进程隔离。而内核空间可以被共享且所有用户空间共享一个内核空间。用户空间与内核空间的通信是使用系统调用:
copy_from_user();//将用户空间的数据拷贝到内核空间
copy_to_user();//将内核空间的数据拷贝到用户空间
传统跨进程通信的基本原理:
- “进程1”通过系统调用copy_form_user() 将需要发送的数据拷贝到Linux进程的内核空间中的缓冲区。(拷贝第一次)
- 内核服务程序唤醒“进程2”的接收线程,通过系统调用copy_to_user() 将数据发送到用户空间。(拷贝第二次)
由于传统的跨进程通信需拷贝数据2次,且接收方事先并不知道需要创建多大的缓存空间用来接收数据,造成了资源浪费。而采用Binder机制实现通信的时候,通过内存映射调用Linux下的系统调用方法mmap(),只需系统调用copy_from_user()拷贝1次数据就可以实现进程间传递数据,节省了内存,提高了效率。
1、内存映射
通过关联一个进程中的一个虚拟内存区域和一个磁盘上的对象,使得二者存在映射关系。被映射的对象称为共享对象。当多个进程的虚拟内存区域和同一个共享对象建立映射关系时,对进程1的虚拟内存区域进行写操作时,也会映射到进程2中的虚拟内存区域。
2、 系统调用mmap()
该函数主要是创建虚拟内存区域 与 共享对象建立映射关系。用内存读写代替 I/O读写。
/**
* 函数原型
*/
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
内部原理:
- 步骤1:创建虚拟内存区域
- 步骤2:实现地址映射关系(进程的虚拟地址空间关联到共享对象)
使用时:
用户进程直接调用mmap()建立映射。
/**
* MAP_SIZE的接收缓存区大小 , 关联到共享对象中,即建立映射
*/
mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
二、Binder机制
1. 定义
Binder是Android中的一个类,它实现了IBinder接口。
从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有。
从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等等)和相应ManagerService的桥梁。
从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过Binder对象,客户端就可以获取服务端提供的服务或者数据。这里的服务包括普通服务和基于AIDL的服务。
2. 模型
从字面上理解binder是"粘结剂"的意思,那么google的工程师为什会以"粘结剂"来命名binder呢?这是因为binder是基于C-S架构,而在这个模型中存在着四个角色,如下:
3. 模式原理
Client、Server 和 Service Manager 属于进程的用户空间,不可进行进程间交互。Binder驱动在内核空间中,能持有Server服务端进程的Binder实体,并给Client客户端提供Binder实体的引用。
Binder驱动 和 Service Manager进程 属于 Android基础架构;而Client进程 和 Server进程 属于Android应用层。
4. 具体说明
三、AIDL实例
假设一个场景:在图书馆,有两个进程。A进程是图书管理员,B进程向A进程添加图书。当B进程收到A进程添加的图书时,通知A进程,图书已经添加成功。了解场景之后,我们通过aidl来实现这个需求。
下文贴出binder服务端与客户端的代码。
1、binder服务端
使用一个远程Service模拟进程A,提供binder服务端。
package com.shine.binderdemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import java.util.concurrent.CopyOnWriteArrayList;
public class MyService extends Service {
private CopyOnWriteArrayList booklist;
private IOnBookAddListener mListener;
private IBinder mBinder = new IBookManager.Stub() {
@Override
public void addBook(Book book) throws RemoteException {
booklist.add(book);
if (mListener != null) {
mListener.onBookadd(book);
}
}
@Override
public void registerListener(IOnBookAddListener listener) throws RemoteException {
mListener = listener;
}
};
@Override
public void onCreate() {
super.onCreate();
booklist = new CopyOnWriteArrayList();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
在AndroidManifest.xml中通过process属性使MyService 运行在其他进程。
有一点我们需要格外注意:我们看到在用来存放添加书籍的booklist是一个CopyOnWriteArrayList。为什么在这要用CopyOnWriteArrayList呢?这是因为binder服务端在接收到客户端的请求访问时都会在一个单独的线程中处理这个请求,所以会出现线程安全问题,在这个地方通过CopyOnWriteArrayList来避免出现的线程安全问题,这个过程在子线程中完成也可以通过客户端的代码来验证。
2、binder客户端
在activity中绑定服务来模拟进程B,通过binder跨进程调用A的添加图书的方法,实现binder客户端。
package com.shine.binderdemo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private IBookManager mBookManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MyService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
public void add(View view) {
Book book = new Book();
book.setBookId(1);
book.setBookName("《第一行代码》");
try {
mBookManager.addBook(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mServiceConnection);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManager = IBookManager.Stub.asInterface(service);
try {
mBookManager.registerListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private IOnBookAddListener mListener = new IOnBookAddListener.Stub() {
@Override
public void onBookadd(final Book book) throws RemoteException {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, book.toString(), Toast.LENGTH_SHORT).show();
}
});
}
};
}
首先来验证服务端中mBinder 的addBook(Book book)方法确实是在一个子线程中进行的。
private IOnBookAddListener mListener = new IOnBookAddListener.Stub() {
@Override
public void onBookadd(final Book book) throws RemoteException {
//方法回调在子线程中
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, book.toString(), Toast.LENGTH_SHORT).show();
}
});
}
};
在onBookadd(final Book book) 回调中通过runOnUiThread(new Runnable())将线程切回主线程然后才能Toast提示。这也从侧面证明了binder服务端处理客户端的请求时会在一个子线程中处理。这里我们还有一个地方要注意,虽然binder服务会在一个子线程中处理客户端的请求,但是客户端请求时却不会新开一个线程,从上面的代码我们可能还看不出什么,如果将服务端的添加图书的代码设置为耗时操作,运行程序,点击添加图书可能就会出现ANR。(这里就不验证了)所以在确定服务端处理请求时是耗时的操作的时候。有必要新开一个线程去请求。
上面说了这么多,总结一句话:binder服务在处理客户端的请求时是在一个独立的线程中完成的,而客户端请求处理,不会新开一个线程,如果是耗时操作,则可能出现ANR。
四、Binder跨进程间原理
首先来看,在activity中点击添加图书会调用add(View view),然后调用mBookManager.addBook(book);就能成功的往MyService的bookList中添加一本书,这是为什么呢?我们的activity和service明明处于两个进程,确好像在同一个进程中直接持有了服务端实现addBook(Book book)方法的mBinder 的引用,通过这个引用就调用了MyService 所在进程的方法。要解释这个问题,我们自然而然的要分析activity中的mBookManager是怎么被赋值的:
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//mBookManager 在这里赋值
mBookManager = IBookManager.Stub.asInterface(service);
try {
mBookManager.registerListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
看到这里mBookManager = IBookManager.Stub.asInterface(service);我们可能会糊涂,这个IBookManager.Stub到底是个什么东西,为什么调用它的asInterface(service)方法就能得到mBinder 的引用?接下来我们就来分析IBookManager.Stub是个啥,它的asInterface(service)到底有什么奥秘。
package com.shine.binderdemo;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.shine.binderdemo.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.shine.binderdemo.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.shine.binderdemo.IBookManager interface,
* generating a proxy if needed.
*/
public static com.shine.binderdemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.shine.binderdemo.IBookManager))) {
return ((com.shine.binderdemo.IBookManager) iin);
}
return new com.shine.binderdemo.IBookManager.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 {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.shine.binderdemo.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.shine.binderdemo.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_registerListener: {
data.enforceInterface(DESCRIPTOR);
com.shine.binderdemo.IOnBookAddListener _arg0;
_arg0 = com.shine.binderdemo.IOnBookAddListener.Stub.asInterface(data.readStrongBinder());
this.registerListener(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.shine.binderdemo.IBookManager {
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 addBook(com.shine.binderdemo.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void registerListener(com.shine.binderdemo.IOnBookAddListener listener) 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.writeStrongBinder((((listener != null)) ? (listener.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void addBook(com.shine.binderdemo.Book book) throws android.os.RemoteException;
public void registerListener(com.shine.binderdemo.IOnBookAddListener listener) throws android.os.RemoteException;
}
上面直接贴出了IBookManager.aidl编译之后生成的IBookManager.java的代码。这个代码初看之下很长,不容易读懂,下面我贴一张as里面关于这个类的结构图。
[图片上传失败...(image-7fc006-1570421770459)]
通过这个结构图大概可以看出Stub是IBookManager的一个内部类,有一个asInterface(android.os.IBinder obj)方法,这也是上面activity中给mBookManager 赋值中用到的一个方法,我们待会再来分析。Stub内部又有一个内部类Proxy ,从名字上来看它应该是一个代理类,那么它到底代理的那个类呢?
public static com.shine.binderdemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.shine.binderdemo.IBookManager))) {
return ((com.shine.binderdemo.IBookManager) iin);
}
//将IBinder类型的obj通过构造方法传入Proxy
return new com.shine.binderdemo.IBookManager.Stub.Proxy(obj);
}
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
看完上面两段代码应该可以看出Proxy代理的就是一个IBinder类型的obj,到这里我们明白了mBookManager原来就是一个Proxy代理类。熟悉代理模式的话,我们知道代理类只是持有一个真实类的引用,真正功能都是由这个真实类实现的。在这个IBookManager.Stub.Proxy里面,真实类是什么呢?
private static class Proxy implements com.shine.binderdemo.IBookManager {
//mRemote是这个代理中的真实对象
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
......
@Override
public void addBook(com.shine.binderdemo.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//mBookManager的addBook(Book book)方法实际上是调用 mRemote.transact(...)方法
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
......
}
这段代码的注释已经很明确的IBinder类型的mRemote是这个类的真实引用。
mBookManager.addBook(Book book)方法最后会调用mRemote.transact(...)方法,那么这个mRemote是个啥呢?mRemote是如何传入Proxy呢?
我们在之前的binder机制的概念时说过,binder机制涉及到4个组件,binder server ,binder client,binder内核驱动,Service Manager。在上面的情景中我们只分析了binder server 和binder client,接下来binder内核驱动(对于binder驱动,在下面只会提到它的作用,不涉及具体的代码,具体的代码分析我也不懂)就要出场了,而对于Service Manager,这个例子中却不会直接涉及,在activity的启动过程中会出现,到时候再分析,敬请期待。。。
有了binder驱动介入,就可以解决mRemote到底是个啥了。先看下MyService的onBinder(...)方法
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private IBinder mBinder = new IBookManager.Stub() {
@Override
public void addBook(Book book) throws RemoteException {
booklist.add(book);
if (mListener != null) {
mListener.onBookadd(book);
}
}
@Override
public void registerListener(IOnBookAddListener listener) throws RemoteException {
mListener = listener;
}
};
这个mBinder是一个IBookManager.Stub()类型的变量,而IBookManager.Stub()继承Binder,所以mBinder是一个Binder类型的对象。这个binder类型的对象实际上就是binder服务端,在binder服务端开启的时候,同时会在binder内核建一个mRemote的binder对象,我们在上面提到的mRemote其实就是binder内核里面的mRemote binder对象。实际在binder进程间调用的时候必须要考虑的问题就是如何获取binder内核mRemote 对象。这个例子采用的是Service做binder服务端,而binderService中google的工程师已经为我们实现好了。在ServiceConnection里面有个回调方法可以获取binder内核的mRemote,如下:
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//通过获取到的service(mRemote)生成IBookManager.Stub.Proxy对象
mBookManager = IBookManager.Stub.asInterface(service);
try {
mBookManager.registerListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
这样我们就获取到了binder内核的mRemote对象,同时也传入了IBookManager.Stub.Proxy,接着就可以使用mRemote来进行跨线程的调用了。
接下来看下mBookManager到底是怎样实现addBook(Book,book)方法的,上面已经分析了会调用到IBookManager.Stub.Proxy.addBook(Book book)。
@Override
public void addBook(com.shine.binderdemo.Book book) throws android.os.RemoteException {
//_data表示发送binder服务端的数据,这些数据需要通过Parcel (包裹)进行传递
android.os.Parcel _data = android.os.Parcel.obtain();
//_reply 表示binder服务端相应的数据,这些数据同样需要通过Parcel (包裹)进行传递
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
//用来做安全检查,binder服务端会处理这个数据
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
//Parcel (包裹)不仅可以传递基本类型的数据还可以传递对象,但是对象必须实现Parcelable接口
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//调用binder内核的mRemote对象往binder服务端发送信息
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
代码中注释已经很详细了,就不解释了,接着看下面,我们说过mRemote实际上是一个binder类型的对象, mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);就会调用到Binder的transact(...)方法中
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
//调用binder服务端的onTransact(...)中
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
其中transact(...)有四个参数,分别是 code,data,reply,flag。
code:整形的一个识别码,客户端传入,用于区分服务端执行哪个方法。
data:客户端传入的Parcel类型的参数。
reply:服务端返回的结果值,也是Parcel类型数据。
flag:整形的一个标记,用于标记是否是否有返回值,0表示有返回值,1表示没有。
接着再次涉及到binder内核驱动,具体的细节我也不太懂,直接的结论是流程会进入到binder服务端的IBookManager.Stub的onTransact(...)中:
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
//根据transact第一个参数来确定请求的是哪个方法
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.shine.binderdemo.Book _arg0;
if ((0 != data.readInt())) {//如果客户端传递是有非基本类型的数据从data中取出
_arg0 = com.shine.binderdemo.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//取出来的数据传到MyService的mBinder的addBook(...)
this.addBook(_arg0);
//由于addBook(...)没有返回值,所以不需要通过reply返回结果
reply.writeNoException();
return true;
}
case TRANSACTION_registerListener: {
data.enforceInterface(DESCRIPTOR);
com.shine.binderdemo.IOnBookAddListener _arg0;
_arg0 = com.shine.binderdemo.IOnBookAddListener.Stub.asInterface(data.readStrongBinder());
this.registerListener(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
通过上面从activity的addBool(Book book)开始,我们一步一步的分析了binder客户端如何调用binder服务端的方法,这个过程在binder内核驱动的基础上实现,让我们感觉好像是调用本地(同一个进程)方法一样,实质上底层为我们做了大量的工作。这样基于aidl实现的Binder跨进程调用就大概谈完了,对binder跨进程调用也应该有了一定的了解。
#######binder小结
跨进程调用的关键点在于如何获得服务端的binder对象在内核里面的引用(如上面分析的mRemote)。
一般来说有两种途径,其一是通过Service Manager,这篇文章没有直接涉及Service Manager,但是在底层源码里面这种情况很常见,在Activity启动过程中用到的ActivityManagerService就是通过这种方式在客户端得到服务端的binder对象在内核里面的引用,我们以后再分析。其二是通过已经建立好的binder连接来获取这个引用。如上面的例子中用到的一样。
private IOnBookAddListener mListener = new IOnBookAddListener.Stub() {
@Override
public void onBookadd(final Book book) throws RemoteException {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, book.toString(), Toast.LENGTH_SHORT).show();
}
});
}
};
这是一个实现了接口方法的Binder服务,通过已经建立好的binde连接mBookManager传递给Myservice所在进程。
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManager = IBookManager.Stub.asInterface(service);
try {
//通过已经建立好的连接传送服务端binder的引用
mBookManager.registerListener(mListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
有一点需要注意,在此时activity所在的进程就成为了我们通常所说的binder服务端,而MyService则是binder客户端。这就是第二种获取binder服务引用的方式。再多谈一点,其实在这个例子中,两个建立的binder连接都是通过已将建立好的连接来传递的,除了mListener 这个binder引用的获得,mBinder也是这种情况。这里就不再详细讨论了,如果感兴趣可以学习一下bindService的源码,就肯定能发现这一点。最后贴上一张例子用到的uml类图:
[图片上传失败...(image-6d3652-1570421770459)]
参考博客
- Android IPC进程间通信,Binder机制原理及AIDL实例