2018-07-23
下面主要是抄的《Android开发艺术探索》2.4.4章节,看过书的就不用看了。
上一篇主要抄了Messenger来进行进程间通信的方法,可以发现Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了。同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装从而方便上层的调用而已。在上一节中,我们介绍了Binder的概念,大家对Binder也有了一定的了解,在Binder的基础上我们可以更加容易地理解AIDL。这里先介绍使用AIDL来进行进程间通信的流程,分为服务端和客户端两个方面。
服务端
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
客户端
客户端所要做事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。
具体实现方式
AIDL接口的创建
收看看AIDL接口的创建,如下所示创建了一个后缀为AIDL的文件,在里面声明了一个接口和两个方法。创建AIDL文件的方式可以看这个Android多进程(一)
// IBookManager.aidl
package com.huangyuanlove.testandroid;
// Declare any non-default types here with import statements
import com.huangyuanlove.testandroid.Book;
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);
List<Book> getBookList();
void addBook(in Book book);
}
在AIDL文件中,并不是所有的数据类型都是可以使用的,只支持如下几种类型:
- 基本数据类型(int、long、char、boolean、double等);
- String和CharSequence;
- List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
- Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;
- Parcelable:所有实现了Parcelable接口的对象;
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
以上6种数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个包内。比如IBookManager.aidl这个文件,里面用到了Book这个类,这个类实现了Parcelable接口并且和IBookManager.aidl位于同一个包中,但是遵守AIDL的规范,我们仍然需要显式地import进来:com.huangyuanlove.testandroid.Book。
另外一个需要注意的地方是,如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。在上面的IBookManager.aidl中,我们用到了Book这个类,所以,我们必须要创建Book.aidl,然后在里面添加如下内容:
package com.huangyuanlove.testandroid;
parcelable Book;
远程服务端Service的实现
上面讲述了如何定义AIDL接口,接下来实现这个接口。先创建一个service,代码如下:
package com.huangyuanlove.testandroid;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class BookManagerService extends Service {
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"Android"));
mBookList.add(new Book(2,"IOS"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
上面是一个服务端Service的典型实现,首先在onCreate中初始化添加了两本图书的信息,然后创建了一个Binder对象并在onBind中返回它,这个对象继承IBookManager.Stub
并实现了它内部的AIDL方法,注意这里采用了CopyOnWriteArrayList,这个CopyOnWriteArrayList支持并发读/写。在前面
我们提到,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,而我们这里直接使用CopyOnWriteArrayList来进行自动的线程同步。AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端。所以,我们在服务端采用CopyOnWriteArrayList是完全可以的。和此类似的还有ConcurrentHashMap,然后我们需要在XML中注册这个Service:
<service android:name=".BookManagerService"
android:process=":remote"/>
客户端的实现
客户端的实现就比较简单了,首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了,代码如下所示:
public class MainActivity extends AppCompatActivity {
private ServiceConnection mConnection = new ServiceConnection() {
@Override