Android进程间通信 深入浅出AIDL(一)

官方推荐我们用Messenger来进行跨进程通信,但是Messenger是以串行的方式来处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了.这种情况就得用AIDL了.其实Messenger的底层也是AIDL,只不过系统做了层封装,简化使用.

2. AIDL使用


2.1 大致流程

  1. 创建 .aidl 文件:此文件定义带有方法签名的编程接口.

  2. 实现接口: Android SDK 工具会基于你的 .aidl 文件,使用 Java 编程语言生成接口.此接口拥有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法.你必须扩展 Stub 类并实现这些方法.

  3. 向客户端公开接口: 实现 Service 并重写 onBind(),从而返回 Stub 类的实现.

2.2 案例

2.2.1 定义aidl接口

首先是定义好客户端与服务端通信的AIDL接口,在里面声明方法用于客户端调用,服务端实现.

src/main下面创建aidl目录,然后新建IPersonManager.aidl文件

package com.xfhy.allinone.ipc.aidl;

import com.xfhy.allinone.ipc.aidl.Person;

interface IPersonManager {

List getPersonList();

//in: 从客户端流向服务端

boolean addPerson(in Person person);

}

这个接口和平常我们定义接口时差别不是很大,需要注意的是即使Person和IPersonManager在同一个包下面,还是得导包,这是AIDL的规则.

AIDL支持的数据类型:

而且在AIDL文件中,不是所有数据类型都是可以使用的,支持的数据类型如下:

  • Java 编程语言中的所有原语类型(如 int、long、char、boolean 等)

  • String和CharSequence;

  • List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;

  • Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;

  • Parcelable:所有实现了Parcelable接口的对象;

  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用.

注意:

  • 当需要传递对象时,则对象必须实现Parcelable接口

  • 所有非原语参数均需要指示数据走向的方向标记.这类标记可以是 in、out 或 inout.

  • in : 客户端流向服务端

  • out : 服务端流向客户端

  • inout : 双向流通

  • 原语默认是in,这里应该考虑一下是用什么原语标记,因为如果是inout的话开销其实蛮大的.

定义传输的对象:

在kotlin这边需要定义好这个需要传输的对象Person

class Person(var name: String? = “”) : Parcelable {

constructor(parcel: Parcel) : this(parcel.readString())

override fun toString(): String {

return “Person(name=$name) hashcode = ${hashCode()}”

}

override fun writeToParcel(parcel: Parcel, flags: Int) {

parcel.writeString(name)

}

fun readFromParcel(parcel: Parcel) {

this.name = parcel.readString()

}

override fun describeContents(): Int {

return 0

}

companion object CREATOR : Parcelable.Creator {

override fun createFromParcel(parcel: Parcel): Person {

return Person(parcel)

}

override fun newArray(size: Int): Array<Person?> {

return arrayOfNulls(size)

}

}

然后得在aidl的相同目录下也需要声明一下这个Person对象.新建一个Person.aidl

package com.xfhy.allinone.ipc.aidl;

parcelable Person;

都完成了之后,rebuild一下,AS会自动生成如下代码IPersonManager.java:

小插曲: 不能在aidl里面使用中文注释,否则可能会出现无法自动生成java代码的问题.奇怪的是我在macOS上面能自动生成,Windows上就不行.

package com.xfhy.allinone.ipc.aidl;

public interface IPersonManager extends android.os.IInterface {

/**

  • Default implementation for IPersonManager.

*/

public static class Default implements com.xfhy.allinone.ipc.aidl.IPersonManager {

@Override

public java.util.List<com.xfhy.allinone.ipc.aidl.Person> getPersonList() throws android.os.RemoteException {

return null;

}

@Override

public boolean addPerson(com.xfhy.allinone.ipc.aidl.Person person) throws android.os.RemoteException {

return false;

}

@Override

public android.os.IBinder asBinder() {

return null;

}

}

/**

  • Local-side IPC implementation stub class.

*/

public static abstract class Stub extends android.os.Binder implements com.xfhy.allinone.ipc.aidl.IPersonManager {

private static final java.lang.String DESCRIPTOR = “com.xfhy.allinone.ipc.aidl.IPersonManager”;

/**

  • Construct the stub at attach it to the interface.

*/

public Stub() {

this.attachInterface(this, DESCRIPTOR);

}

/**

  • Cast an IBinder object into an com.xfhy.allinone.ipc.aidl.IPersonManager interface,

  • generating a proxy if needed.

*/

public static com.xfhy.allinone.ipc.aidl.IPersonManager asInterface(android.os.IBinder obj) {

if ((obj == null)) {

return null;

}

android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

if (((iin != null) && (iin instanceof com.xfhy.allinone.ipc.aidl.IPersonManager))) {

return ((com.xfhy.allinone.ipc.aidl.IPersonManager) iin);

}

return new com.xfhy.allinone.ipc.aidl.IPersonManager.Stub.Proxy(obj);

}

@Override

public android.os.IBinder asBinder() {

return this;

}

@Override

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {

java.lang.String descriptor = DESCRIPTOR;

switch (code) {

case INTERFACE_TRANSACTION: {

reply.writeString(descriptor);

return true;

}

case TRANSACTION_getPersonList: {

data.enforceInterface(descriptor);

java.util.List<com.xfhy.allinone.ipc.aidl.Person> _result = this.getPersonList();

reply.writeNoException();

reply.writeTypedList(_result);

return true;

}

case TRANSACTION_addPerson: {

data.enforceInterface(descriptor);

com.xfhy.allinone.ipc.aidl.Person _arg0;

if ((0 != data.readInt())) {

_arg0 = com.xfhy.allinone.ipc.aidl.Person.CREATOR.createFromParcel(data);

} else {

_arg0 = null;

}

boolean _result = this.addPerson(_arg0);

reply.writeNoException();

reply.writeInt(((_result) ? (1) : (0)));

return true;

}

default: {

return super.onTransact(code, data, reply, flags);

}

}

}

private static class Proxy implements com.xfhy.allinone.ipc.aidl.IPersonManager {

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;

}

@Override

public java.util.List<com.xfhy.allinone.ipc.aidl.Person> getPersonList() throws android.os.RemoteException {

android.os.Parcel _data = android.os.Parcel.obtain();

android.os.Parcel _reply = android.os.Parcel.obtain();

java.util.List<com.xfhy.allinone.ipc.aidl.Person> _result;

try {

_data.writeInterfaceToken(DESCRIPTOR);

boolean _status = mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);

if (!_status && getDefaultImpl() != null) {

return getDefaultImpl().getPersonList();

}

_reply.readException();

_result = _reply.createTypedArrayList(com.xfhy.allinone.ipc.aidl.Person.CREATOR);

} finally {

_reply.recycle();

_data.recycle();

}

return _result;

}

@Override

public boolean addPerson(com.xfhy.allinone.ipc.aidl.Person person) throws android.os.RemoteException {

android.os.Parcel _data = android.os.Parcel.obtain();

android.os.Parcel _reply = android.os.Parcel.obtain();

boolean _result;

try {

_data.writeInterfaceToken(DESCRIPTOR);

if ((person != null)) {

_data.writeInt(1);

person.writeToParcel(_data, 0);

} else {

_data.writeInt(0);

}

boolean _status = mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);

if (!_status && getDefaultImpl() != null) {

return getDefaultImpl().addPerson(person);

}

_reply.readException();

_result = (0 != _reply.readInt());

} finally {

_reply.recycle();

_data.recycle();

}

return _result;

}

public static com.xfhy.allinone.ipc.aidl.IPersonManager sDefaultImpl;

}

static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

public static boolean setDefaultImpl(com.xfhy.allinone.ipc.aidl.IPersonManager impl) {

// Only one user of this interface can use this function

// at a time. This is a heuristic to detect if two different

// users in the same process use this function.

if (Stub.Proxy.sDefaultImpl != null) {

throw new IllegalStateException(“setDefaultImpl() called twice”);

}

if (impl != null) {

Stub.Proxy.sDefaultImpl = impl;

return true;

}

return false;

}

public static com.xfhy.allinone.ipc.aidl.IPersonManager getDefaultImpl() {

return Stub.Proxy.sDefaultImpl;

}

}

public java.util.List<com.xfhy.allinone.ipc.aidl.Person> getPersonList() throws android.os.RemoteException;

public boolean addPerson(com.xfhy.allinone.ipc.aidl.Person person) throws android.os.RemoteException;

}

我感觉AIDL的功能其实就在于此,写了AIDL文件之后,AS会自动帮我们生成一些代码,用于与Server通信.其实这些代码完全可以我们自己写,就是稍微麻烦些.有工具咱就用工具.

简单看一下这个文件,IPersonManager是继承了一个android.os.IInterface的interface,然后有一个Default类用于默认实现IPersonManager.然后一个抽象类Stub继承了android.os.Binder且实现了IPersonManager接口,这相当于扩展了Binder.因为它是继承了Binder,那它肯定是用来做IPC通信用的.

  • asInterface 方法用于将服务端的Binder对象转换为客户端所需要的接口对象,该过程区分进程,如果进程一样,就返回服务端Stub对象本身,否则呢就返回封装后的Stub.Proxy对象

  • onTransact 方法是运行在服务端的Binder线程中的,当客户端发起远程请求后,在底层封装后会交由此方法来处理.通过code来区分客户端请求的方法,注意一点的是,如果该方法返回false的话,客户端的请求就会失败.一般可以用来做权限控制

Proxy中的方法是运行在客户端的,当客户端发起远程请求时,_data会写入参数,然后调用transact方法发起RPC(远程过程调用)请求,同时挂起当前线程,然后服务端的onTransact方法就会被 调起,直到RPC过程返回后,当前线程继续执行,并从_reply取出返回值(如果有的话),并返回结果

2.2.2 服务端实现接口

定义一个Service,然后将其process设置成一个新的进程,与主进程区分开,模拟跨进程访问.它里面需要实现.aidl生成的接口

class RemoteService : Service() {

private val mPersonList = mutableListOf<Person?>()

private val mBinder: Binder = object : IPersonManager.Stub() {

override fun getPersonList(): MutableList<Person?> = mPersonList

override fun addPerson(person: Person?): Boolean {

return mPersonList.add(person)

}

}

override fun onBind(intent: Intent?): IBinder? {

return mBinder

}

override fun onCreate() {

super.onCreate()

mPersonList.add(Person(“Garen”))

mPersonList.add(Person(“Darius”))

}

}

实现的IPersonManager.Stub是一个Binder,需要通过onBind()返回,客户端需要通过这个Binder来跨进程调用Service这边的服务.

2.2.3 客户端与服务端进行通信

客户端这边需要通过bindService()来连接此Service,进而实现通信.客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法所返回的 binder 实例.当客户端在 onServiceConnected() 回调中收到 IBinder 时,它必须调用 YourServiceInterface.Stub.asInterface(service),以将返回的参数转换成 YourServiceInterface 类型.

因为是模仿的跨进程,咱就模仿得彻底一点,模仿跨app的情况.假设客户端那边是不能直接拿到Service的引用,咱需要定义一个action,方便bindService()

<service

android:name=“.ipc.aidl.RemoteService”

android:enabled=“true”

android:exported=“true”

android:process=“:other”>

service定义好之后,再来通信

class AidlActivity : TitleBarActivity() {

companion object {

const val TAG = “xfhy_aidl”

}

private var remoteServer: IPersonManager? = null

private val serviceConnection = object : ServiceConnection {

override fun onServiceConnected(name: ComponentName?, service: IBinder?) {

log(TAG, “onServiceConnected”)

//在onServiceConnected调用IPersonManager.Stub.asInterface获取接口类型的实例

//通过这个实例调用服务端的服务

remoteServer = IPersonManager.Stub.asInterface(service)

}

override fun onServiceDisconnected(name: ComponentName?) {

log(TAG, “onServiceDisconnected”)

}

}

override fun getThisTitle(): CharSequence {

return “AIDL”

}

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_aidl)

btnConnect.setOnClickListener {

connectService()

}

btnGetPerson.setOnClickListener {

getPerson()

}

btnAddPerson.setOnClickListener {

addPerson()

}

}

private fun connectService() {

val intent = Intent()

//action 和 package(app的包名)

intent.action = “com.xfhy.aidl.Server.Action”

intent.setPackage(“com.xfhy.allinone”)

val bindServiceResult = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)

log(TAG, “bindService $bindServiceResult”)

//如果targetSdk是30,那么需要处理Android 11中的程序包可见性 具体参见: https://developer.android.com/about/versions/11/privacy/package-visibility

}

private fun addPerson() {

//客户端调服务端方法时,需要捕获以下几个异常:

//RemoteException 异常:

//DeadObjectException 异常:连接中断时会抛出异常;

//SecurityException 异常:客户端和服务端中定义的 AIDL 发生冲突时会抛出异常;

try {

val addPersonResult = remoteServer?.addPerson(Person(“盖伦”))

log(TAG, “addPerson result = $addPersonResult”)

} catch (e: RemoteException) {

e.printStackTrace()

} catch (e: DeadObjectException) {

e.printStackTrace()

} catch (e: SecurityException) {

e.printStackTrace()

}

}

private fun getPerson() {

val personList = remoteServer?.personList

log(TAG, “person 列表 $personList”)

}

override fun onDestroy() {

super.onDestroy()

//最后记得unbindService

unbindService(serviceConnection)

}

}

客户端这边首先需要bindService(),然后通过ServiceConnection实例的onServiceConnected()回调拿到IBinder,将这个IBinder转成aidl里面定义的接口类型实例,通过该实例就能间距与服务端进行通信了.上面的demo中,我们调用了服务端的addPerson和getPerson方法,测试时我先get再add,再get,然后看输出日志

2020-12-24 12:41:00.170 24785-24785/com.xfhy.allinone D/xfhy_aidl: bindService true

2020-12-24 12:41:00.906 24785-24785/com.xfhy.allinone D/xfhy_aidl: onServiceConnected

2020-12-24 12:41:04.253 24785-24785/com.xfhy.allinone D/xfhy_aidl: person 列表 [Person(name=Garen), Person(name=Darius)]

2020-12-24 12:41:05.952 24785-24785/com.xfhy.allinone D/xfhy_aidl: addPerson result = true

2020-12-24 12:41:09.022 24785-24785/com.xfhy.allinone D/xfhy_aidl: person 列表 [Person(name=Garen), Person(name=Darius), Person(name=盖伦)]

可以看到,第2次get时,已经将之前添加的数据也取回来了,所以通信是OK的.

需要注意的是在客户端调用这些远程方法时是同步调用,在主线程调用可能会导致ANR,应该在子线程去调用.

调用的时候可能会出现下面几个异常,必须得捕获一下:

  • RemoteException 异常:

  • DeadObjectException 异常:连接中断时会抛出异常;

  • SecurityException 异常:客户端和服务端中定义的 AIDL 发生冲突时会抛出异常;

2.3 in,out,inout关键字

在上面定义AIDL接口的时候,咱用到了一个关键字in,这个关键是其实是定向tag,是用来指出数据流通的方式.还有2个tag是out和inout.所有的非基本参数都需要一个定向tag来指出数据的流向,基本参数的定向tag默认并且只能是in.

写个demo验证一下:

先修改aidl接口,把3种方式都安排上

interface IPersonManager {

void addPersonIn(in Person person);

void addPersonOut(out Person person);

void addPersonInout(inout Person person);

}

服务端实现:

override fun addPersonIn(person: Person?) {

log(TAG,“服务端 addPersonIn() person = $person”)

person?.name = “被addPersonIn修改”

}

override fun addPersonOut(person: Person?) {

log(TAG,“服务端 addPersonOut() person = $person}”)

person?.name = “被addPersonOut修改”

}

override fun addPersonInout(person: Person?) {

log(TAG,“服务端 addPersonInout() person = $person}”)

person?.name = “被addPersonInout修改”

}

客户端实现:

private fun addPersonIn() {

var person = Person(“寒冰”)

log(TAG, “客户端 addPersonIn() 调用之前 person = $person}”)

remoteServer?.addPersonIn(person)

log(TAG, “客户端 addPersonIn() 调用之后 person = $person}”)

}

private fun addPersonOut() {

var person = Person(“蛮王”)

log(TAG, “客户端 addPersonOut() 调用之前 person = $person}”)

remoteServer?.addPersonOut(person)

log(TAG, “客户端 addPersonOut() 调用之后 person = $person}”)

}

private fun addPersonInout() {

var person = Person(“艾克”)

log(TAG, “客户端 addPersonInout() 调用之前 person = $person}”)

remoteServer?.addPersonInout(person)

log(TAG, “客户端 addPersonInout() 调用之后 person = $person}”)

}

最后输出的日志如下:

//in 方式 服务端那边修改了,但是服务端这边不知道

客户端 addPersonIn() 调用之前 person = Person(name=寒冰) hashcode = 142695478}

服务端 addPersonIn() person = Person(name=寒冰) hashcode = 38642374

客户端 addPersonIn() 调用之后 person = Person(name=寒冰) hashcode = 142695478}

//out方式 客户端能感知服务端的修改,且客户端不能向服务端传数据

//可以看到服务端是没有拿到客户端的数据的!

客户端 addPersonOut() 调用之前 person = Person(name=蛮王) hashcode = 15787831}

服务端 addPersonOut() person = Person(name=) hashcode = 231395975}

客户端 addPersonOut() 调用之后 person = Person(name=被addPersonOut修改) hashcode = 15787831}

//inout方式 客户端能感知服务端的修改

客户端 addPersonInout() 调用之前 person = Person(name=艾克) hashcode = 143615140}

服务端 addPersonInout() person = Person(name=艾克) hashcode = 116061620}

客户端 addPersonInout() 调用之后 person = Person(name=被addPersonInout修改) hashcode = 143615140}

由上面的demo可以更容易理解数据流向的含义.而且我们还发现了以下规律:

  • in方式是可以从客户端向服务端传数据的,out则不行

  • out方式是可以从服务端向客户端传数据的,in则不行

  • 不管服务端是否有修改传过去的对象数据,客户端的对象引用是不会变的,变化的只是客户端的数据.合情合理,跨进程是序列化与反序列化的方式操作数据.

2.4 oneway 关键字

将aidl接口的方法前加上oneway关键字则这个方法是异步调用,不会阻塞调用线程.当客户端这边调用服务端的方法时,如果不需要知道其返回结果,这时使用异步调用可以提高客户端的执行效率.

验证: 我将aidl接口方法定义成oneway的,在服务端AIDL方法实现中加入Thread.sleep(2000)阻塞一下方法调用,然后客户端调用这个方法,查看方法调用的前后时间

private fun addPersonOneway() {

log(TAG, “oneway开始时间: ${System.currentTimeMillis()}”)

remoteServer?.addPersonOneway(Person(“oneway”))

log(TAG, “oneway结束时间: ${System.currentTimeMillis()}”)

}

//日志输出

//oneway开始时间: 1608858291371

//oneway结束时间: 1608858291372

可以看到,客户端调用这个方法时确实是没有被阻塞的.

2.5 线程安全

AIDL的方法是在服务端的Binder线程池中执行的,所以多个客户端同时进行连接且操作数据时可能存在多个线程同时访问的情形.这样的话,我们就需要在服务端AIDL方法中处理多线程同步问题.

先看下服务端的AIDL方法是在哪个线程中:

override fun addPerson(person: Person?): Boolean {

log(TAG, “服务端 addPerson() 当前线程 : ${Thread.currentThread().name}”)

return mPersonList.add(person)

}

//日志输出

服务端 addPerson() 当前线程 : Binder:3961_3

可以看到,确实是在非主线程中执行的.那确实会存在多线程安全问题,我们需要将mPersonList的类型修改为CopyOnWriteArrayList,以确保线程安全. 需要注意的是即使这里的数据类型是CopyOnWriteArrayList,但是在返回给客户端的时候,还是会被转化成ArrayList.能被转化成功的原因是它们都是实现了List接口,AIDL是支持List的.

验证一下,在客户端看看这个返回来的mPersonList类型是啥:

//服务端

private val mPersonList = CopyOnWriteArrayList<Person?>()

最后

针对于上面的问题,我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
Android进阶视频+面试资料部分截图

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
Millis()}")

}

//日志输出

//oneway开始时间: 1608858291371

//oneway结束时间: 1608858291372

可以看到,客户端调用这个方法时确实是没有被阻塞的.

2.5 线程安全

AIDL的方法是在服务端的Binder线程池中执行的,所以多个客户端同时进行连接且操作数据时可能存在多个线程同时访问的情形.这样的话,我们就需要在服务端AIDL方法中处理多线程同步问题.

先看下服务端的AIDL方法是在哪个线程中:

override fun addPerson(person: Person?): Boolean {

log(TAG, “服务端 addPerson() 当前线程 : ${Thread.currentThread().name}”)

return mPersonList.add(person)

}

//日志输出

服务端 addPerson() 当前线程 : Binder:3961_3

可以看到,确实是在非主线程中执行的.那确实会存在多线程安全问题,我们需要将mPersonList的类型修改为CopyOnWriteArrayList,以确保线程安全. 需要注意的是即使这里的数据类型是CopyOnWriteArrayList,但是在返回给客户端的时候,还是会被转化成ArrayList.能被转化成功的原因是它们都是实现了List接口,AIDL是支持List的.

验证一下,在客户端看看这个返回来的mPersonList类型是啥:

//服务端

private val mPersonList = CopyOnWriteArrayList<Person?>()

最后

针对于上面的问题,我总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。
[外链图片转存中…(img-nmlVBpXp-1714873872462)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 13
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值