Android 关于AIDL通信,RemoteCallbackList实现Server回调Client
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文件内容:
- 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); }
- 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); }
- 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。
总结:
- 注意理解Binder机制
- AIDL只提供了有限的传输数据类型,自定义的传输类型需要序列化
- RemoteCallbackList很好的解决了IPC过程中可能出现的某一方进程Crash,引起另一方Exception的问题
- DeathRecipient理解和调用(解决Server端进程意外终止,Client端获得的Binder对象丢失问题)
OK,结束
Android 关于AIDL通信,RemoteCallbackList实现Server回调Client相关推荐
- Android之使用AIDL时的跨进程回调—Server回调Client
首先建立在server端建立两个aidl文件 ITaskCallback.aidl 用于存放要回调client端的方法 package com.cmcc.demo.server; interface ...
- android aidl通信,Android的AIDL通信机制
Android 接口定义语言 (AIDL) AIDL(Android 接口定义语言)与您可能使用过的其他 IDL 类似. 您可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的 ...
- 【spring-cloud】Eureka server和client之间的心跳通信
为什么80%的码农都做不了架构师?>>> 启动两个Eureka Client,过了一会,停了其中一个,访问注册中心时,界面上显示了红色粗体警告信息: EMERGENCY! EU ...
- Android进阶——AIDL详解之使用远程服务AIDL实现进程间带远程回调接口和自定义Bean的较复杂通信小结(二)
文章大纲 引言 一.远程回调AIDL接口的应用 1.封装基本的父类和一些工具类 2. 创建服务端的AIDL 2.1.定义回调AIDL接口 2.2.定义业务AIDL接口 3.实现服务端对应AIDL的带有 ...
- Android跨进程通信Binder机制与AIDL实例
文章目录 进程通信 1.1 进程空间划分 1.2 跨进程通信IPC 1.3 Linux跨进程通信 1.4 Android进程通信 Binder跨进程通信 2.1 Binder简介 2.2 Binder ...
- 【朝花夕拾】Android跨进程通信总结篇
前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...
- 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇
前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10256379.html],谢谢! 只要是面试高级工程师岗位,Android跨进程通信就是最受面 ...
- 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇...
前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...
- Android—Binder+AIDL
Binder Binder机制优点: 只需要进行一次数据拷贝,性能上仅次于共享内存. 基于C/S架构,职责明确,架构清晰,稳定性较好. 安全性好,为每个App分配UID,UID是鉴别进程身份的标志. ...
最新文章
- 平台数据库导入导出快捷工具说明
- 如何使用Cisco命令阻止访问特定网站
- Python读取保存在hdf5文件中的脑电数据
- java vert.x_使用Vert.x将JavaScript引入Java企业
- linux按照更改时间查看文件,Linux查看特定时间段内修改过的文件
- JavaScript中获取数组元素索引号方法
- DTD与XML的关系。。说的不错,拿来看看,学习了
- scala学习(一)
- Swift - 使用导航条和导航条控制器来进行页面切换并传递数据
- python 连接数据库 慢_python自动结束mysql慢查询会话的实例代码
- 2019年7月28日解决战网BLZBNTBNA00000005BLZBNTBNA00000006BLZBNTBTS0000005DBLZBNTBTS0000004A 007D0 008A4 00840
- 代码对比工具,我就用这6个
- 使用python将文字转为语音
- 根据列表内车牌号,统计各省市车牌占有量
- 360免费wifi设置位置服务器,win10系统使用360免费wifi的操作方法
- android 9.0 默认打开开发者选项显示
- Python生成带圆角图片的二维码
- sja1c语言,A1SJ71AP21-S3基础知识三菱A1SJ71AP21-S3用户手册(硬件) - 广州正凌
- 新闻周刊文字内容_新闻周刊解说词
- 2022-2028全球与中国X射线平板探测器市场现状及未来发展趋势