上一篇博文介绍了使用Messenger来进行IPC,可以发现Messenger是以串行的方式处理客户端发来的消息,如果有大量的消息发过来只能一个一个的处理,就先的不太合适。这篇博文介绍使用AIDL进行进程间通信,使用IPC。
AIDL的大概实现过程如下
1、服务端
服务端首先要创建一个Service用来坚挺客户端的链接请求,然后创建一个AIDL文件,将暴露给客户端的接口在合格AIDL文件中声明,然后在Service中实现这个AIDL接口即可。
2、客户端
客户端所要做的事情就稍微简单点,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着调用AIDL中的方法就可以了。
工程目录:
首先创建一个IBookManager.aidl
package com.qian.aidlipc;
import com.qian.aidlipc.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
在aidl文件中声明了两个方法,这两个方法就是暴露给客户端的,我们需要服务端实现。另外上面的aidl文件还用到了Book这个类,所以也要创建Book.aidl,然后添加如下内容
package com.qian.aidlipc;
parcelable Book;
Book.java是一个实体类,继承了Parcelable,可序列化
package com.qian.aidlipc;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book() {
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(bookId);
out.writeString(bookName);
}
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
public Book createFromParcel(Parcel in) {
return new Book(in);
}
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
@Override
public String toString() {
return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
}
}
3、远程服务端Service的实现
上面定义了AIDL接口,接下来要再服务端实现这个AIDL接口,先创建一个BookManagerService,代码如下:
package com.qian.aidlipc;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
public class BookManagerService extends Service {
private static final String TAG = "BookManagerService";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
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 onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
上面是一个服务端Service的典型实现,首先在onCreate中中石化添加了两本图书的信息,然后创建了一个Binder对象并在onBind方法中返回它,这个对象继承自IBookManager.Stub对象,并实现了它内部的AIDL方法,这个过程在浅谈Binder机制一文中已经详细说明了,这里不多说,也就是前面所说的,将暴露给客户端的接口在Service中实现。其中实现了getBookList,addBook两个方法,实现过程也比较简单,只是为了说明原理,不用搞那么复杂,实际情况中可能会出现很复杂的情况,但是原理都一样。注意这里采用CopyOnWriteArrayList,这个CopyOnWriteArrayList支持并发读写。实际上AIDL方法是在服务端的Binder线程池中执行的,这样就有多有客户端同时访问的情况,所以需要处理线程同步问题,而这个CopyOnWriteArrayList就可以处理线程同步。
然后注册一下远程服务:
<service
android:name="com.qian.aidlipc.BookManagerService"
android:process=":remote" >
</service>
4、客户端的实现
客户端的实现,首先要绑定服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后通过这个接口去调用服务端的远程方法,代码如下:
package com.qian.aidlipc;
import java.util.List;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.qian.aidlipc.R;
public class BookManagerActivity extends Activity {
private static final String TAG = "BookManagerActivity";
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = bookManager.getBookList();
Log.i(TAG, "query book list:" + list.toString());
Book newBook = new Book(3, "夜莺与玫瑰");
bookManager.addBook(newBook);
Log.i(TAG, "add book:" + newBook);
List<Book> newList = bookManager.getBookList();
Log.i(TAG, "query book list:" + newList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@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() {
unbindService(mConnection);
super.onDestroy();
}
}
代码很简单,其中IBookManager bookManager = IBookManager.Stub.asInterface(service);就是将服务端返回的Binder对象转换成AIDL接口,再通过这个接口调用远程方法。注册xml然后观察一下Log:
先是getBookList。得到两本书 Android和IOS,然后调用addBook添加了一本书夜莺与玫瑰,然后又调用getBookList。和我们预想的结果是一样的,远程调用方法成功了。
最后再说明一下:
当客户端远程调用服务的方法时,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端的方法比较耗时,就会出现客户端线程尝试加被阻塞,而如果这个客户端是UI线程的话,就会出现NR,客户端的onServiceConnected和onServiceDisconnected都是运行在UI线程,所以尽量避免UI线程调用远程方法。而服务端的方法本身就运行在Binder线程池里,所以本身可以执行大量耗时操作,所以不必再开线程执行耗时操作。另外服务端也有可能会运行客户端的回调方法,而如果客户端的这个回调方法比较耗时的话,也会造成服务端ANR,所以最好保证服务端回调接口的地方运行在非UI线程。