Android 接口定义语言 (AIDL) 与您可能使用过的其他接口语言 (IDL) 类似。您可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。
这里先创建Server进程,包名为com.enjoy.myplugin;创建Client进程,报名为com.enjoy.plugin;
Server进程目录结构如下;
Client进程的目录结构如下:
简单做个说明,IAidlCbListener.aidl是用于注册回掉的接口,IMyAidlInterface.aidl是Server进程提供的对外开放的接口,也就是用于Client可以调用的接口;由于AIDL只能传递Java基本类型和list,map等几个类型,而Person.aidl是为了传递对象而声明的自定义类型。
需要强调的是,自定义的类型和所有的AIDL文件都需要在Server进程和Client进程中保持同步,也就是包名和内容要保证一致。
// IAidlCbListener.aidl
package com.enjoy.myplugin;
// Declare any non-default types here with import statements
interface IAidlCbListener {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void showServerMsg(String msg);
}
// IMyAidlInterface.aidl
package com.enjoy.myplugin;
import com.enjoy.myplugin.IAidlCbListener;
import com.enjoy.myplugin.Person;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* 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);
String getData();
void sendData(String data);
void addPerson(in Person person);
void notifyClient(String notifyContent);
void registerCb(IAidlCbListener icbi);
void unRegisterCb(IAidlCbListener icbi);
}
// Person.aidl
package com.enjoy.myplugin;
// Declare any non-default types here with import statements
parcelable Person;
自定义的传递数据类型Person.java
package com.enjoy.myplugin;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public class Person implements Parcelable {
public int age;
public String addr;
public Person(){}
public Person(int age, String addr){
this.age = age;
this.addr = addr;
}
protected Person(Parcel in) {
this.age = in.readInt();
this.addr = in.readString();
}
public static final Creator CREATOR = new Creator() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(age);
dest.writeString(addr);
}
@NonNull
@Override
public String toString() {
return "Person: age = "+age+",addr = "+addr;
}
}
自定义的类型需要将其序列化,才能在进程之间传递。
说明:
上述要用到的AIDL和java文件,都是Server和Client之前交互需要用到的,故在Server和Client进程中,这些文件要同时存在,并且要保证包名和内容一致
AidlServerService.java
package com.enjoy.myplugin;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import com.enjoy.myplugin.Person;
public class AidlServerService extends Service {
private static final String TAG = "Rayman AidlServerService";
private RemoteCallbackList mCbs = new RemoteCallbackList<>();
private com.enjoy.myplugin.Person newPerson = new com.enjoy.myplugin.Person();
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
class MyBinder extends IMyAidlInterface.Stub{
public String mData;
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
Log.d(TAG, "basicTypes: anInt = "+anInt);
}
@Override
public String getData() throws RemoteException {
notifyClient("invoke notifyClient...");
return mData;
}
@Override
public void sendData(String data) throws RemoteException {
mData = data;
}
@Override
public void addPerson(Person person) throws RemoteException {
newPerson = person;
}
@Override
public void notifyClient(String notifyContent) throws RemoteException {
mCbs.beginBroadcast();
//遍历所有注册的Listener,逐个调用它们的实现方法,也就是通知所有的注册者
for(int i=0;i
在IPC的过程中,可能会遇到以下情况需要考虑:
在AIDL中客户端向服务端注册一个回调方法时,服务端要考虑客户端是否意外退出(客户端因为错误应用Crash,或者被Kill掉了),服务端还不知道去回调客户端,出现错误
客户端和服务端进程状态
在进程间通信过程中,很可能出现一个进程死亡的情况。如果这时活着的一方不知道另一方已经死了就会出现问题。那我们如何在A进程中获取B进程的存活状态呢?
android肯定给我们提供了解决方式,那就是Binder的linkToDeath和unlinkToDeath方法,linkToDeath方法需要传入一个DeathRecipient对象,DeathRecipient类里面有个binderDied方法,当binder对象的所在进程死亡,binderDied方法就会被执行,我们就可以在binderDied方法里面做一些异常处理,释放资源等操作了
Android SDK提供一个封装好的对象:RemoteCallbackList,帮我自动处理了Link-To-Death的问题。
这里,简单介绍一下RemoteCallbackList:
public class RemoteCallbackList
extends Object
java.lang.Object ↳ android.os.RemoteCallbackList
负责维护远程接口列表的繁琐工作,通常用于执行从
Service
到其客户端的回调 。特别是:
- 跟踪一组已注册的
IInterface
回调,注意通过其基础唯一性IBinder
(通过调用)进行识别IInterface#asBinder
。- 将附加
IBinder.DeathRecipient
到每个已注册的接口,以便在其过程消失时可以将其从列表中清除。- 对接口的基础列表执行锁定以处理多线程传入的调用,并以线程安全的方式遍历该列表的快照而无需保持其锁定。
要使用此类,只需与服务一起创建一个实例,然后在客户端注册和取消注册服务时调用其
register(E)
和unregister(E)
方法。回调到注册客户端,使用beginBroadcast()
,getBroadcastItem(int)
和finishBroadcast()
。如果注册的回调过程消失了,该类将负责自动将其从列表中删除。如果要在这种情况下做其他工作,可以创建一个实现该
onCallbackDied(E)
方法的子类。
RemoteCallbackList帮我们避免了IPC两个进程在调用过程中发生意外crash,导致回调失败或者进程crash的问题。
在Client进程的MainActivity中实现如下代码:
package com.enjoy.plugin;
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.enjoy.myplugin.IAidlCbListener;
import com.enjoy.myplugin.IMyAidlInterface;
import com.enjoy.myplugin.Person;
public class MainActivity extends BaseActivity {
private static final String TAG = "Rayman plugin_MainActivity";
IMyAidlInterface myAidlInterface = null;
private MyCbListener myCbListener = new MyCbListener();
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if(myAidlInterface != null){
//Server端意外died
try {
myAidlInterface.unRegisterCb(myCbListener);
} catch (RemoteException e) {
e.printStackTrace();
}
myAidlInterface.asBinder().unlinkToDeath(mDeathRecipient,0);
myAidlInterface = null;
//TODO:bindService again
Intent intent = new Intent("android.intent.action.aidl");
intent.setComponent(new ComponentName("com.enjoy.myplugin","com.enjoy.myplugin.AidlServerService"));
bindService(intent,mConn, Context.BIND_AUTO_CREATE);
}
}
};
private ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
if(myAidlInterface != null){
try {
//设定死亡接收器,这个是针对IBinder对象的
service.linkToDeath(mDeathRecipient,0);
//调用Server进程通过AIDL提供的接口方法
myAidlInterface.sendData("testaidl...");
Log.d(TAG, "onServiceConnected: myCbListener = "+myCbListener);
if(myCbListener == null){
myCbListener = new MyCbListener();
}
//调用Server进程通过AIDL提供的接口方法
myAidlInterface.addPerson(new Person(32,"Rayman"));
//注册回调监听器
myAidlInterface.registerCb(myCbListener);
//在log中调用getData方法
Log.d(TAG, "onServiceConnected: data = "+myAidlInterface.getData());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
myAidlInterface = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("Rayman", "onCreate: this is plugin Activity...");
Intent intent = new Intent("android.intent.action.aidl");
intent.setComponent(new ComponentName("com.enjoy.myplugin","com.enjoy.myplugin.AidlServerService"));
bindService(intent,mConn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
if(myAidlInterface != null && myAidlInterface.asBinder().isBinderAlive()){
myAidlInterface.unRegisterCb(myCbListener);
}
} catch (RemoteException e) {
e.printStackTrace();
}
unbindService(mConn);
}
//实现回调监听器
class MyCbListener extends IAidlCbListener.Stub{
@Override
public void showServerMsg(String msg) throws RemoteException {
Log.d(TAG, "showServerMsg: "+msg);
}
}
}
在Client进程的MainActivity中绑定Service并调用Server进程的通过AIDL提供的接口方法。
这里需要说明的是在MainActivity中,通过DeathRecipient对象,并调用IBinder的linkToDeath和unLinkToDeath方法,实现了万一Server进程Died之后,对Service进行重新注册。
简单介绍一下linkToDeath和unlinkToDeath:
/** * Interface for receiving a callback when the process hosting an IBinder * has gone away. * * @see #linkToDeath */ public interface DeathRecipient { public void binderDied(); } /** * 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. * *
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(@NonNull DeathRecipient recipient, int flags) throws RemoteException; /** * Remove a previously registered death notification. * The recipient will no longer be called if this object * dies. * * @return {@code true} if the recipient 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 * recipient has not been registered with the IBinder, and * the IBinder is still alive. Note that if the recipient * was never registered, but the IBinder has already died, then this * exception will not be thrown, and you will receive a false * return value instead. */ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);
使用它比较简单,只需要实现DeathRecipient的bindDied方法。
linkToDeath和unLinkToDeath是成对出现的,可以参照上面Client中MainActivity的实现,在ServiceConnection中onServiceConnected的时候link,在bindDied方法中unlink。linkToDeath是为IBinder对象设置死亡代理,unLinkToDeath是解除之前设置的死亡代理,并可以在此时做重新绑定的动作。
把Server进程和Client继承都编译出来,并安装到同一部手机或者虚拟机,先启动Server进程,然后再启动Client进程,看到如下结果:
请注意,这个log内容只是Client进程的,Server进程的log没有再截图中。从log可以看出,在client进程中调用的Server进程提供的AIDL接口方法,都成功调用了,并且Server端回调Client进程设置的监听器也执行成功,说明当前代码OK。
OK,结束