Android 接口定义语言 (AIDL)

Android 接口定义语言 (AIDL) 与您可能使用过的其他接口语言 (IDL) 类似。您可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。

AIDL文件创建和使用

  • 先创建两个Android项目,分别为Server进程和Client进程

这里先创建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进程中保持同步,也就是包名和内容要保证一致。

  • 接下来看AIDL文件内容:
  1. IAidlCbListener.aidl

    // IAidlCbListener.aidl
    package com.enjoy.myplugin;// Declare any non-default types here with import statementsinterface IAidlCbListener {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/void showServerMsg(String msg);
    }
  2. IMyAidlInterface.aidl
    // IMyAidlInterface.aidl
    package com.enjoy.myplugin;import com.enjoy.myplugin.IAidlCbListener;
    import com.enjoy.myplugin.Person;
    // Declare any non-default types here with import statementsinterface 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);
    }
    
  3. Person.aidl
    // Person.aidl
    package com.enjoy.myplugin;// Declare any non-default types here with import statementsparcelable 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<Person> CREATOR = new Creator<Person>() {@Overridepublic Person createFromParcel(Parcel in) {return new Person(in);}@Overridepublic Person[] newArray(int size) {return new Person[size];}};@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeInt(age);dest.writeString(addr);}@NonNull@Overridepublic String toString() {return "Person: age = "+age+",addr = "+addr;}
}

自定义的类型需要将其序列化,才能在进程之间传递。

说明:

上述要用到的AIDL和java文件,都是Server和Client之前交互需要用到的,故在Server和Client进程中,这些文件要同时存在,并且要保证包名和内容一致

Server进程中创建AIDL对应的Service,并实现AIDL中对外提供的接口方法

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<IAidlCbListener> mCbs = new RemoteCallbackList<>();private com.enjoy.myplugin.Person newPerson = new com.enjoy.myplugin.Person();@Overridepublic IBinder onBind(Intent intent) {return new MyBinder();}class MyBinder extends IMyAidlInterface.Stub{public String mData;@Overridepublic void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {Log.d(TAG, "basicTypes: anInt = "+anInt);}@Overridepublic String getData() throws RemoteException {notifyClient("invoke notifyClient...");return mData;}@Overridepublic void sendData(String data) throws RemoteException {mData = data;}@Overridepublic void addPerson(Person person) throws RemoteException {newPerson = person;}@Overridepublic void notifyClient(String notifyContent) throws RemoteException {mCbs.beginBroadcast();//遍历所有注册的Listener,逐个调用它们的实现方法,也就是通知所有的注册者for(int i=0;i<mCbs.getRegisteredCallbackCount();i++){IAidlCbListener cb = mCbs.getBroadcastItem(i);cb.showServerMsg(""+i+"msg from Server is:"+notifyContent+"-"+newPerson.toString());}mCbs.finishBroadcast();}@Overridepublic void registerCb(IAidlCbListener icbi) throws RemoteException {Log.d(TAG, "registerCb: icbi = "+icbi+",mcbs = "+mCbs);mCbs.register(icbi);}@Overridepublic void unRegisterCb(IAidlCbListener icbi) throws RemoteException {mCbs.unregister(icbi);}}
}

在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<E extends android.os.IInterface>

负责维护远程接口列表的繁琐工作,通常用于执行从Service到其客户端的回调 。特别是:

  • 跟踪一组已注册的IInterface回调,注意通过其基础唯一性IBinder (通过调用)进行识别IInterface#asBinder
  • 将附加IBinder.DeathRecipient到每个已注册的接口,以便在其过程消失时可以将其从列表中清除。
  • 对接口的基础列表执行锁定以处理多线程传入的调用,并以线程安全的方式遍历该列表的快照而无需保持其锁定。

要使用此类,只需与服务一起创建一个实例,然后在客户端注册和取消注册服务时调用其register(E)unregister(E)方法。回调到注册客户端,使用beginBroadcast(), getBroadcastItem(int)finishBroadcast()

如果注册的回调过程消失了,该类将负责自动将其从列表中删除。如果要在这种情况下做其他工作,可以创建一个实现该onCallbackDied(E)方法的子类。

RemoteCallbackList帮我们避免了IPC两个进程在调用过程中发生意外crash,导致回调失败或者进程crash的问题。

Client进程实现对Server进程AIDL对应Service的绑定,调用其提供的接口

在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() {@Overridepublic void binderDied() {if(myAidlInterface != null){//Server端意外diedtry {myAidlInterface.unRegisterCb(myCbListener);} catch (RemoteException e) {e.printStackTrace();}myAidlInterface.asBinder().unlinkToDeath(mDeathRecipient,0);myAidlInterface = null;//TODO:bindService againIntent 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() {@Overridepublic 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();}}}@Overridepublic void onServiceDisconnected(ComponentName name) {myAidlInterface = null;}};@Overrideprotected 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);}@Overrideprotected 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{@Overridepublic 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.* * <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(@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 <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(@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。

总结:

  1. 注意理解Binder机制
  2. AIDL只提供了有限的传输数据类型,自定义的传输类型需要序列化
  3. RemoteCallbackList很好的解决了IPC过程中可能出现的某一方进程Crash,引起另一方Exception的问题
  4. DeathRecipient理解和调用(解决Server端进程意外终止,Client端获得的Binder对象丢失问题)

OK,结束

Android 关于AIDL通信,RemoteCallbackList实现Server回调Client相关推荐

  1. Android之使用AIDL时的跨进程回调—Server回调Client

    首先建立在server端建立两个aidl文件 ITaskCallback.aidl 用于存放要回调client端的方法 package com.cmcc.demo.server; interface ...

  2. android aidl通信,Android的AIDL通信机制

    Android 接口定义语言 (AIDL) AIDL(Android 接口定义语言)与您可能使用过的其他 IDL 类似. 您可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的 ...

  3. 【spring-cloud】Eureka server和client之间的心跳通信

    为什么80%的码农都做不了架构师?>>>    启动两个Eureka Client,过了一会,停了其中一个,访问注册中心时,界面上显示了红色粗体警告信息: EMERGENCY! EU ...

  4. Android进阶——AIDL详解之使用远程服务AIDL实现进程间带远程回调接口和自定义Bean的较复杂通信小结(二)

    文章大纲 引言 一.远程回调AIDL接口的应用 1.封装基本的父类和一些工具类 2. 创建服务端的AIDL 2.1.定义回调AIDL接口 2.2.定义业务AIDL接口 3.实现服务端对应AIDL的带有 ...

  5. Android跨进程通信Binder机制与AIDL实例

    文章目录 进程通信 1.1 进程空间划分 1.2 跨进程通信IPC 1.3 Linux跨进程通信 1.4 Android进程通信 Binder跨进程通信 2.1 Binder简介 2.2 Binder ...

  6. 【朝花夕拾】Android跨进程通信总结篇

    前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...

  7. 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10256379.html],谢谢! 只要是面试高级工程师岗位,Android跨进程通信就是最受面 ...

  8. 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇...

    前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...

  9. Android—Binder+AIDL

    Binder Binder机制优点: 只需要进行一次数据拷贝,性能上仅次于共享内存. 基于C/S架构,职责明确,架构清晰,稳定性较好. 安全性好,为每个App分配UID,UID是鉴别进程身份的标志. ...

最新文章

  1. 平台数据库导入导出快捷工具说明
  2. 如何使用Cisco命令阻止访问特定网站
  3. Python读取保存在hdf5文件中的脑电数据
  4. java vert.x_使用Vert.x将JavaScript引入Java企业
  5. linux按照更改时间查看文件,Linux查看特定时间段内修改过的文件
  6. JavaScript中获取数组元素索引号方法
  7. DTD与XML的关系。。说的不错,拿来看看,学习了
  8. scala学习(一)
  9. Swift - 使用导航条和导航条控制器来进行页面切换并传递数据
  10. python 连接数据库 慢_python自动结束mysql慢查询会话的实例代码
  11. 2019年7月28日解决战网BLZBNTBNA00000005BLZBNTBNA00000006BLZBNTBTS0000005DBLZBNTBTS0000004A 007D0 008A4 00840
  12. 代码对比工具,我就用这6个
  13. 使用python将文字转为语音
  14. 根据列表内车牌号,统计各省市车牌占有量
  15. 360免费wifi设置位置服务器,win10系统使用360免费wifi的操作方法
  16. android 9.0 默认打开开发者选项显示
  17. Python生成带圆角图片的二维码
  18. sja1c语言,A1SJ71AP21-S3基础知识三菱A1SJ71AP21-S3用户手册(硬件) - 广州正凌
  19. 新闻周刊文字内容_新闻周刊解说词
  20. 2022-2028全球与中国X射线平板探测器市场现状及未来发展趋势

热门文章

  1. 有没有免费的 BI 软件
  2. 通过路由器访问光猫(openwrt)
  3. 保护你的眼睛——设置电脑屏幕颜色和ClearType字体
  4. 畅购商城-添加订单实现(一)
  5. 插件小王子的插件源码汇总
  6. ASEMI代理AD8603AUJZ-REEL7原装ADI车规级AD8603AUJZ-REEL7
  7. 《蒋勋说宋词》 读后感
  8. python爬取天极网手机信息代码
  9. 大佬总结的4条宝贵经验,送给初入职场的你,从此一飞冲天
  10. Linux常用命令——jwhois命令