简介
在进程之间的通信——Messenger的文章中说到了通过Messenger进行进程间的通信,可以发现Messenger是以串行的方式处理客户端发送来的消息,如果大量的消息同时发送到服务端,服务端依然只能一个个处理,如果有大量的并发请求,那么Messenger就不太实用了。同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这个时候Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。
AIDL接口
实现AIDL的跨进程通信,需要创建AIDL接口,ALDL接口支持的数据类型:
- 基本数据类型(int、long、char 、boolean、double等)
- String和CharSequence
- List:只支持ArrayList,里面每个元素都必须能够被AIDL支持。
- Map:只支持HashMap,里面的每个元素都必须被AIDL支持。
- Parcelable: 所有实现了Parcelable接口的对象。
- AIDL:所有的AIDL接口本身都可以在AIDL中支持。
其中自定义的Parcelable对象和AIDL对象必须要显示import进来,不管它们是否和当前的AIDL文件位于同一个包内。
另外需要注意的是,如果AIDL文件中用到了自定义的Parcelable对象,必须新建一个和它同名得到AIDL文件,比在其中声明是Parcelable
AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout。in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。根据实际情况指定。
还需要注意的是AIDL的包结构在服务端和客服端需要保持一致,否则运行会出错,这是因为客服端需要反序列化服务端中和ALDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序就无法正常运行。如果是先创建了服务端的接口,就将服务端的AIDL文件复制到客户端对应的目录下,不要改动包名。
服务端
服务端首先创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的连接在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
客户端
需要绑定服务端的Service,绑定成功后,将服务顿返回的Binder对象转化成AIDL接口所属于的类型,接着就可以调用AIDL的方法了。
示例
现在有这样一个要求,在服务端中有个书库,客户端可以向书库中添加书本,并且客户端可以获取书库中的所有书本,如果服务端中添加了新的书本,可以发出通知告诉客户端。
服务端
1.创建AIDL接口
image.png
这里有一个Book的Java类,如果这个类放在aidl的包下的,需要在gradle(app)的Android节点中添加这个代码:
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/aidl']
resources.srcDirs = ['src/main/java', 'src/main/aidl']
aidl.srcDirs = ['src/main/aidl']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
}
}
或者放在普通的包下,但是这样有个缺点就是在客户端中也要在普通的包下创建一个book的类,而且还需要注意导包的问题。建议上面那种解决方法。接下来看各个类的代码
Book.java
》 这个类要实现 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();
}
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];
}
};
/**
* 当我们写完属性,和构造发现之后再实现Parcelable的时候
* 可自动生成以下的代码。
*
*/
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
/**
* 重写equals,如果Id相同,且名字相同则说明是同一本书
* @param obj
* @return
*/
@Override
public boolean equals(@Nullable Object obj) {
return this.bookId== ((Book)obj).bookId && this.bookName.equals(((Book)obj).bookName);
}
}
因为这个Book是自己自定义而定Parcelable对象,所以必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable,否则会构建失败。
// Book.aidl
package com.example.hx.servicesaidl;
parcelable Book;
IBookManger.aidl
这个AIDL的接口,里面提供了四个方法,将在Service中实现。
// IBookManager.aidl
package com.example.hx.servicesaidl;
// Declare any non-default types here with import statements
import com.example.hx.servicesaidl.Book;
import com.example.hx.servicesaidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unRegisterListener( IOnNewBookArrivedListener listener);
}
定义这个接口是为了,进行通知客户端的方法,让客户端进行回调。
// IonNewBookArrivedListener.aidl
package com.example.hx.servicesaidl;
// Declare any non-default types here with import statements
import com.example.hx.servicesaidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
接下来就是BooKMangerService,这是一个服务,需要在AndroidManifest.xml,并且设置action。
<service
android:name=".BookManagerService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.START_SERVICE"/>
</intent-filter>
BookManagerService.java
public class BookManagerService extends Service {
private static final String TAG="BookManagerService";
private AtomicBoolean atomicBoolean=new AtomicBoolean(false);
public BookManagerService() {
}
//使用RemoteCallbackList来用于删除进程的接口
private static RemoteCallbackList<IOnNewBookArrivedListener> mOnNewBookArrivedListeners =
new RemoteCallbackList<>();
private static CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "第一行代码"));
mBookList.add(new Book(2, "Android群英传"));
new Thread(new ServiceWorker()).start();
}
static class BookManagerBinder extends IBookManager.Stub {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
if (!mBookList.contains(book)){
mBookList.add(book);
}
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
if (listener!=null){
mOnNewBookArrivedListeners.register(listener);
Log.e(TAG, "@@@ 注册了监听器 " );
}
}
@Override
public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
if (listener!=null){
mOnNewBookArrivedListeners.unregister(listener);
Log.e(TAG, "@@@ 取消注册了监听器 " );
}
}
}
@Override
public IBinder onBind(Intent intent) {
return new BookManagerBinder();
}
/**
* 添加新的书本,调用该方法,通知用户
* @param book
*/
private void onNewBookArrived(Book book) throws RemoteException {
if (mBookList.contains(book)){
return;
}
mBookList.add(book);
//RemoteCallbackList与List的遍历方式不同
final int num=mOnNewBookArrivedListeners.beginBroadcast();
for (int i=0;i<num;i++){
IOnNewBookArrivedListener listener=mOnNewBookArrivedListeners.getBroadcastItem(i);
listener.onNewBookArrived(book);
Log.e(TAG, "@@@ 通知客户端,服务端添加了新的书本 "+book.bookId+" "+book.bookName);
}
mOnNewBookArrivedListeners.finishBroadcast();
}
/**
* 为了实现在服务端中添加书本的效果,开启一条线程进行添加
*
*/
private class ServiceWorker implements Runnable{
@Override
public void run() {
while (!atomicBoolean.get()){
int id=mBookList.size()+1;
Book book=new Book(id,"书本1");
try {
//添加书
onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void onDestroy() {
atomicBoolean.set(true);
super.onDestroy();
}
}
这里的继承的IBookManager.Stub是重点,这个是AIDL自动生成,也可以通过匿名类的方式初始化,然后再onBind()方法中返回,然后再客户端连接中就可以获得IBookManager对象,然后执行方法的时候,就会调用服务端重新的方法,实现了交互。
在上面的代码中用了CopyOnWriteArrayList保存书本,前面说到AIDL中所支持的只有ArrayList,但是我们用了CopyOnWriteArrayList,为什么能正常工作呢?这是因为AIDL中所支持的是抽象的List,而List是一个接口,因此虽然服务端返回CopyOnWriteArrayList,但是Binder中会按照List的规范去访问数据并最终形成一个新得ArrayList传递给客户端,所以在服务端使用CopyOnWriteArrayList是完成可以的。
客户端
将服务端的aidl的文件复制到main下面,不需改动任何东西。
image.png
客户端连接service,通过IBookManager.Stub.asInterface(service)等到对象之后就可以进行交互了。
public class MainActivity extends AppCompatActivity {
private final static String TAG = "MainActivity";
private static IBookManager mBookManager;
public static Handler handler = new MyHandler();
private MyServiceConnection myServiceConnection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通过隐式的方式启动服务
Intent intent = new Intent("android.intent.action.START_SERVICE");
myServiceConnection = new MyServiceConnection();
intent.setComponent(new ComponentName("com.example.hx.servicesaidl", "com.example.hx.servicesaidl.BookManagerService"));
bindService(intent, myServiceConnection, Context.BIND_AUTO_CREATE);
}
private static class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//获取IBookManager
mBookManager = IBookManager.Stub.asInterface(service);
try {
//获取所有的书本
List<Book> list = mBookManager.getBookList();
Log.e(TAG, "客户端: 获取服务端的书本 " + list.size());
//添加书本
Book book = new Book(3, "Android开发艺术与探索");
mBookManager.addBook(book);
Log.e(TAG, "客户端: 添加书本到服务端 " + book.bookId + " " + book.bookName);
//重新获取书本
List<Book> listNew = mBookManager.getBookList();
Log.e(TAG, "客户端: 获取服务端的书本 " + listNew.size());
//注册监听者
mBookManager.registerListener(mIOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
private static IOnNewBookArrivedListener mIOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
/**
* 这个方法的回调是在客户端的Binder线程上执行的
* 如果要进行Ui的操作,就需要切换到主线程上。
* @param newBook
* @throws RemoteException
*/
//do someThing
Message message = Message.obtain(handler, 1);
Bundle bundle = new Bundle();
bundle.putParcelable("book", newBook);
message.setData(bundle);
handler.sendMessage(message);
}
};
static class MyHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1:
Bundle bundle = msg.getData();
Book book = (Book) bundle.getParcelable("book");
assert book != null;
Log.e(TAG, "@@@ 服务端中添加新的书本 " + book.bookId + " " + book.bookName);
break;
default:
super.handleMessage(msg);
}
}
}
//销毁的时候取消注册
@Override
protected void onDestroy() {
if (mBookManager != null && mBookManager.asBinder().isBinderAlive()) {
try {
mBookManager.unRegisterListener(mIOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(myServiceConnection);
super.onDestroy();
}
}
因为这里的客户端和服务端是两个不同的应用,如果启动Service,可以参考这个链接Android在一个app中启动其他app中的service或者Activity
结果:
服务端:
image.png
客户端:
image.png
以上是实现AIDL的简单例子,但是在实际使用的时候需要注意,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端会被挂起,这个时候如果服务端方法执行比较耗时的,就会导致客户端线程长时间阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR。因此如果我们明确知道某个远程方法是耗时的,那么就要避免在UI线程上执行。客户端的onServiceConnected和onServiceDisconnected方法都运行在UI线程中,所以也不可以在这两个方法中直接调用远程的方法,本例子是为了方便,在实际开发中不要这样做。另外需要注意的是,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开启线程去进行异步任务,除非你明确知道自己在干什么,否则不要这样做。
网友评论