本文是任玉刚《Android开发艺术探索》的学习笔记,介绍Binder的使用以及上层原理
什么是Binder
- 直观上,Binder是Android的一个类,实现IBider借口
- 从IPC角度,Binder是Android的一种跨进程通信方式
- 从硬件角度,Binder是一种虚拟的物理设备,设备驱动是/dev/binder
- 从Framework角度,Binder是ServiceManager连接各种Manager和ManagerService的桥梁
- 从应用层角度,Binder是客户端和服务端进行通信的媒介。当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据。这里的服务包括普通服务和基于AIDL的服务
通过AIDL分析Binder的工作机制
Android开发中,Binder主要用在Service中,包括AIDL,其中普通Service中的Binder不涉及进程间的通信,所以较为简单,无法触及Binder的核心。
AIDL (Android Interface Definition Language) 是一种IDL 语言,编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程(IPC)的目的。如果需要在一个Activity中, 访问另一个Service中的某个对象, 需要先将对象转化成AIDL可识别的参数(可能是多个参数), 然后使用AIDL来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象。
实例
涉及三个文件:
Book.java 表示图书信息的类
package com.example.zzh.binderdemo;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.bookId);
dest.writeString(this.bookName);
}
protected Book(Parcel in) {
this.bookId = in.readInt();
this.bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public String toString() {
return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
}
}
Book.aidl Book类在AIDL中的声明
package com.example.zzh.binderdemo;
parcelable Book;
IBookManager.aidl 定义的接口
package com.example.zzh.binderdemo;
import com.example.zzh.binderdemo.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
编译后,在model工程的build/generated目录下,系统为IBookManager.aidl
生成了Binder类IBookManager.java
其中这个类核心内容是内部类Stub
和Stub的内部代理类Proxy
。
Stub
是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程;
当二者在不同进程中时,方法调用需要走transact过程,由Proxy
来完成。
介绍针对这两个类的买个方法的含义:
DESCRIPTOR
Binder的唯一标识,一般用当前Binder的类名表示,如***.***.IBookManager
asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程,
如果客户端和服务端位于同一进程,那么这个方法返回就是服务端的Stub对象本身,反则返回的是
系统封装后的Stub.proxy对象。asBinder
返回当前Binder对象。
onTransact
这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统封装后
交由此方法来处理。当这个方法返回false时,那么客户端的请求会失败,因此可以利用这个特性来做
权限验证(避免随便一个进程都能远程调用我们的服务)。Proxy#getBookList
Proxy#addBook
这两个方法运行在客户端中。内部实现是:首先创建该方法所需要的输入型Parcel对象_data、输出型
Parcel对象_reply和返回值对象List(addBook没有返回值);然后把该方法的参数信息写入_data中
(如果有参数的话);接着调用transact方法来发起RPC
(远程过程调用)请求,同时当前线程挂起;
然后服务的onTransaction方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取
出RPC过程的返回结果;最后返回_reply中的数据。
- 当客户端发起远程请求时,由于当前线程会挂起直至服务端进程返回数据,所以如果一个远程方法是耗时的,那么不能在UI线程中发起此远程请求。
- 由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应采用同步的方式去实现,因为它已经运行一个线程中。
Binder的工作机制图
Binder两个重要的方法
Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候与服务端的Binder连接断裂,导致远程调用失败。
linkToDeath
unlinkToDeath
给Binder设置一个死亡代理,当Binder死亡时,收到通知,可以重新发起连接请求从而恢复连接:
首先,声明一个DeathRecipient对象。当Binder死亡时候,系统就回调DeathRecipient接口方法binderDied
,
然后就可以移除之前绑定的Binder代理并重新绑定远程服务。
IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mRemoteBookManager == null) {
return;
}
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
//TODO 这里重新绑定远程Service
}
};
其次,在客户端绑定远程Service成功后,给binder设置死亡代理
IBookManager bookManager = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);
另外,通过Binder的方法isBinderAlive
也可以判断Binder是否死亡。
基于AIDL的IPC
使用AIDL进行进程间通信的流程
服务端
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的
接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。客户端
首先绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,最后
在Service中实现这个AIDL接口即可。
AIDL文件支持的数据类型
- 基本数据类型(int、long、char、boolean等)
- String和CharSequence
- List: 只支持ArrayList, 里面每个元素都必须能够被AIDL支持
- Map: 只支持HashMap, 里面的每个元素都必须被AIDL支持,包括key和value
- Parcelabe: 所有实现了Parcelable接口的对象
AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
其中自定义的Parcelable对象和AIDL对象必须要显式import进来,无论是否位于同一个包内。
如果AIDL文件用到了Parcelabl对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为
Parcelable类型。如上个实例中Book.aidl
RemoteCallbackList
如果服务端提供一些监听给客户端,那么我们需要采用RemoteCallbackList
,才能实现在客户端取消
注册功能。RemoteCallbackList
是系统专门提供的用于删除跨进程listener的接口。
其中原理是:虽然跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一
共同点,那就是它们底层的Binder是同一个,利用这个特性(Array
实例
远程服务端Service的实现
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList =
new RemoteCallbackList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "IOS"));
Log.d(TAG, "BMS add");
new Thread(new ServiceWorker()).start();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if (l != null) {
l.onNewBookArrived(book);
}
}
mListenerList.finishBroadcast();
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestoryed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size() + 1;
Book newBook = new Book(bookId, "new book#" + bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
客户端的实现
public class BookManagerActivity extends Activity {
private static final String TAG = "BookManagerActivity";
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteBookManager;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.d(TAG, "receive new book: " + msg.obj);
break;
default:
break;
}
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
mRemoteBookManager = bookManager;
List<Book> list = bookManager.getBookList();
Log.i(TAG, "onServiceConnected: query book list, list type:" + list.getClass().getCanonicalName());
Log.i(TAG, "onServiceConnected: query book list:" + list.toString());
Book newBook = new Book(3, "Android开发艺术探索");
bookManager.addBook(newBook);
Log.i(TAG, "add Book:" + newBook);
List<Book> newList = bookManager.getBookList();
Log.i(TAG, "onServiceConnected: query book list:" + newList.toString());
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mRemoteBookManager = null;
Log.e(TAG, "binder died.");
}
};
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new
IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
if (mRemoteBookManager != null
&& mRemoteBookManager.asBinder().isBinderAlive()) {
try {
Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
}
在AIDL中使用权限验证功能的方式(保证权限验证成功的一方才能连接远程服务)
- 在onBind中验证,验证不通过返回null,这样验证失败的客户端直接无法绑定服务。
- 在服务端的onTransact方法中进行权限验证,如果验证失败就返回false,这样服务端就不会终止执行AIDL中的方法从而达到保护服务端的效果。