之前在自己应用中把一个视频播放界面采取了多进程机制实现,这样的好处是当播放界面出现任何异常会把视频播放的那个进程强制关闭而不会影响到整个应用,从用户体验上来讲有很大的改善。我们采取的做法:
android:process=":remote"
这样能轻松实现多进程机制,可是由它带来的不可预见的问题也是接踵而来主要表现的就是两个进程间如何通信的问题,所以当时我采用的aidl实现两个进程间的通信问题。
首先,我们需要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件暴露给客户端接口以便供它使用。
// IBookManager.aidl
package com.binder.demo;
import com.binder.demo.Book;
import com.binder.demo.IOnNewBookListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookListener listener);
void unRegisterListener(IOnNewBookListener listener);
}
这里我们需要理解一下AIDL的规则。
AIDL支持的数据类型:
1. 基本数据类型如:int long char boolean double
2. String CharSequence
3. List Map 数据集合必须实现了Parcelable才能被AIDL支持
4. Parcelable 一个实现了Parcelable的对象
5. AIDL 所有AIDL之间可以互相支持
了解这些之后我们就可以定义Service实现一个服务端:
public class AIDLService extends Service {
// 定义一个用来存放Book对象
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
// 定义一个用来存放数据监听
private RemoteCallbackList<IOnNewBookListener> listeners = 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);
// 逐一通知监听者数据改变了
final int size = listeners.beginBroadcast();
for (int i = 0; i < size; i++) {
IOnNewBookListener listener = (IOnNewBookListener) listeners.getBroadcastItem(i);
if (null != listener) {
listener.onNewBookArrived(book);
}
}
listeners.finishBroadcast();
}
@Override
public void registerListener(IOnNewBookListener listener) throws RemoteException {
listeners.register(listener);
Log.w("Jayuchou", "--- add new Listener ---");
}
@Override
public void unRegisterListener(IOnNewBookListener listener) throws RemoteException {
listeners.unregister(listener);
Log.w("Jayuchou", "--- remove listener ---");
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
我们里面采用了两个CopyOnWriteArrayList和RemoteCallbackList平时开发很少见的类似集合的类。
1. CopyOnWriteArrayList主要的作用:因为AIDL运行在Binder线程池中,如果有多个客户端同时并发请求的时候所以需要采用CopyOnWriteArrayList才支持并发处理。
2. RemoteCallbackList :我们知道事件监听都同时存在register和unRegister,但是由于多进程从客户端传进入的对象Binder会重新转成一个新的对象所以我们unRegister(Object obj)其中的obj并不是客户端的监听对象相反却只是他的副本而已,所以我们需要采用RemoteCallbackList一个专门用于多进程间删除跨进程的listener接口。
final int size = listeners.beginBroadcast();
for (int i = 0; i < size; i++) {
...
}
listeners.finishBroadcast();
我们看到RemoteCallbackList是必须采用beginBroadcast开始并以finishBroadcast结束。
Ok,一切就绪这时候我们客户端就开始处理这个绑定过程了。
private IBookManager bookManager;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.w("Jayuchou", "--- onServiceConnected ---");
bookManager = IBookManager.Stub.asInterface(service);
try {
bookManager.registerListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.w("Jayuchou", "--- onServiceDisconnected ---");
bookManager = null;
}
};
// 创建一个监听者 负者监听新的book对象到达
private IOnNewBookListener listener = new IOnNewBookListener.Stub() {
@Override
public void onNewBookArrived(Book book) throws RemoteException {
Log.w("Jayuchou", "this is new Book " + book.bookId + "/" + book.bookName);
}
};
客户端做的事就是将服务端的Binder转成AIDL接口所属的对象,然后就可以通过该对象调用相对应的方法实现操作了。
bookManager = IBookManager.Stub.asInterface(service);
这一切之后我们需要绑定这个Service实现两者数据传输:
Intent intent = new Intent(this, AIDLService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
一切就绪之后我们通过下面这段代码像服务端进程写入数据
bookManager.addBook(new Book(123456, "English"));
客户端进程就可以打印出
this is new Book 123456/English
当然我们需要在客户端的onDestroy去除两者的连接和绑定。
protected void onDestroy() {
if (null != bookManager && bookManager.asBinder().isBinderAlive()) {
try {
bookManager.unRegisterListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(connection);
super.onDestroy();
}
最后我们当然不能忘记在AndroidManifest中注册该Service
<service android:name="com.binder.demo.AIDLService" android:process=":aidl"/>
这就是一个当一个进程客户端数据发生改变通过Service服务端通知另一个进程的客户端做相对应的数据改变。
当然我们里面涉及了Book类和Book.aidl两个文件的包名要一致才行,否则运行的时候回出错需要留心注意一下。
其中Book就是一个实现了Parcelable接口的类
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
@Override
public int describeContents() {
return 0;
}
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];
}
};
}
而Book.aidl的声明方式:
// Book.aidl
package com.binder.demo;
parcelable Book;
切记两个包名要一致 如图我项目demo中的包名:
本来还要简单写些Binder的实现原理发现太麻烦了只能另行Google了解了。