2.3 使用 AIDL 进行进程间通信 - TomeOkin/Learning-Notes GitHub Wiki
当 Service 与客户端处于不同的进程时,它们运行在不同的虚拟机中,内存空间也是各自独立的,因此它们之间不能直接进行通信。Android 中,使用 Binder 作为进程间通信的桥梁。使用 Binder 进行数据传输时,需要将数据类型分解为原始的数据类型,为了简化 Binder 的使用,提供了 AIDL 文件。
在 Android Studio 中,AIDL 文件存放于 src/main/aidl
文件夹(相应的,如 test/main/aidl
等)中,文件夹内的 Package 命名与一般 java Package 相同,文件扩展名为 .aidl
,编译后存放在 generated/source/aidl
文件夹中。
aidl 文件用于定义客户端与 Service 通信的接口,由于
- 原子类型(int、long 等)
- String、CharSequence
- List:本质上都会被转换为 ArrayList。
- Map:不支持通用的 Map(如 Map<String,Integer>),本质上都会被转换为 HashMap。
- Parelable:所有实现了 Parelable 的自定义类型都必须在 aidl 文件中声明和显示导入(即使位于同一个 Package 中)。
- aidl 文件中定义的 aidl 接口。
aidl 接口只支持定义方法,该方法使用到的所有非原子类型的参数(不包含 aidl 接口)都必须声明参数的传递方向,可以是 in, out 或者 inout。方法如果包括接口做参数使用,需要使用的是 aidl 接口而不是普通的 java 接口。
aidl 中不能使用静态域。
// Book.aidl
package com.ryg.chapter_2.aidl;
parcelable Book;
// IOnNewBookArrivedListener.aidl
package com.ryg.chapter_2.aidl;
import com.ryg.chapter_2.aidl.Book;
interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
// IBookManager.aidl
package com.ryg.chapter_2.aidl;
import com.ryg.chapter_2.aidl.Book;
import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener listener);
void unregisterListener(IOnNewBookArrivedListener listener);
}
以上代码编译后,可以看到两个文件:
// IOnNewBookArrivedListener.java
package com.ryg.chapter_2.aidl;
public interface IOnNewBookArrivedListener extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
// 客户端使用的 Binder
public static abstract class Stub extends android.os.Binder
implements com.ryg.chapter_2.aidl.IOnNewBookArrivedListener {
private static final java.lang.String DESCRIPTOR =
"com.ryg.chapter_2.aidl.IOnNewBookArrivedListener";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
// 如果是 Service 与客户端处于同个进程,则返回原始的对象,否则构造一个 Proxy 对象,并返回
public static com.ryg.chapter_2.aidl.IOnNewBookArrivedListener asInterface(
android.os.IBinder obj) {...}
@Override
public android.os.IBinder asBinder() {
return this;
}
// 每个方法都会定义一个对应的代码,用于下文与 code 比较
static final int TRANSACTION_onNewBookArrived =
(android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
// 根据 code 选择找到对应的方法,接着对 Service 执行响应的方法
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
int flags) throws android.os.RemoteException {...}
// 代理类
private static class Proxy implements com.ryg.chapter_2.aidl.IOnNewBookArrivedListener {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
// 代理对象提供的与服务器接口具有相同签名的方法,在该方法中,会对数据进行序列化传输;
// 同时,还会调用 mRemote.transact() 进行数据传输等
@Override
public void onNewBookArrived(com.ryg.chapter_2.aidl.Book newBook)
throws android.os.RemoteException {...}
}
}
public void onNewBookArrived(com.ryg.chapter_2.aidl.Book newBook)
throws android.os.RemoteException;
}
另一个文件是 IBookManager.java
,由于结构相同,就不再列出。
可以看出,当客户端与 Service 处于不同进程时,客户端需要通过代理对象执行操作,而代理对象与 Service 之间的交互,则由系统来完成,此处不深入分析。
默认情况下,当客户端调用 Service 端的接口时,客户端进程会被挂起直到 Service 端执行完毕,如果 Service 中对应的方法执行时间过长,客户端会阻塞。解决方法有两种,一种是客户端开线程去操作服务器接口,另一种方法是使用关键字 oneway 对 aidl 接口进行声明,第二种方式下,当客户端完成数据传输后就会立刻返回,而不会等待 Service 的执行,这种情况下,如果需要获得 Service 端的执行结果,需要用其他方式。
服务器端需要实现生成的 Stub
接口,也即实现一个 Binder,以响应客户端的请求。
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
SystemClock.sleep(5000);
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener)
throws RemoteException {
mListenerList.register(listener);
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
Log.d(TAG, "registerListener, current size:" + N);
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener)
throws RemoteException {
boolean success = mListenerList.unregister(listener);
if (success) {
Log.d(TAG, "unregister success.");
} else {
Log.d(TAG, "not found, can not unregister.");
}
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
Log.d(TAG, "unregisterListener, current size:" + N);
}
};
@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;
}
@Override
public void onDestroy() {
mIsServiceDestoryed.set(true);
super.onDestroy();
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if (l != null) {
try {
l.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}
}
有几点需要注意:
(1)Service 端会从 Binder 连接池中获得一个常规的远程调用,该调用以同步方式执行,如果 Service 端执行时间过长,会影响 Service 端对其他请求的响应。当然,如果 Service 不需要返回结果给客户端,就可以直接开启线程进行操作,如果 Service 需要返回结果给客户端,则还需要提供一个回调接口(see Binding Callback)。一般情况下,连接池提供的线程数可以满足要求,可以不用担心服务器端被阻塞的问题。
(2)Service 支持并发访问,因此,Service 端需要保证线程安全,因此,上面代码使用了 CopyOnWriteArrayList
以支持并发读写,类似支持并发读写的,还有 ConcurrentHashMap
。
(3)由于数据传输到 Service 端的时候,数据对象已经是重新构造出来的了,因此,如果有注册监听器、注销监听器之类的需求,则不应该使用普通的 List 来管理。如上,使用了 RemoteCallbackList
来处理接口。
当我们往 RemoteCallbackList
中注册一个 callback 时,RemoteCallbackList
通过 callback.asBinder()
获得 Binder,并将其作为 key,callback 作为 value,放入 ArrayMap<IBinder, Callback>
。因为 Binder 能够唯一标识一个当前绑定的客户端,因此可以借助 Binder 来区分 callback。RemoteCallbackList
的 beginBroadcast()
和 finishBroadcast()
需要成对使用,因此,获取已注册的监听器个数时,需要使用:
final int N = mListenerList.beginBroadcast();
mListenerList.finishBroadcast();
另外,当客户端进程终结后,RemoteCallbackList
能够自动移除客户端注册的监听器。
public class BookManagerActivity extends Activity {
private static final String TAG = "BookManagerActivity";
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private IBookManager mRemoteBookManager;
@SuppressLint("HandlerLeak") private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.d(TAG, "receive new book :" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
List<Book> list = bookManager.getBookList();
Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName());
Log.i(TAG, "query book list:" + list.toString());
Book newBook = new Book(3, "Android进阶");
bookManager.addBook(newBook);
Log.i(TAG, "add book:" + newBook);
List<Book> newList = bookManager.getBookList();
Log.i(TAG, "query book list:" + newList.toString());
bookManager.registerListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
mRemoteBookManager = null;
Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());
}
};
private IOnNewBookArrivedListener mOnNewBookArrivedListener =
new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
}
};
@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);
}
public void onButton1Click(View view) {
Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
if (mRemoteBookManager != null) {
try {
List<Book> newList = mRemoteBookManager.getBookList();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
protected void onDestroy() {
if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) {
try {
Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
}
这里,同样也有一些需要注意的地方:
(1)通过 IInterface.Stub.asInterface(IBinder)
获得了 aidl 接口实例,在操作对应的方法的时候,要注意避免主线程被阻塞,最好开线程进行操作,如 onButton1Click()
里做的。
(2)当 Service 回调监听器接口或者其他接口(IInterface.Stub)时,不能保证其一定是在主线程,因此,这里借助 Handler 来传递消息,保证是在主线程进行操作,也可以使用 EventBus.getDefault().post(new SomeEvent())
来实现。
(3)可以通过 IInterface.asBinder().isBinderAlive()
判断 Binder 是否还存活,不过返回 true时,也有可能是在返回查询结果的时候,Binder 已经结束了。因此,需要防止抛出 RemoteException
异常。另外,也可以通过 IInterface.asBinder().pingBinder()
来判断。
(4)当我们的 Service 和 客户端是在同个应用内时,如上,可以直接使用显示意图去绑定。但如果不是同个应用呢?
在 Android 中,隐式意图有如下几种情况:
- 安装一个新应用时,如果系统中已经有其它已安装的应用与新应用具有相同 的 ContentProvider,那么新应用会安装失败(INSTALL_FAILED_CONFLICTING_PROVIDER)。
- 安装一个新应用时,如果系统中已经有其它已安装的应用与新应用具有相同 的 Service,Android 5.0 起,新应用会安装失败(INSTALL_FAILED_CONFLICTING_PROVIDER),其他的可以正常安装,
android:priority
优先级高的会接收到访问的请求;如果优先级相同,先安装的应用会接收到访问的请求。
在 Android 5.0 起,不能再使用隐式意图绑定 Service。
因此,当 Service 和 客户端不是在同个应用内时,最好的方式是通过 PackageManager
获得显式的意图后再启动:
<service android:name="BookManagerService">
<intent-filter>
<action android:name="com.ryg.chapter_2.aidl.IBookManagerService" />
</intent-filter>
</service>
Intent implicit = new Intent(IBookManager.class.getName());
List<ResolveInfo> matches = getActivity().getPackageManager()
.queryIntentServices(implicit, 0);
if (matches.size() == 0) {
// Cannot find a matching service!
} else if (matches.size() > 1) {
// Found multiple matching services!
} else {
Intent explicit = new Intent(implicit);
ServiceInfo svcInfo = matches.get(0).serviceInfo;
ComponentName cn = new ComponentName(svcInfo.applicationInfo.packageName, svcInfo.name);
explicit.setComponent(cn);
bindService(explicit, this, Context.BIND_AUTO_CREATE);
}
一般情况下,Service 不会轻易被系统结束,但如果真的发生,服务器端的 Binder 连接会断裂(Binder 死亡),那么客户端的功能就不能正常进行了。在 Android 中,Binder 提供了一种称为 link-to-death
的机制,当通过 Binder 连接的提供端,也即我们这里的 Service,不存在时,客户端进程可以接收到通知,过程如下:
首先,我们需要实现 IBinder.DeathRecipient
接口,在 Service 死亡的的时候,我们会接收到回调;接着通过 IInterface.asBinder().linkToDeath(mDeathRecipient, 0);
注册该回调。在 Service 结束的时候,我们会通过 DeathRecipient
里的 binderDied()
获得通知,在这个时候,onServiceDisconnected()
也是会接收到通知的,区别在于,binderDied()
是在客户端的 Binder 线程池中被回调的,而 onServiceDisconnected()
是在 UI 线程被回调的。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
if (mRemoteBookManager == null) return;
mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mRemoteBookManager = null;
// TODO:这里重新绑定远程Service
}
};
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
// other
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
mRemoteBookManager = null;
Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());
}
};
我们可以通过 permission 来添加访问权限,在 onTransact
中进行认证:
<permission
android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
private Binder mBinder = new IBookManager.Stub() {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
// 权限认证
int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
Log.d(TAG, "check=" + check);
if (check == PackageManager.PERMISSION_DENIED) {
return false;
}
// 包名或者签名进行认证
// 签名方式参见 https://github.com/commonsguy/cw-omnibus/blob/master/Binding/SigCheck/Service/app/src/main/java/com/commonsware/android/advservice/remotebinding/sig/DownloadService.java
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(
getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
Log.d(TAG, "onTransact: " + packageName);
if (!packageName.startsWith("com.ryg")) {
return false;
}
// must do it
return super.onTransact(code, data, reply, flags);
}
// other is hided
};
客户端使用时,需要添加访问的权限:
<uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />
《Android 开发艺术探索》 本文中的例子大部分都来自这里
《The Busy Coders Guide to Android Development》
Binders & Death Recipients
Android Interface Definition Language (AIDL)