Android AIDL 笔记

一、AIDL 是什么?

AIDL 全称 Android Interface Definition Language(安卓接口定义语言)。它是一种让你能够定义客户端与服务端之间通过进程间通信(IPC)进行交互的接口的语言。简单说,它就是你用一套语法规则定义好接口,Android 工具链会自动帮你生成 Binder 通信的模板代码,让你可以像调用本地方法一样,调用另一个进程中的方法。

为什么需要 AIDL?

  • Android 系统中,每个进程有自己的独立内存空间,不能直接访问对方的数据。
  • Binder 是底层的 IPC 机制,但直接使用 Binder 需要手动处理序列化、反序列化、事务码等,非常繁琐。
  • AIDL 是对 Binder 的高层封装,让你只需关注接口定义,不用关心底层细节。

二、AIDL 的基本使用步骤

2.1 定义 AIDL 接口

src/main/aidl 目录下创建 .aidl 文件(包名需与 Java/Kotlin 包名一致或对应)。例如,定义一个 IRemoteService.aidl

java 复制代码
// IRemoteService.aidl
package com.example.myserver;

interface IRemoteService {
    // 基本类型
    int getPid();
    
    // 带参数和返回值
    String getData(String key);
    
    // 自定义 Parcelable 对象(需 import)
    import com.example.myserver.User;
    User getUser(int id);
}

注意:

  • AIDL 语法与 Java 类似,支持基本类型、String、CharSequence、List、Map、Parcelable 等。
  • 非基本类型必须显式 import
  • 每个方法默认是同步阻塞的,运行在 Binder 线程池中。

2.2 实现接口(服务端)

在服务端(通常是一个 Service)中实现这个接口:

kotlin 复制代码
// RemoteService.kt
class RemoteService : Service() {
    
    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return android.os.Process.myPid()
        }

        override fun getData(key: String?): String {
            return "Data for $key"
        }

        override fun getUser(id: Int): User {
            return User(id, "User$id")
        }
    }

    override fun onBind(intent: Intent?): IBinder {
        return binder.asBinder()
    }
}

关键点:

  • 生成的 Stub 类继承了 Binder 并实现了你的 AIDL 接口。
  • onBind 中返回这个 Binder 对象。

2.3 客户端绑定服务并调用

客户端需要知道服务端的包名和 Service 类名(或使用隐式 Intent + action)。

kotlin 复制代码
// ClientActivity.kt
class ClientActivity : AppCompatActivity() {

    private var remoteService: IRemoteService? = null

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            // 通过 asInterface 将 IBinder 转换为接口对象
            remoteService = IRemoteService.Stub.asInterface(service)
            // 现在可以调用远程方法了
            val pid = remoteService?.pid
            Log.d("Client", "Remote pid = $pid")
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            remoteService = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 绑定服务
        val intent = Intent()
        intent.component = ComponentName("com.example.myserver", "com.example.myserver.RemoteService")
        bindService(intent, connection, Context.BIND_AUTO_CREATE)
    }

    override fun onDestroy() {
        super.onDestroy()
        unbindService(connection)
    }
}

三、AIDL 支持的数据类型

AIDL 对数据类型的支持是有限制的,必须能被序列化跨进程传输:

类型 说明
基本类型 int, long, boolean, byte, char, float, double
String , CharSequence 自动支持
List 元素类型必须也是 AIDL 支持的;接收端始终是 ArrayList
Map 键值类型必须支持;接收端始终是 HashMap
Parcelable 自定义对象需实现 Parcelable 接口,并在同包名下定义同名 .aidl 文件声明
其他 AIDL 接口 可以在 AIDL 中传递另一个 AIDL 接口(用于回调)

自定义 Parcelable 对象

  1. 创建 User.javaUser.kt,实现 Parcelable
  2. 在同包名目录下创建 User.aidl 文件(即使里面为空,也需要声明):
java 复制代码
// User.aidl
package com.example.myserver;
parcelable User;

四、定向 Tag:in, out, inout

在 AIDL 方法参数中,可以用 inoutinout 修饰,指定参数的数据流向。默认是 in

Tag 含义 用途
in 数据从客户端流向服务端 客户端传递参数给服务端,服务端修改不影响客户端
out 数据从服务端流向客户端 客户端传递一个空对象给服务端,服务端填充后返回给客户端
inout 双向流动 客户端传递对象,服务端修改后,客户端能感知到修改

示例:

java 复制代码
void processData(in String input, out StringBuilder output, inout User user);

经验之谈:

  • outinout 会产生额外的序列化开销,能不用尽量不用。
  • 对于基本类型,定向 tag 无意义,因为它们是值传递。
  • 自定义 Parcelable 才真正体现定向 tag 的作用。

五、实现回调(双向通信)

有时服务端需要主动向客户端发送消息(如监听器模式),这时就需要在 AIDL 中定义回调接口。

  1. 定义回调 AIDL:
java 复制代码
// IRemoteCallback.aidl
package com.example.myserver;
import com.example.myserver.User;

interface IRemoteCallback {
    void onUserChanged(User user);
}
  1. 在服务接口中注册回调:
java 复制代码
// IRemoteService.aidl
import com.example.myserver.IRemoteCallback;

interface IRemoteService {
    void registerCallback(IRemoteCallback callback);
    void unregisterCallback(IRemoteCallback callback);
}
  1. 服务端管理回调列表:
kotlin 复制代码
// RemoteService.kt
private val callbacks = RemoteCallbackList<IRemoteCallback>()

override fun registerCallback(callback: IRemoteCallback?) {
    callback?.let { callbacks.register(it) }
}

override fun unregisterCallback(callback: IRemoteCallback?) {
    callback?.let { callbacks.unregister(it) }
}

// 当有事件发生时,遍历回调
callbacks.broadcast { it.onUserChanged(user) }

关键点:

  • 使用 RemoteCallbackList 管理跨进程回调,它会自动处理进程终止时的清理,避免内存泄漏。
  • 回调方法通常不建议耗时,且应标记为 oneway 以避免阻塞服务端(见下文)。

六、权限验证

为了防止未授权的客户端连接你的 AIDL 服务,可以在 onBind 或方法内部进行权限检查。

6.1 自定义权限

  • AndroidManifest.xml 声明自定义权限:
xml 复制代码
<permission android:name="com.example.myserver.ACCESS_SERVICE" />
  • 服务端在 onBind 中检查:
kotlin 复制代码
override fun onBind(intent: Intent?): IBinder? {
    if (checkCallingPermission("com.example.myserver.ACCESS_SERVICE") == PackageManager.PERMISSION_DENIED) {
        return null
    }
    return binder
}
  • 客户端需在 AndroidManifest.xml 中使用该权限:
xml 复制代码
<uses-permission android:name="com.example.myserver.ACCESS_SERVICE" />

6.2 检查包名或签名

可以在每个方法内部调用 getCallingUid()getCallingPid(),然后验证 UID 对应的包名是否在白名单中,甚至验证签名是否一致(用于同一个开发者的不同应用)。


七、线程管理与 oneway

  • AIDL 方法是阻塞的 :客户端调用远程方法时,当前线程会挂起直到结果返回。如果在 UI 线程调用,容易导致 ANR。因此必须在子线程调用远程方法
  • 服务端方法运行在 Binder 线程池:默认不运行在主线程,所以可以在方法内直接做耗时操作,但注意不要影响其他 Binder 调用。

oneway 关键字

在 AIDL 方法前加 oneway 修饰:

java 复制代码
oneway void notifyEvent(String event);
  • 含义:客户端调用后立即返回,不等待服务端执行完成。服务端会接收请求并放入线程池排队,但客户端不会阻塞。
  • 适用场景:回调方法、通知类方法,不希望客户端等待的场景。
  • 限制 :oneway 方法不能有返回值,也不能有 outinout 参数。

八、异常处理

所有 AIDL 方法都可能抛出 RemoteException,表示通信过程中发生错误(如服务端进程死亡、Binder 死亡等)。客户端调用时必须捕获或声明抛出。

kotlin 复制代码
try {
    remoteService?.someMethod()
} catch (e: RemoteException) {
    // 处理连接异常,比如重新绑定服务
}

此外,可以通过 linkToDeath 监听服务端进程死亡,以便重新连接。


九、AIDL 与 Messenger 对比

特性 AIDL Messenger
底层机制 基于 Binder,直接生成接口 基于 Handler,将消息封装在 Message 中
并发处理 支持多线程并发调用 消息串行处理(Handler 单线程)
适用场景 复杂 IPC、需要多线程、需要高吞吐量 简单的跨进程消息传递,不需要高并发
复杂度 较高,需定义 AIDL 文件 简单,只需创建 Messenger
数据类型 支持 Parcelable 等多种类型 只支持 Message 能携带的数据(Bundle)

我的选择建议:

  • 如果只是简单的发送一些命令或事件,用 Messenger 更轻量。
  • 如果需要频繁调用、传递复杂对象、并发操作,用 AIDL。

十、实战经验与常见坑

10.1 TransactionTooLargeException

当传输的数据量超过 Binder 缓冲区上限(通常是 1MB)时,会抛出此异常。

  • 避免方法:不要在 AIDL 中直接传递大文件或 Bitmap;改用文件共享、SharedMemory 等方式。
  • 检查数据大小:对于 List/Map 要控制元素数量,或分批传输。

10.2 谨慎使用 inout

inout 会导致对象两次序列化(进出各一次),增加开销。除非真的需要修改对象并返回给客户端,否则用 in 就好。

10.3 接口粒度设计

  • 每个 AIDL 方法尽量职责单一,参数不要太多。
  • 频繁调用的方法尽量设计成轻量级,避免在远程调用中做复杂计算。

10.4 进程死亡与重连

  • RemoteCallbackList 管理回调,它会自动处理进程死亡。
  • 客户端可以监听 IBinder.DeathRecipient,在服务端死亡时尝试重连。

10.5 使用 Kotlin 协程简化

在客户端,可以将 AIDL 调用包装成挂起函数,避免手动切线程:

kotlin 复制代码
suspend fun <T> awaitRemoteCall(block: () -> T): T = withContext(Dispatchers.IO) {
    block()
}

然后在协程作用域内调用。


十一、总结

AIDL 是 Android 跨进程通信的基石,虽然入门有一些门槛,但掌握后你会发现它的强大。回想这些年,我用 AIDL 做过音乐播放器的跨进程控制、插件化框架的服务管理、多进程应用的模块通信...... 每次遇到跨进程问题,AIDL 总是首选。

记住几个关键点:

  • AIDL 是接口定义,工具自动生成 Stub。
  • 跨进程调用是同步的,注意线程。
  • 数据序列化有限制,避免大对象。
  • 权限和回调管理要谨慎。
相关推荐
城东米粉儿1 小时前
Android 进程间传递大数据 笔记
android
城东米粉儿2 小时前
Android KMP 笔记
android
冬奇Lab3 小时前
WMS核心机制:窗口管理与层级控制深度解析
android·源码阅读
松仔log4 小时前
JetPack——Paging
android·rxjava
城东米粉儿4 小时前
Android Kotlin DSL 笔记
android
城东米粉儿4 小时前
Android Gradle 笔记
android
城东米粉儿4 小时前
Android Monkey 笔记
android
城东米粉儿5 小时前
Android 组件化 笔记
android