谈到跨进程通信,实现的方式多种多样,比如上文简单讨论的Messenger,除此之外还有Bundle,文件共享,ContenProvider,Socket,还有就是本文讨论的AIDL。各种方式的和有优缺点,但是AIDL是所有方式中功能最强大的实现方式,支持一对多并发通信(弥补Messenger的缺点),适用场景是一对多通信而且有RPC需求,缺点是实现起来是上述方法中最麻烦的。
实现跨进程通信的方式一般都依赖于Android提供的Binder机制,可以说这是Android系统中最复杂最难理解的知识之一,因为笔者能力有限,并没有深入去了解。如果以后有能力能够学完Binder,一定会分享给大家
首先我们要明白我们使用AIDL的场景,依然是和Messenger一样的方式,一个服务端和一个客户端。至于如何让服务端的Service运行在其他进程相信无需多言。功能场景:(来自Android开发艺术探索中的示例)假设客户端是去图书馆的看书的同学,服务端是图书馆,同学可以查阅图书馆的属鸡数量,可以捐赠图书,图书馆则每隔一段时间新近一本书,然后通知所有的同学新书到了。显而易见,在进程中传递的数据就是Book那好,现在我们进入正题:
第一步:创建AIDL文件
我们要创建的AIDL文件有三个,一个是可以在进程中传递的数据Book实体,一个是用来管理图书的图书馆接口,一个是用来通知新书到馆,通知所有同学的接口。
1、博主使用的是AS开发,右键新建就可以直接创建一个AIDL接口,我们命名为Book.aidl。AS会直接在main目录下生成生成一个包,而且结构和java目录下的结构相同,具体实现如下:
有同学要问,这个Book是什么,其实这是我们在AIDL中声明的要在进程之间传送的数据的类型,parcelable关键字大家应该很熟悉,序列化的意思,当然了,我们需要创建一个Book实体类,这是下一步的操作了。
2、通知客户端数据发生改变的接口
这里要说明的两点是,1,因为在AIDL里面无法调用普通的接口,所以我们要创建一个aidl文件,2,因为我们在文件中使用到了Book类,所以我们要importBook类,无论这个类是否与该AIDL文件在同一个文件夹中。抽象方法void onNewBookArrived(in Book newBook);意思的是通知所有读者前来借阅。
3、服务端的核心类
在这个接口中,首先我们要import要用到的类或者文件,然后,声明一个接口,接口里面定义业务需求要用到的方法,addBook,getBooks和好理解(“in”暂时忽略,因为博主也搞不清他的作用,以后也许会为大家介绍),但是另外两个什么意思呢?熟悉面向对象编程的童鞋应该不难想到,我们的业务需求当中有个需求是这样的:当图书馆新到一本书,需要通知所有的读者前来借阅,那么我们实现这一需求的一般逻辑就是让所有的读者创建一个监听器实现这个接口,然后新书到馆之后,使用监听器调用抽象方法,这样就可以回到客户端。所以,我们需要创建这么两个方法实现监听器的注册和解注册。
第二步:创建服务端
这个步骤我们插入一个小步骤,创建好三个aidl文件之后,我们创建一个Book.java,让他实现Parcelable接口将其序列化,注意Book.java创建的目录位置,他的包名必须与前面三个aidl文件保持一致,但是,又不能在aidl目录下的包里面,因为as在编译项目的时候只会在java目录下寻找java文件。
public class Book implements Parcelable {
private String name;
private int price;
public Book(String name, int price) {
this.name = name;
this.price = price;
}
protected Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
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];
}
};
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setPrice(int price) {
this.price = price;
}
public int getPrice() {
return price;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
}
}
将Book.java序列化之后,我们clean一下项目,这样就可以根据aidl文件生成对应的java文件。好了,现在正是编写服务端的代码。
public class BookManagerService extends Service {
private AtomicBoolean isServiceDestroyed = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> books = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IOnNewBookArrivedListener> listeners = new RemoteCallbackList<>();
//生成Binder返回给客户端
private Binder mBinder = new IBookManager.Stub() {
@Override
public void addBook(Book book) throws RemoteException {
books.add(book);
Log.d("AIDL", "服务端 addBook");
}
@Override
public List<Book> getBooks() throws RemoteException {
Log.d("AIDL", "服务端 getBooks");
return books;
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
listeners.register(listener);
Log.d("AIDL", "registerListener " + listeners.beginBroadcast());
listeners.finishBroadcast();
}
@Override
public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
listeners.unregister(listener);
Log.d("AIDL", "unregisterListener " + listeners.beginBroadcast());
listeners.finishBroadcast();
}
};
//计时线程,每隔一段时间就新近一本书,并通知所有读者
private class ServiceTimer implements Runnable {
@Override
public void run() {
try {
while (!isServiceDestroyed.get()) {
Thread.sleep(3000);
Book newBook = new Book("新增的书", 111);
books.add(newBook);
final int N = listeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener listener = listeners.getBroadcastItem(i);
listener.onNewBookArrived(newBook);
}
listeners.finishBroadcast();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
//默认图书馆有两本书
@Override
public void onCreate() {
super.onCreate();
Book book1 = new Book("Android群英传", 100);
Book book2 = new Book("Android开发艺术探索", 200);
books.add(book1);
books.add(book2);
//fork新线程
new Thread(new ServiceTimer()).start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onDestroy() {
super.onDestroy();
isServiceDestroyed.set(true);
}
}
CopyOnWriteArrayList用来代替list集合在进程之间通信
RemoteCallbackList专用存监听器,如果使用CopyOnWriteArrayList来存放会出现无法解注册的尴尬局面,使用rcl的时候要注意,无论是获取他的元素个数还是遍历取出元素,都需要beginBroadcast();onNewBookArrived(newBook)操作。
第三步:创建客户端
先贴出代码如下:
public class Main2Activity extends AppCompatActivity implements View.OnClickListener {
private IBookManager bookManager;
private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
//新书到馆,服务端使用listener调用抽象方法时,会回调到此处
}
};
//远程服务链接回调,发生在bindService之后
private ServiceConnection serviceConnection2 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
/**
* IBookManger是系统根据同名的.aidl文件生成的java文件,使用它的静态方法Stub可以生成对应的实例,
* 使用生成的实例,就可以调用.adil文件中定义的方法addBook、getBooks(成功运行的前提是服务端已经给
* 实现了其抽象方法,并重写,然后通过Binder对象传到此处)
*/
bookManager = IBookManager.Stub.asInterface(service);
try {
bookManager.addBook(new Book("自然", 1000));
List<Book> books = bookManager.getBooks();
Log.d("AIDL", "绑定成功! " + books.get(2).getName());
//注意此实例化的方式:new IOnNewBookArrivedListener.Stub()
bookManager.registerListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bookManager = null;
}
};
@Override
protected void onDestroy() {
super.onDestroy();
if (bookManager != null && bookManager.asBinder().isBinderAlive()) {
try {
bookManager.unRegisterListener(listener);
Log.d("AIDL", "Activity__onDestroy");
} catch (RemoteException e) {
e.printStackTrace();
}
}
// unbindService(serviceConnection);
unbindService(serviceConnection2);
}
}
过程不再细讲,代码注释有部分解释。
PS:IPC机制是个比较有挑战的知识,学习资料是任玉刚大神的《Android开发艺术探索》,这是一本Android开发进阶书籍。博主是个Android小白,正在不断学习的路上,因为能力有限,时间有限,博文会有些许失误,希望网友不喜轻喷。有问题的话可以在评论区留言。