Android binder
Binder在Android的重要性就不说了。看了很多文章,自己想总结一番,发现还是太菜,有那么多大牛已经说的很好,且都有系统性讲解。所以本篇参照《Android开发艺术探索》,重要记录下binder的使用,感受下其在IPC中的媒介作用,本篇作为笔记使用。
推荐文章
- 罗升阳Android进程间通信(IPC)机制Binder简要介绍和学习计划
- Gityuan Binder系列—开篇
- 图文详解 Android Binder跨进程通信的原理
- Android Binder 完全解析(一)概述
实在看不下去,就看第3篇吧。
这里借用一张图。
明白几点:
1. 各个进程相互隔离,数据不可共享。此处指的时用户空间。
2. 每个进程可分为用户空间、内核空间。用户空间数据不可共享,内核空间可以。
3. 1/2/3步骤的虚线表示我们开发中感受到的流程,也是Android想实现的CS流程。实际过程并不能直接联系。
4. Client、Server、Service Manager在用户空间,Binder驱动在内核空间。
5. 用户可以实现的时应用层内容,Client、Server;Service Manager和Binder是Android平台已经实现好了的。
下面我们看下Android中怎样使用Binder实现IPC。
< Android开发艺术探索 >
直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,他的设备驱动是/dev/binder,该通信方式linux中没有;从Android Framework 角度来说,Binder是ServiceManager 连接各种Manager(ActivityManager、WindowManager等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当binderService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务和数据,这里的服务包括普通服务和AIDL服务。
在Android开发中,Binder一般用在service,而一般的service的Binder并不涉及进程间通信,这里我们主要分析AIDL。
在Android studio 中创建aidl
AIDL是Android中定义的接口语言,用它能实现IPC跨进程通信。
studio中生成aidl还是很方便的,使用自动生成即可。
之后,studio会自动为我们在main下创建一个aidl文件夹,里面的包名还是跟项目包名一致,看图。
此时的IBookManager里面还没有东西,basicTypes只是一个举例,看英文解释(举例一些在AIDL中可以作为参数和返回值的基本类型),其实哪能就这些,还玩儿不玩儿了,可参考官方说明Android 接口定义语言 (AIDL)。
interface IBookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
分别在aild的对象包名下面,创建Book.java、Book.aidl,修改IBookManager.aidl,代码如下。
Book
package com.breezehan.ipc;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private int bookId;
private String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
Book.aidl
// Book.aidl
package com.breezehan.ipc;
parcelable Book;
IBookManager.aidl
// IBookManager.aidl
package com.breezehan.ipc;
import com.breezehan.ipc.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
然后我们make project,就能在build中看到系统为我们生成的完整的IBookManager
注意,注意!有人说,毛线啊,我都报错了好不!
那是啊,没有错才有鬼呢。
因为默认我们的java文件都是在main->java下面的,你写在了adil下面,又没告诉studio,那肯定找不到了。
需要我们在module的build.gradle中设置。
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
***
}
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
}
分析IBookManager
上面三个类Book实现parcelable接口,Book.aidl是因为adil的特殊情况需要添加;IBookManager是一个接口,此处虽然和Book在同一个包中,但是仍需import,也是aidl特殊之处。
系统会根据IBookManager中的信息(比如里面我们添加了两个方法)生成对应的Binder类,看一下build->generated->source->aidl->com.breezehan.ipc
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\android_work\\workspace_studio\\AndroidKnowledgeSummary\\ipc\\src\\main\\aidl\\com\\breezehan\\ipc\\IBookManager.aidl
*/
package com.breezehan.ipc;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.breezehan.ipc.IBookManager {
// 1.Binder的唯一标识,一般使用当前类名表示,带包名。
private static final java.lang.String DESCRIPTOR = "com.breezehan.ipc.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
// 2. 用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这里转换时区分进程的,如果客户端和服务端处于同一进程,那么返回的就是Stub对象本身,否则返回的就是系统封装后的Stub.Proxy对象。
/**
* Cast an IBinder object into an com.breezehan.ipc.IBookManager interface,
* generating a proxy if needed.
*/
public static com.breezehan.ipc.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.breezehan.ipc.IBookManager))) {
return ((com.breezehan.ipc.IBookManager) iin);
}
return new com.breezehan.ipc.IBookManager.Stub.Proxy(obj);
}
// 3.返回当前binder对象
@Override
public android.os.IBinder asBinder() {
return this;
}
// 4.这个方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会被系统底层封装后交由此方法来处理。服务端通过code判断客户端请求的方法是哪个,接着从data取出目标方法所需参数(有参数的话),然后执行目标方法。当目标方法执行完毕后,就像reply中写入返回值(目标方法有返回值的话)。另外,onTransact有个boolean的返回值,当是false的时候,客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程就能调用我们的服务。
@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_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.breezehan.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.breezehan.ipc.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.breezehan.ipc.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.breezehan.ipc.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;
}
// 5.这个方法运行在客户端,当客户端远程调用此方法时:首先创建本方法所需的输入型Parcel对象_data、输出型Parcel对象_reply以及返回值对象List;然后把该方法的参数信息写入_data中(如果有参数的话);接着调用transact方法发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法调用,知道RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;然后返回_reply中的数据。
@Override
public java.util.List<com.breezehan.ipc.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.breezehan.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.breezehan.ipc.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.breezehan.ipc.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();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.breezehan.ipc.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.breezehan.ipc.Book book) throws android.os.RemoteException;
}
上面代码是自动生成的,这里只是格式化了一下。IBookManager本身是一个接口,这里又继承了IInterface接口(IInterface里面只有一个asBinder方法),但是想要在Binder中传输的接口都需要继承IInterface。
1. 里面有两个方法getBookList和addBook,这是我们在IBookManager.aidl文件中声明的。
2. 同时此处声明了一个内部类Stub,它是一个Binder,跨进程时,通过代理proxy的transact,最终在onTransact中根据不同id(不同id对应IBookManager的方法)执行相应的不同方法;同一个进程时,直接执行IBookManager中的方法。
两点需要注意:
1. 客户端发起远程请求时,会挂起一直到服务端返回数据,所以如果一个远程方法时很耗时的,不要在UI线程中发起此远程请求;
2. 由于服务端的Binder方法已经在Binder线程池,所以不管Binder方法是否耗时都应该采取同步的方式,因为已经在一个线程中了。下面是Binder机制图(来自Android开发艺术探索):
自己实现Binder
如果我们想要实现完整的IPC,需要一个Service
public class BookService extends Service{
private List<Book> mBookList = new ArrayList<>();
private IBinder binder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
当然,清单文件中需配置
<service
android:name=".BookService"
android:process=":remote" />
调用
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager iBookManager = IBookManager.Stub.asInterface(service);
try {
iBookManager.addBook(new Book(1, "Java编程思想"));
iBookManager.addBook(new Book(2, "Android开发艺术探索"));
List<Book> bookList = iBookManager.getBookList();
Log.d(TAG, "onServiceConnected:" + bookList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(this, BookService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
}
上面即是一个完整的IPC过程。
使用AIDL,我们不用关心很多东西,系统会帮助规范生成一系列的内容,那么我们能不能不借助AIDL,直接使用binder做到IPC呢。
先看service中binder的使用
private IBinder binder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
所以我们可以实现一个Stub的对象,并且实现了IBookManager的方法;总体上,一个Stub,一个IBookManager就可以完事儿了。其他还一样,如Service的onBind。
手动实现Binder步骤
(1)、声明一个AIDL性质的接口,只需要继承IInterface即可。
public interface IUserManager extends IInterface {
public static final String DESCRIPTOR = "com.breezehan.ipc.IUserManager";
public final static int TRANSACTION_getUserList = IBinder.FIRST_CALL_TRANSACTION + 0;
public final static int TRANSACTION_addUser = IBinder.FIRST_CALL_TRANSACTION + 1;
public List<User> getUserList() throws RemoteException;
public void addUser(User user) throws RemoteException;
}
看,是不是感觉像回事儿,当前此处 两个方法对象的id常量,我们仿照系统自动生成的写法,就是一个规则。无论多少方法,我们类似添加即可。
其中的public、static、final其实不明写,接口中这些都是默认的。
(2)实现Stub类,和Stub类中的Proxy代理类,不过此处写出来和系统生成几乎是一样的。
public class UserManagerImpl extends Binder implements IUserManager {
private List<User> implList = new ArrayList<>();
public UserManagerImpl() {
this.attachInterface(this, DESCRIPTOR);
}
public static IUserManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IInterface iIn = obj.queryLocalInterface(DESCRIPTOR);
if (iIn != null && iIn instanceof IUserManager) {
return (IUserManager) iIn;
}
return new UserManagerImpl.Proxy(obj);
}
@Override
public List<User> getUserList() throws RemoteException {
//逻辑代码
return implList;
}
@Override
public void addUser(User user) throws RemoteException {
//逻辑代码
implList.add(user);
}
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSACTION_addUser:
data.enforceInterface(DESCRIPTOR);
User _arg0;
if (0 != data.readInt()) {
_arg0 = User.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
addUser(_arg0);
reply.writeNoException();
return true;
case TRANSACTION_getUserList:
data.enforceInterface(DESCRIPTOR);
List<User> _result = getUserList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
return super.onTransact(code, data, reply, flags);
}
static class Proxy implements IUserManager {
private IBinder mRemote;
public Proxy(IBinder remote) {
this.mRemote = remote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public List<User> getUserList() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
List<User> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(TRANSACTION_getUserList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(User.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addUser(User user) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if (user != null) {
_data.writeInt(1);
user.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(TRANSACTION_addUser, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public IBinder asBinder() {
return mRemote;
}
}
}
完整代码参考手动实现Binder
结语
搞了半天,自己写的跟系统生成的差不多吗。手动去写,加深下Binder的理解,也知道AIDL并不是实现Binder的必须,只不过是系统的快速实现。原理都一样。
当Binder服务端因为某些原因异常终止,客户端会调用失败,最关键的是我们不知道两者连接是否已经断裂。这就需要给Binder设置一个死亡代理,当Binder停止时,客户端会收到一个通知,这时就可以做一些操作。
private IBookManager iBookManager;
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (iBookManager != null) {
iBookManager.asBinder().unlinkToDeath(deathRecipient, 0);
iBookManager = null;
//重新连接或者其他操作
}
}
};
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBookManager = IBookManager.Stub.asInterface(service);
try {
service.linkToDeath(deathRecipient, 0);
iBookManager.addBook(new Book(1, "Java编程思想"));
iBookManager.addBook(new Book(2, "Android开发艺术探索"));
List<Book> bookList = iBookManager.getBookList();
Log.d(TAG, "onServiceConnected:" + bookList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};