在上一节中,主要阐述了Parcelable的序列化和AIDL文件的书写规范,以及相应的示例。这一节,将实现一个完整的通过AIDL实现进程通信的示例。首先要说明的是,这个例子并不符合Android的开发规范, AIDL是在涉及跨应用并需要处理多线程的情况下才有必要使用的,而这个例子并不涉及跨应用的情况。为了方便起见,此例将客户端和服务端放在了同一应用下的不同进程。(AIDL代码和Parcelable代码沿用上文示例)
准备工作
在示例中,创建一个简单的Activity和一个简单的Service。以Activity作为客户端, 以Service作为服务端, 在AndroidManifest.xml文件中将service的进程设为 :remote
接下来实现Service中的相关内容, 首先要实现Binder
//定义一个Binder, IMyAidlInterface.Stub类继承自Binder类, 并实现了IMyAidlInterface接口的抽象类, 经AIDL文件编译后生成
private final IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
@Override
public NewsEntity getData(int id, String title) throws RemoteException {
System.out.println("Server: Thread.currentThread().getName() = " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
callbackClient();
}catch (Exception e){
e.printStackTrace();
}
return new NewsEntity(id, title);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
Activity 的相关实现
//在Activity中要实现ServiceConnection接口的相关方法, 例如.
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iService = IMyAidlInterface.Stub.asInterface(service);
System.out.println("mConnection: Thread.currentThread().getName() = " + Thread.currentThread().getName());
try {
new Thread(new Runnable() {
@Override
public void run() {
try {
NewsEntity entity = iService.getData(1, "Hello Activity");
System.out.println(entity.toString());
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
客户端调用服务端
首先要在Activity中绑定Service
Intent intent = new Intent(this, RemoteService.class);
intent.setAction(RemoteService.class.getName());
bindService(intent, mConnection, BIND_AUTO_CREATE);
在与Service连接成功后, 需要获取到对应的接口对象
iService = IMyAidlInterface.Stub.asInterface(service);
调用Service方法
new Thread(new Runnable() {
@Override
public void run() {
try {
NewsEntity entity = iService.getData(1, "Hello Activity");
System.out.println(entity.toString());
}catch (Exception e){
e.printStackTrace();
}
}
).start();
为什么另起线程调用?
在AIDL文件中, 若没有在对应的方法中声明oneway, 则调线程将会处于阻塞状态,直到被调用的方法执行完毕.在客户端的主线程中跨进程调用,有可能导致ANR的发生.
服务端回调客户端
要实现服务端回调客户端, 需要借助到RemoteCallbackList类.RemoteCallbackList的主要作用是为了负责维护一个远程接口列表,比如执行服务端对客户端的回调.
- 新建一个.aidl文件: IClientInterface.aidl
// IClientInterface.aidl
package th.how.bean;
// Declare any non-default types here with import statements
interface IClientInterface {
void callbackClient();
}
- 修原来的.aidl文件,新添加两个方:
register(IClientInterface callback);
unRegister(IClientInterface callback);
package th.how.bean;
import th.how.bean.NewsEntity;
import th.how.bean.IClientInterface;
interface IMyAidlInterface {
NewsEntity getData(int id, String title);
void register(IClientInterface callback);
void unRegister(IClientInterface callback);
}
- 修改Service文件, 增加新的RemoteCallbackList成员, 和修改Binder, 实现aidl文件中新增的两个方法, 以及添加一个回调事件:callbackClient().
private RemoteCallbackList<IClientInterface> mCallback = new RemoteCallbackList<>();
private final IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
@Override
public NewsEntity getData(int id, String title) throws RemoteException {
System.out.println("Server: Thread.currentThread().getName() = " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
callbackClient();
}catch (Exception e){
e.printStackTrace();
}
return new NewsEntity(id, title);
}
@Override
public void register(IClientInterface callback) throws RemoteException {
mCallback.register(callback);
}
@Override
public void unRegister(IClientInterface callback) throws RemoteException {
mCallback.unregister(callback);
}
};
private void callbackClient(){
int num = mCallback.beginBroadcast();
for(int i = 0; i < num; i ++){
IClientInterface iClientInterface = mCallback.getBroadcastItem(i);
if(iClientInterface != null){
try{
iClientInterface.callbackClient();
}catch(Exception e){
e.printStackTrace();
}
}
}
mCallback.finishBroadcast();
}
- 在客户端实现IClientInterface.aidl编译后生成的接口, 并调用service 的register方法
private IClientInterface client = new IClientInterface.Stub() {
@Override
public void callbackClient() throws RemoteException {
System.out.println("Client: Thread.currentThread().getName() = " + Thread.currentThread().getName());
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iService = IMyAidlInterface.Stub.asInterface(service);
System.out.println("mConnection: Thread.currentThread().getName() = " + Thread.currentThread().getName());
try {
iService.register(client);
new Thread(new Runnable() {
@Override
public void run() {
try {
NewsEntity entity = iService.getData(1, "Hello Activity");
System.out.println(entity.toString());
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
通过以上步骤,就大概实现了Service和Client的双向通信.
补充
- DeathRecipient IBinder的死亡通知机制
DeathRecipient是一个接口类,设计出来是为了当对应Ibinder的进程死亡时另一进程能接受回调通知
IBinder.java
/**
*为这个BInder注册一个死亡通知容器.如果这个Binder意外死
*亡,则对应的DeathRecipient对象的binderDied方法将会被调
*用
*它只能用来接受来自远端Binder的通知,而不能用来处理本地
*IBinder的死亡
* Register the recipient for a notification if this binder
* goes away. If this binder object unexpectedly goes away
* (typically because its hosting process has been killed),
* then the given {@link DeathRecipient}'s
* {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
* will be called.
*
* <p>You will only receive death notifications for remote binders,
* as local binders by definition can't die without you dying as well.
*
* @throws RemoteException if the target IBinder's
* process has already died.
*
* @see #unlinkToDeath
*/
public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException;
/**
*移除已经注册了的死亡通知, 如果这个IBinder对象死掉,容器也不会再
*发出通知
* Remove a previously registered death notification.
* The recipient will no longer be called if this object
* dies.
*
* @return {@code true} if the <var>recipient</var> is successfully
* unlinked, assuring you that its
* {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
* will not be called; {@code false} if the target IBinder has already
* died, meaning the method has been (or soon will be) called.
*
* @throws java.util.NoSuchElementException if the given
* <var>recipient</var> has not been registered with the IBinder, and
* the IBinder is still alive. Note that if the <var>recipient</var>
* was never registered, but the IBinder has already died, then this
* exception will <em>not</em> be thrown, and you will receive a false
* return value instead.
*/
public boolean unlinkToDeath(DeathRecipient recipient, int flags);
- RemoteCallbackList
负责维护一个远程接口列表, 如执行从服务端到客户端的回调.它保持了对一系列注册的回调事件的追踪.通过调用IInterface.asBinder()方法可以分辨出它们.
使用mCallbacks的对象锁,应对多线程调用.并在不持有列表锁的情况下,可以线程安全的遍历列表.
public class RemoteCallbackList<E extends IInterface> {
//一个存储IBinder与Callback键值对的Map
/*package*/ ArrayMap<IBinder, Callback> mCallbacks
= new ArrayMap<IBinder, Callback>();
private Object[] mActiveBroadcast;
private int mBroadcastCount = -1;
private boolean mKilled = false;
//实现DeathRecipient接口, 当IBinder对应的进程死亡后,移除对应的Callback
private final class Callback implements IBinder.DeathRecipient {
final E mCallback;
final Object mCookie;
Callback(E callback, Object cookie) {
mCallback = callback;
mCookie = cookie;
}
public void binderDied() {
synchronized (mCallbacks) {
mCallbacks.remove(mCallback.asBinder());
}
onCallbackDied(mCallback, mCookie);
}
}
/**
* Simple version of {@link RemoteCallbackList#register(E, Object)}
* that does not take a cookie object.
*/
public boolean register(E callback) {
return register(callback, null);
}
/**
*使用synchronized关键字, 保证对mCallbacks操作的线程安全
*构造Callback对象, 完成binder注册死亡通知事件,
*存储键值对
* @see #unregister
* @see #kill
* @see #onCallbackDied
*/
public boolean register(E callback, Object cookie) {
synchronized (mCallbacks) {
if (mKilled) {
return false;
}
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
return true;
} catch (RemoteException e) {
return false;
}
}
}
/**
*从mCallbacks中移除对应的callback
*并解绑死亡通知
* @see #register
*/
public boolean unregister(E callback) {
synchronized (mCallbacks) {
Callback cb = mCallbacks.remove(callback.asBinder());
if (cb != null) {
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
return true;
}
return false;
}
}
/**
*清空mCallbacks
*
* @see #register
*/
public void kill() {
synchronized (mCallbacks) {
for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
Callback cb = mCallbacks.valueAt(cbi);
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
}
mCallbacks.clear();
mKilled = true;
}
}
/**
*callback对应的进程死亡后的回调
*/
public void onCallbackDied(E callback) {
}
/**
* Called when the process hosting a callback in the list has gone away.
* The default implementation calls {@link #onCallbackDied(E)}
* for backwards compatibility.
*
* @param callback The callback whose process has died. Note that, since
* its process has died, you can not make any calls on to this interface.
* You can, however, retrieve its IBinder and compare it with another
* IBinder to see if it is the same object.
* @param cookie The cookie object original provided to
* {@link #register(E, Object)}.
*
* @see #register
*/
public void onCallbackDied(E callback, Object cookie) {
onCallbackDied(callback);
}
/**
*准备调用已经注册的回调, 为数组mActiveBroadcast赋值
*需要注意的是同一时间,只能有一个broadcast是激活的.
* @see #getBroadcastItem
* @see #finishBroadcast
*/
public int beginBroadcast() {
synchronized (mCallbacks) {
if (mBroadcastCount > 0) {
throw new IllegalStateException(
"beginBroadcast() called while already in a broadcast");
}
final int N = mBroadcastCount = mCallbacks.size();
if (N <= 0) {
return 0;
}
Object[] active = mActiveBroadcast;
if (active == null || active.length < N) {
mActiveBroadcast = active = new Object[N];
}
for (int i=0; i<N; i++) {
active[i] = mCallbacks.valueAt(i);
}
return N;
}
}
/**
* 从mActiveBroadcast中检索一item
*只有在beginBroadcast和finishBroadcast之间获取值才有效需要处理.
*由于返回的callback对应的进程可能kill了, 所以需要处理RemoteException(简单处理, 忽略掉它)
* @param index Which of the registered callbacks you would like to
* retrieve. Ranges from 0 to 1-{@link #beginBroadcast}.
*
* @return Returns the callback interface that you can call. This will
* always be non-null.
*
* @see #beginBroadcast
*/
public E getBroadcastItem(int index) {
return ((Callback)mActiveBroadcast[index]).mCallback;
}
/**
* Retrieve the cookie associated with the item
* returned by {@link #getBroadcastItem(int)}.
*
* @see #getBroadcastItem
*/
public Object getBroadcastCookie(int index) {
return ((Callback)mActiveBroadcast[index]).mCookie;
}
/**
*清理掉mActiveBroadcast
* @see #beginBroadcast
*/
public void finishBroadcast() {
synchronized (mCallbacks) {
if (mBroadcastCount < 0) {
throw new IllegalStateException(
"finishBroadcast() called outside of a broadcast");
}
Object[] active = mActiveBroadcast;
if (active != null) {
final int N = mBroadcastCount;
for (int i=0; i<N; i++) {
active[i] = null;
}
}
mBroadcastCount = -1;
}
}
/**
* Returns the number of registered callbacks. Note that the number of registered
* callbacks may differ from the value returned by {@link #beginBroadcast()} since
* the former returns the number of callbacks registered at the time of the call
* and the second the number of callback to which the broadcast will be delivered.
* <p>
* This function is useful to decide whether to schedule a broadcast if this
* requires doing some work which otherwise would not be performed.
* </p>
*
* @return The size.
*/
public int getRegisteredCallbackCount() {
synchronized (mCallbacks) {
if (mKilled) {
return 0;
}
return mCallbacks.size();
}
}
}
RemoteCallbackList帮助我们维护了一个Map,并且处理了远端进程死亡后的事件, 从Map中移除了对应的callback,实现了对资源的清理.
Binder连接池
Binder连接池是为了解决客户端通过多个AIDL的方式实现IPC需要创建多个Service的问题.
可以参考这篇文章
本文完整代码
NewsEntity.aidl
package th.how.bean;
parcelable NewsEntity;
IMyAidlInterface.aidl
package th.how.bean;
import th.how.bean.NewsEntity;
import th.how.bean.IClientInterface;
interface IMyAidlInterface {
NewsEntity getData(int id, String title);
void register(IClientInterface callback);
void unRegister(IClientInterface callback);
}
IClientInterface.aidl
package th.how.bean;
interface IClientInterface {
void callbackClient();
}
RemoteService.java
package th.ipc.aidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import th.how.bean.IClientInterface;
import th.how.bean.IMyAidlInterface;
import th.how.bean.NewsEntity;
import th.how.utils.DpUtils;
/**
* Created by me_touch on 2017/8/16.
*
*/
public class RemoteService extends Service{
private RemoteCallbackList<IClientInterface> mCallback = new RemoteCallbackList<>();
@Override
public void onCreate() {
super.onCreate();
}
private final IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
@Override
public NewsEntity getData(int id, String title) throws RemoteException {
System.out.println("Server: Thread.currentThread().getName() = " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
int[] value = {10,20,1,5};
int[] space = {6, 3, 12, 1};
int value1 = DpUtils.completePack(4, space, value, 11);
System.out.println("value1 = " + value1);
callbackClient();
}catch (Exception e){
e.printStackTrace();
}
return new NewsEntity(id, title);
}
@Override
public void register(IClientInterface callback) throws RemoteException {
mCallback.register(callback);
}
@Override
public void unRegister(IClientInterface callback) throws RemoteException {
mCallback.unregister(callback);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
}
private void callbackClient(){
int num = mCallback.beginBroadcast();
for(int i = 0; i < num; i ++){
IClientInterface iClientInterface = mCallback.getBroadcastItem(i);
if(iClientInterface != null){
try{
iClientInterface.callbackClient();
}catch(Exception e){
e.printStackTrace();
}
}
}
mCallback.finishBroadcast();
}
}
AidlClientActivity
package th.ipc.aidl;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import th.how.app.R;
import th.how.bean.IClientInterface;
import th.how.bean.IMyAidlInterface;
import th.how.bean.NewsEntity;
public class AidlClientActivity extends AppCompatActivity {
private IMyAidlInterface iService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl_client);
Intent intent = new Intent(this, RemoteService.class);
intent.setAction(RemoteService.class.getName());
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
try {
iService.asBinder().unlinkToDeath(deathRecipient, 0);
}catch (Exception e){
e.printStackTrace();
}finally {
iService = null;
}
}
};
private IClientInterface client = new IClientInterface.Stub() {
@Override
public void callbackClient() throws RemoteException {
System.out.println("Client: Thread.currentThread().getName() = " + Thread.currentThread().getName());
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iService = IMyAidlInterface.Stub.asInterface(service);
System.out.println("mConnection: Thread.currentThread().getName() = " + Thread.currentThread().getName());
try {
service.linkToDeath(deathRecipient, 0);
iService.register(client);
new Thread(new Runnable() {
@Override
public void run() {
try {
NewsEntity entity = iService.getData(1, "Hello Activity");
System.out.println(entity.toString());
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}