Android AIDL 详细使用指南

5,524 阅读8分钟

AIDL 全称 Android Interface Definition Language ,安卓接口定义语言。

AIDL 用来解决 Android 的跨进程通信问题,底层原理是 Binder ,实现思路是 C / S 架构思想。

  • Server:接收请求,提供处理逻辑,并发送响应数据。
  • Client:发起请求,接收响应数据。

C / S 之间通过 Binder 对象进行通信。

  • Server 需要实现一个 Service 作为服务器,Client 侧则需要调用发起请求的能力。
  • Client 需要调用 bindService 绑定到远程服务,然后通过 ServiceConnection 来接收远程服务的 Binder 对象。拿到 Binder 对象后就可以调用远程服务中定义的方法了。

因为是跨进程通信,所以需要实现序列化,AIDL 专门为 Android 设计,所以它的序列化不能使用 Java 提供的 Serializable ,而是 Android 提供的 Parcelable 接口。

AIDL 的用法

以一个跨进程相互发送消息的 Demo 为例,演示 AIDL 的用法。

场景:两个 App ,一个作为 Server ,用来启动一个服务,接收另一个作为 Client 的 App 发来的请求(Request),然后并进行响应(Response),另外,Server 也可以主动发消息给绑定到 Server 的 Client 。

Step 1 定义通信协议

AIDL 解决的是远程通信问题,是一种 C / S 架构思想,参考网络通信模型,客户端和服务端之间,需要约定好彼此支持哪些东西,提供了什么能力给对方(这里主要是服务端提供给客户端的),而定义能力在面向对象的编程中,自然就是通过接口来定义。所以 AIDL 是 Interface Definition Language。

定义服务端暴露给客户端的能力,首先要创建 AIDL 文件。AIDL 文件在同包名下,与 java / res 等目录同级,创建 aidl 文件夹,其内部结构和 java 保持一致:

src
|- java
|-- com--- chunyu---- aidl----- service------ ...// java file
|
|- aidl
|-- com--- chunyu---- aidl----- service------ ... // aidl file
|
|- ...

在定义 AIDL 接口前,我们现实现一个数据类,这个数据类作为服务端和客户端之间通信的数据结构。如果你的通信不需要复杂的数据对象,而是 int 、 long 等基本数据类型和 String ,则不需要这一个步骤。

通过 Android Studio 右键创建 AIDL 文件时,默认会生成一个方法:

interface IServerManager {
    /**
     * 演示了一些可以在AIDL中用作参数和返回值的基本类型。
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

这里说明了除了要实现 Parcelable 序列化接口的对象,这些类型可以直接传递。

这里我们定义一个 Msg 类,作为通信传递的数据类型,在 java 目录下实现这个类:

class Msg(var msg: String, var time: Long? = null): Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readString() ?: "",
        parcel.readValue(Long::class.java.classLoader) as? Long
    ) {
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(msg)
        parcel.writeValue(time)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Creator<Msg> {
        override fun createFromParcel(parcel: Parcel): Msg {
            return Msg(parcel)
        }

        override fun newArray(size: Int): Array<Msg?> {
            return arrayOfNulls(size)
        }
    }
}

其实只需要定义参数即可,方法和成员通过快捷键会自动实现。实现完这个类后,需要在 aidl 同包名下创建一个 Msg.aidl 文件,将这个类型进行声明:

package com.chunyu.aidl.service;

parcelable Msg;

数据对象定义好了,下一步就可以定义服务端暴露给客户端的接口了。

首先定义一个服务端主动回调给客户端的接口,所有注册了的 Client ,都可以接收到服务端的主动消息:

package com.chunyu.aidl.service;

import com.chunyu.aidl.service.Msg;

interface IReceiveMsgListener {
    void onReceive(in Msg msg);
}

需要注意的一点是,AIDL 文件中的 import 并不会自动导入,需要开发者自行添加。

然后定义 Server 暴露给 Client 的能力:

package com.chunyu.aidl.service;

import com.chunyu.aidl.service.Msg;
import com.chunyu.aidl.service.IReceiveMsgListener;

interface IMsgManager {
  	// 发消息
    void sendMsg(in Msg msg);
  	// 客户端注册监听回调
    void registerReceiveListener(IReceiveMsgListener listener);
  	// 客户端取消监听回调
    void unregisterReceiveListener(IReceiveMsgListener listener);
}

IMsgManager 提供了三个方法,用来解决两个场景:

  • Client 主动发送 Msg 给 Server (sendMsg 方法,也可以理解为网络通信中的客户端发起请求)。
  • Server 主动发消息给所有订阅者,通过回调 IReceiveMsgListener 中的 onReceive 方法,每个注册的 Client 都会收到回调(典型的观察者模式)。

所有关于 AIDL 的部分就到这里了,此时,开发者需要手动运行 build 重新构建项目,这样 AIDL 会在 build 后生成一些 class 文件,供项目代码中调用。

注意:每次 AIDL 的改动都需要手动 build 一下。

Step 2 定义服务端

服务端的定义需要创建一个 Service 来作为服务器。这里创建一个 MyService 类,然后在 AndroidManifest.xml 中配置一个 action ,这个 action 后续会用来进行跨进程启动:

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.chunyu.aidl.service.MyService"></action>
            </intent-filter>
        </service>

配置完这个后,就可以在 MyService 中实现服务器的逻辑了。

Service 的启动方式有两种,startService 和 bindService ,后者是我们在实现 Client 连接 Server 的核心方法。通过 bindService 创建 C / S 之间的连接。而 Service 在通过 bindService 启动时,会回调 onBind 方法:

fun onBind(intent: Intent): IBinder 

onBind 的返回类型时 IBinder ,这个就是跨进程通信间传递的 Binder 。在同一个进程中不需要进行跨进程通信,这里可以返回为 null 。而此时需要实现 IPC ,自然这个方法需要返回一个存在的 Binder 对象,所以,配置服务器的第二步,就是实现一个 Binder 并在 onBind 中返回。

我们在定义通信协议时,定义了一个用来表示 Server 提供给 Client 能力的接口 IMsgManager,经过 build 后,编译器会自动生成 IMsgManager.Stub ,这是一个自动生成的实现了 IMsgManager 接口的 Binder 抽象实现:

public static abstract class Stub extends android.os.Binder implements com.chunyu.aidl.service.IMsgManager

在 MyService 中实现这个抽象类:

class MyService : Service() {

  	// ...
  
    inner class MyBinder: IMsgManager.Stub() {
        override fun sendMsg(msg: Msg?) {
            // todo 收到 Client 发来的消息,此处实现 Server 的处理逻辑
        }
        override fun sendMsg(msg: Msg?) {
            // todo 收到 Client 发来的消息,此处实现 Server 的处理逻辑
            val n = receiveListeners.beginBroadcast()
            for (i in 0 until n) {
                val listener = receiveListeners.getBroadcastItem(i)
                listener?.let {
                    try {
                        val serverMsg = Msg("服务器响应 ${Date(System.currentTimeMillis())}\n ${packageName}", System.currentTimeMillis())
                        listener.onReceive(serverMsg)
                    } catch (e: RemoteException) {
                        e.printStackTrace()
                    }
                }
            }
            receiveListeners.finishBroadcast()
        }

        override fun registerReceiveListener(listener: IReceiveMsgListener?) {
          	// receiveListeners 记录观察者
            receiveListeners.register(listener)
        }

        override fun unregisterReceiveListener(listener: IReceiveMsgListener?) {
            val success = receiveListeners.unregister(listener)
            if (success) {
                Log.d(TAG, "解除注册成功")
            } else {
                Log.d(TAG, "解除注册失败")
            }
        }
    }
}

然后,在 onBind 方法中返回这个类的对象:

    override fun onBind(intent: Intent): IBinder {
        return MyBinder()
    }

整体的一个服务端代码:

class MyService : Service() {

    private val receiveListeners =  RemoteCallbackList<IReceiveMsgListener>()

    override fun onBind(intent: Intent): IBinder {
        return MyBinder()
    }

    inner class MyBinder: IMsgManager.Stub() {
        override fun sendMsg(msg: Msg?) {
            // server process request at here
        }

        override fun registerReceiveListener(listener: IReceiveMsgListener?) {
            receiveListeners.register(listener)
        }

        override fun unregisterReceiveListener(listener: IReceiveMsgListener?) {
            val success = receiveListeners.unregister(listener)
            if (success) {
                Log.d(TAG, "解除注册成功")
            } else {
                Log.d(TAG, "解除注册失败")
            }
        }
    }
}

这样,服务端的逻辑就完成了。

Step 3 客户端实现

客户端的实现在另一个 App 中实现,创建一个新项目,包名 com.chunyu.aidl.client ,将这个项目中的 MainActivity 作为 Client 。

Client 中需要实现的核心逻辑包括:

  • 创建 Client 到 Server 的连接。
  • 实现发送请求的功能。
  • 实现接收 Server 消息的功能。
  • 在销毁的生命周期中主动关闭连接。

创建连接

创建连接主要通过 bindService 来实现,分为两种情况,在同一个进程中,不需要跨进程,直接通过显式的 Intent 启动 Service :

val intent = Intent(this, MyService::class.java)
bindService(intent, connection!!, BIND_AUTO_CREATE)

这是因为在同一个进程中,能直接访问到服务端 Service 的类。而跨进程没有这个 class ,需要通过隐式 Intent 启动 :

val intent = Intent()
intent.action = "com.chunyu.aidl.service.MyService"
intent.setPackage("com.chunyu.aidl.service")
bindService(intent, connection!!, BIND_AUTO_CREATE)

Action 在实现服务端时,在 AndroidManifest.xml 中进行配置,此刻就用到了。

而不管是哪个进程调用 bindService ,都会需要一个 connection 参数,这是一个 ServiceConnection 的对象。

ServiceConnection 是一个监控应用 Service 状态的接口。和很多系统的其他回调一样,这个接口的实现的方法在进程的主线程中调用。

public interface ServiceConnection {

    void onServiceConnected(ComponentName name, IBinder service);

    void onServiceDisconnected(ComponentName name);

    default void onBindingDied(ComponentName name) {
    }
    
    default void onNullBinding(ComponentName name) {
    }
}

了解了 ServiceConnection ,所以需要在 Client 中实现一个 ServiceConnection 对象,这里用匿名对象的形式:

private var deathRecipient = object : IBinder.DeathRecipient {
    override fun binderDied() {
        iMsgManager?.let {
          	// 当 binder 连接断开时,解除注册
            it.asBinder().unlinkToDeath(this, 0)
            iMsgManager = null
        }
    }
}

val connection = object : ServiceConnection {
  	// 服务连接创建成功时
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        iMsgManager = IMsgManager.Stub.asInterface(binder)
        try {
            iMsgManager?.asBinder()?.linkToDeath(deathRecipient, 0)
            iMsgManager?.registerReceiveListener(receiveMsgListener)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }
    override fun onServiceDisconnected(name: ComponentName?) { }
}

接收 Server 消息

这里注册的 listener是一个 IReceiveMsgListener.Stub 对象:

    private var receiveMsgListener = object : IReceiveMsgListener.Stub() {
        override fun onReceive(msg: Msg?) {
            displayTv.post {
                displayTv.text = "客户端:${msg?.msg}, time: ${msg?.time}"
            }
        }
    }

写到这里的时候,我是有一个疑问的,为什么是 IReceiveMsgListener.Stub 类型,而不是一个 IReceiveMsgListener 的匿名对象。

首先 IReceiveMsgListener.Stub 的继承关系是:

public static abstract class Stub extends android.os.Binder implements com.chunyu.aidl.service.IReceiveMsgListener

IReceiveMsgListener.Stub 本身也是 IReceiveMsgListener 的实现,而且它还继承自 Binder ,也就是具备了 Binder 的能力,能够在跨进程通信中作为 Binder 传输,所以这里是 IReceiveMsgListener.Stub 类型的对象。

而如果直接使用 IReceiveMsgListener , 匿名对象要求多实现一个 asBinder 方法:

    private var receiveMsgListener = object : IReceiveMsgListener {
        override fun asBinder(): IBinder {
            TODO("Not yet implemented")
        }

        override fun onReceive(msg: Msg?) {
            displayTv.post {
                displayTv.text = "客户端:${msg?.msg}, time: ${msg?.time}"
            }
            Log.d(TAG, "客户端:$msg")
        }
    }

其实 IReceiveMsgListener.Stub 就是帮我们实现好了一些逻辑,减少了开发的复杂度。

而从另一个方面也验证了 AIDL 跨进程通信调用的对象能需要具备作为 Binder 的能力。

Client 发送消息

val connection = object : ServiceConnection {
  	// 服务连接创建成功时
    override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
        iMsgManager = IMsgManager.Stub.asInterface(binder)
        try {
            iMsgManager?.asBinder()?.linkToDeath(deathRecipient, 0)
            iMsgManager?.registerReceiveListener(receiveMsgListener)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }
    override fun onServiceDisconnected(name: ComponentName?) { }
}

onServiceConnected 代表着连接创建成功了,此时首先赋值了 iMsgManager ,iMsgManager 是 AIDL 中定义的 Server 暴露给 Client 的能力的接口对象:

private var iMsgManager: IMsgManager?  = null

它的初始化:

iMsgManager = IMsgManager.Stub.asInterface(binder)

这里的 asInerface 和 asBinder 方法,说明 AIDL 中定义的接口可以转换为 Binder ,Binder 也可以转换为 Interface ,因为这里的 Binder 来自于 Service 的 onBind 方法,在 onBind 中返回的就是 IMsgManager.Stub 的实现,自然可以转换为 IMsgManager 。

通过 IMsgManager 就可以调用 Server 中的方法了:

        sendMsgBtn.setOnClickListener {
            iMsgManager?.sendMsg(Msg("from 客户端,当前第 ${count++} 次", System.currentTimeMillis()))
        }

随便给一个 button 的点击事件中调用 sendMsg 方法,发送消息给 MyService 。

生命周期管理

最后是在 Client 的生命周期中及时关闭连接,清除不需要的对象。

    // in MainActivity
    override fun onDestroy() {
        if (iMsgManager?.asBinder()?.isBinderAlive == true) {
            try {
                iMsgManager?.unregisterReceiveListener(receiveMsgListener)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }
        connection?.let {
            unbindService(it)
        }
        super.onDestroy()
    }

基本上这就是一个完整的 AIDL 流程了。

总结

  • AIDL 是一套快速实现 Android Binder 机制的框架。
  • Android 中的 Binder 机制,架构思想是 C / S 架构。
  • 所有跨进程传递的数据需要实现 Parcelable (除了一些基本的类型)。
  • 所有跨进程调用的对象,都必须是 Binder 的实现。
  • Binder 对象可以和 Interface 实例进行转换,这是因为 Service 中返回的 Binder 对象实现了 Interface。
  • 通过 aidl 文件中定义的接口,可以跨进程调用远程对象的方法。