Android | AIDL通信
2026/7/24 20:31:16 网站建设 项目流程

一、前置知识

(一)进程间隔离

A进程无法访问B进程内存,B进程无法访问A进程的内存

在 Android 中,不同 App运行在不同进程,这是 Linux 内核的进程隔离机制。

默认情况下,同一个 App 的所有组件(Activity、Service 等)都运行在同一个主进程中,但为了隔离内存泄漏、防止主界面崩溃拖垮核心后台服务,成熟的大型应用通常会通过android:process属性,将特定组件(如音乐播放 Service)拆分到独立进程(如:music)中运行。

注意:一个进程运行着若干个 Activity 实例

(二)进程间通信

Inter-Process Communication,简称IPC):

进程间通信必须依赖 AIDL / Bundle / ContentProvider 等跨进程机制,这也是 Android Framework 开发中 AIDL 被广泛使用的核心场景之一。

Android中提供了多种可直接调用的IPC方式:

IPC 方式数据传递范围核心特点典型场景实现层级
管道 (Pipe)极少量字节流单工/半双工,延迟极低,仅做信号通知Looper 唤醒、子进程标准输出Linux 内核
文件共享可序列化数据简单,并发不安全低实时性数据交换文件系统
Bundle / Intent基本类型、Parcelable简单易用,数据量小(<1MB)Activity 跳转传参基于 Binder
MessengerBundle 类型轻量串行队列处理低并发消息推送
AIDL几乎所有类型功能强支持 RPC并发多进程音乐服务
ContentProvider结构化数据(Cursor)标准数据共享接口系统通讯录访问
Socket (UDS/TCP)任意字节流跨网络全双工可传输超大文件Zygote 孵化 Appadb 调试聊天室Linux 网络栈

(三)Binder机制

Binder是Android专属的IPC内核机制,是Android Framework所有高阶IPC的底层基座,基于Linux内核驱动实现C/S(客户端/服务端)架构,是Android系统最核心、使用最广泛的IPC方案。

核心优势:一次内存拷贝、支持RPC远程调用、自带权限校验、统一服务管理、线程池调度、并发安全可控

层级

简单理解运行态

核心东西

最简单作用(人话)

应用层(Java)

App普通代码,用户态(权限低)

AIDL、Messenger、Bundle、IBinder、Parcel

开发者唯一会接触的层。负责定义接口、打包数据、发起跨进程调用。

Framework Native层

系统底层代码,用户态(权限低)

客户端代理、服务端桩代码

翻译官。把Java的请求翻译成系统能懂的指令,传给内核,屏蔽所有复杂底层逻辑。

Binder内核驱动层

系统内核代码,内核态(权限最高)

/dev/binder 驱动、内存映射、数据传输

真正干活的。唯一能跨进程传数据、拷贝内存、调度线程、管控通信的核心层。

ServiceManager

服务管理层

独立小程序,用户态运行

服务注册、服务查询

前台登记处。所有服务在这里注册,App需要通信时,在这里找到对应的服务。

核心机制解释后果
一次拷贝发数据进内核(1次),收数据直接内存映射(0次),省掉一次 CPU 搬运。性能优于 Socket/管道,但不能在主线程调耗时 RPC
1MB 限制Binder 缓冲区默认约 1MB,单次事务塞多了会爆。崩溃TransactionTooLargeException(传大图/长列表时常见)。
虚拟隔离进程间地址互不相通,无法传对象指针。多进程下静态变量失效SharedPreferences不安全,必须用 Parcelable。

(四)Parcelable

对比二者都属于序列化,作用都是把对象转成可传输的二进制字节流
Parcelable(Android 专属)Serializable(Java 原生)
实现复杂度较复杂(需手动写writeToParcel等)极简单(仅实现空接口)
序列化速度极快(无反射,直接操作二进制)极慢(依赖反射,开销大)
内存占用紧凑(仅存字段值)臃肿(存大量类描述信息)
定向 Tag完美支持in/out/inout不支持(无法精细控制流向)
AIDL唯一正确选择⚠️ 技术上可行,但严禁使用

Parcelable 是 Android 专属的“快速打包/拆包”协议。

当你要把一个 Java/Kotlin 对象(比如User对象,里面有nameage)从一个进程传到另一个进程时,内存里的对象是散落的(引用指向不同地址)。不能直接扔过去,必须把它“打包”成二进制字节流,对方收到后再“拆包”。

  • 打包(序列化):调用writeToParcel(),把nameage按顺序写进Parcel(快递盒)。

  • 拆包(反序列化):调用构造函数createFromParcel(),按顺序从Parcel里读出nameage,组装成新对象。

Parcelable 就是 Android 为了这个“打包/拆包”过程,给你定的一套必须实现的接口规则。

java的实体类 >= kotlin数据类

(五)Service

Service (后台隐形打工人,分两种上班模式)基本上分为两种形式 :

  • 启动服务(startService):不跟页面交互,后台长期存活,
  • 绑定服务(bindService):页面实时操控服务,没绑定就自动销毁
    • -----IBinder(通话电话线):做电话线
      • 扩展 Binder 类(同 APP+同进程专用)
      • 使用Messenger(跨 APP+ 跨进程,单排队处理消息(×多线程并发))
      • 使用 AIDL(跨 APP +跨进程+多线程并发,同时处理大量请求,需自己处理进程安全)

所有 IPC( Inter-Process Communication,进程间通信。Binder/Messenger/AIDL,)全部建立在「绑定服务」之上

经典场景:音乐播放器
切后台锁屏:用startService启动,页面关掉音乐不会停
重新打开 APP:用bindService绑定服务,拿到播放控制权(暂停 / 切歌 / 看进度)
彻底关闭音乐:页面解绑 + 调用 stopService,缺一不可

操作系统:一座大型工厂

进程:独立的生产车间
线程:车间里各司其职的工人

(六)Android权限

系统预定义权限

允许应用
android.permission.INTERNETinternet,访问网络
android.permission.ACCESS_FINE_LOCATIONaccess_fine_location
获取精确 GPS 位置信息
android.permission.READ_EXTERNAL_STORAGEread_external_storage
读取外部存储(如手机相册、文件)
android.permission.RECORD_AUDIOrecord_audio
通过麦克风录音
android.permission.CAMERA打开摄像头拍照或录像。
android.permission.READ_CONTACTSread_contacts
允许应用读取用户联系人列表
<!-- 在服务端的 AndroidManifest.xml 中 --> <permission android:name="com.example.myapp.ACCESS_SECURE_DATA" android:protectionLevel="normal" />
  • android:name:权限的唯一标识,通常用包名作为前缀。

  • android:protectionLevel:权限的保护级别,常见的有:

    • normal:低风险,系统会自动授予

    • dangerous:高风险,需要用户运行时确认。

    • signature仅当调用方的 APK 与服务端 APK 使用相同签名时,才会授予。这是系统服务间通信最常用的级别,安全性最高。

二、AIDL

Android接口定义语言

(一)AIDL操作步骤

1. 合同(跨进程接口协议):

  • 新建后缀为.aidl的文件(IMyAidlInterface.aidl),定义服务的编程接口;
  • Android SDK自带的工具会解析该文件,自动生成对应的抽象类,这个抽象类可以完成接口实现与进程间通信(IPC)的底层处理工作;

2. 服务端:MyAidlService
在服务中扩展(继承extends/实现implements)抽象类,实现具体的业务逻辑

3.客户端(Activity)

(二)数据流向:定向 Tag

AIDL 参数定向标记,作用:告知 Binder 驱动数据传输方向

标记适用对象基础类型是否可用流向逻辑
inParcelable / 集合 / 基础类型可用(默认)客户端→服务端,修改不回传
out仅 Parcelable、集合不可用客户端传值丢弃,服务端填充数据传回
inout仅 Parcelable、集合不可用客户端原始数据传给服务端,修改后同步返回

(三)调用模式:oneway关键字

AIDL 中一个用来改变跨进程调用(IPC)行为的关键字。((同进程调用时,oneway不生效))

核心是将默认的同步阻塞调用(“打电话”),变为异步非阻塞调用(“发短信”)。

1. 使用与限制

  • 返回值:因为不等待结果,被oneway修饰的方法返回值必须是void

  • 参数oneway不影响参数传递方向,参数仍需配合inoutinout使用

  • 位置:可以单独修饰方法,也可以修饰整个接口,使其所有方法都变成oneway

// 单独修饰方法 interface IPlayer { oneway void play(); // 异步,客户端不等待 int getCurrentPosition(); // 同步,客户端需要等待结果 } // 修饰整个接口(所有方法均为 oneway) oneway interface IUpgradeCallback { void onProgress(int percent); // 隐式 oneway void onStatusChanged(int status); // 隐式 oneway }

2. 典型场景

  • 发送指令,不关心结果:如播放/暂停音乐、开始下载任务等。

  • 回调接口(服务端 → 客户端):服务端向客户端发送进度更新或状态通知。在项目中,ICallback接口被标记为oneway,就是为了防止服务端因等待客户端处理回调而被阻塞

3. 注意事项

(1)无法通过RemoteException感知服务端异常

oneway的优势是:客户端调用不阻塞;代价是:失去了即时异常反馈
在设计oneway方法时,务必要确保调用方“不需要知道执行结果”,或者通过状态查询/回调机制来异步获取结果。在系统服务(如升级、下载、播放控制)中,这种设计非常常见。

当客户端调用一个同步 AIDL 方法时,如果服务端进程死亡或抛出异常,RemoteException立即抛给客户端。客户端可以捕获它并做出处理,例如重试或提示用户。

try { int result = mService.add(100, 200); // 同步调用 } catch (RemoteException e) { // ✅ 能捕获到异常,客户端知道出错了 e.printStackTrace(); // 可以尝试重连 reconnect(); }

当调用oneway方法时,情况完全不同:

  • 客户端调用mService.onewayMethod()立刻返回RemoteException此时无法抛给客户端,因为调用已经“结束”了。

  • 如果服务端在执行onewayMethod()时抛出了异常,这个异常只会被服务端自己捕获,客户端无法感知

// 服务端方法 oneway void doSomething() { throw new IllegalStateException("服务端内部错误"); // 客户端不会收到这个异常 }

客户端调用mService.doSomething()不会收到任何异常。客户端只会看到调用“成功返回”,但实际上服务端的业务逻辑可能已经失败了。

解决方案

既然客户端无法通过异常感知错误,系统服务通常会采用以下两种设计方案:

方案一:增加状态查询接口

客户端调用oneway指令后,如果有需要确认执行结果的需求,可以额外提供一个getStatus()方法:

interface IUpgrade { // 异步指令 oneway void startUpgrade(String path); // 同步查询:客户端调用后轮询这个方法来确认执行结果 int getUpgradeStatus(); }

方案二:状态变更通过回调返回

客户端先注册一个回调接口,服务端在oneway方法执行完毕后(无论成功还是失败),通过回调把结果通知给客户端:

interface IUpgrade { oneway void startUpgrade(String path); } interface IUpgradeCallback { // 服务端执行完 startUpgrade 后,调用此回调通知结果 oneway void onUpgradeResult(int code, String message); }
(2)在服务端是串行执行的

这是oneway最关键且最容易被忽视的特性:从同一个客户端线程发往同一个服务端 Binder 对象的oneway调用,在服务端是串行执行的

普通 AIDL 方法:不同客户端、不同线程的调用可以并发执行。服务端 Binder 线程池中有多个线程,可以同时处理多个请求。

条件是否串行
同一个客户端线程 → 同一个 Binder 对象串行(保证顺序)
不同客户端线程 → 同一个 Binder 对象❌ 不保证(但仍按顺序入队)
不同客户端 → 同一个 Binder 对象❌ 不保证(可以并行处理)

假设客户端依次调用:

mService.onewayMethodA(); // 调用1 mService.onewayMethodB(); // 调用2 mService.onewayMethodC(); // 调用3 //这三个调用在服务端的执行顺序永远是 A → B → C(先到先处理)。 //A 执行完,才会执行 B,再执行 C。

这是因为:oneway方法不返回结果,如果允许并发执行,可能导致指令顺序错乱。例如:

mService.stop(); // 调用1 mService.play(); // 调用2 //如果这两个 oneway 调用在服务端并发执行, //play() 可能先被处理,而 stop() 后执行,导致状态异常。

串行化保证了指令执行的顺序与客户端调用的顺序一致,避免了这类状态错乱问题。

在系统服务中,许多控制类方法被设计为oneway,正是为了利用这个“顺序保证”特性。例如:

  • 音频控制:pause()play()seekTo()

  • 升级流程:prepare()start()cancel()

(四)Binder 死亡监听机制

在真实系统中,服务端进程可能因为以下原因突然死亡:

  1. 进程崩溃:服务端代码出现未捕获异常,进程被系统杀死。

  2. 系统内存不足:系统(Low Memory Killer)回收后台进程,服务端进程被杀死。

  3. 用户手动停止:用户在“设置 → 应用”中强行停止应用。

  4. 系统重启:手机重启,所有进程被清空。

在 Framework 开发中,系统服务(如 AMS、WMS)如果突然死亡,所有依赖它的 App 都会受到影响。因此,客户端必须能感知服务端的死亡,并做出相应处理(如重连、清理资源、提示用户)

Android 提供了DeathRecipient接口,允许客户端在Binder 对象上注册一个“死亡监听器”。当 Binder 对象所在的进程死亡时,系统会回调客户端的binderDied()方法。

步骤API
在 Binder 对象上注册死亡监听IBinder.linkToDeath(DeathRecipient recipient, int flags)
取消注册死亡监听(防止内存泄漏)。IBinder.unlinkToDeath(DeathRecipient recipient, int flags)
当 Binder 对象所在进程死亡时,系统回调此方法。DeathRecipient.binderDied()
步骤核心动作在哪里写关键说明
实现死亡监听接口创建DeathRecipient对象,重写binderDied()客户端 Activity / 任意持有 Binder 引用的类binderDied()运行在Binder 线程池,不能直接更新 UI,需要切到主线程
注册死亡监听onServiceConnected()中拿到IBinder后,调用linkToDeath()ServiceConnection.onServiceConnected()注册时机必须是在绑定成功后,否则IBinder为 null;linkToDeath可能抛出RemoteException,需 try-catch
处理死亡回调binderDied()中:① 清空引用 ② 更新 UI ③ 触发重连DeathRecipient.binderDied()重连时需加防重复标志(如isReconnecting),防止多次绑定;重连成功后需要重新注册回调监听器重新注册死亡监听
取消注册在客户端销毁或解绑时调用unlinkToDeath()onDestroy()或解绑方法中必须调用,否则会内存泄漏(服务端已死但客户端仍持有DeathRecipient引用)
重连后恢复状态重新绑定成功后,重新注册回调、重新注册死亡监听onServiceConnected()(重连后的再次回调)重连成功后,所有之前注册的监听器都会失效,需要重新执行步骤 2 的注册逻辑

(五)回调管理:RemoteCallbackList

在 AIDL 跨进程通信中,服务端经常需要主动向客户端推送消息(如升级进度、状态变化)。这需要客户端把回调接口注册到服务端,服务端持有这些回调并在适当时机调用。
然而,当 Client 和 Server 处于不同进程时,客户端进程可能随时崩溃或退出。如果服务端仍向已死亡的客户端发送回调,会触发RemoteException,轻则抛出异常,重则导致服务端进程崩溃。

"Takes care of the grunt work of maintaining a list of remote interfaces, typically for the use of performing callbacks from a Service to its clients."——官方
替你打理维护远程接口列表的繁琐工作,典型用途是从 Service 向其客户端执行回调。

RemoteCallbackList:存放 AIDL 回调接口的“安全容器”。让服务端能安全地向多个客户端发送回调,并自动清理已经死亡的客户端,防止内存泄漏和服务端崩

//泛型定义 public class RemoteCallbackList<E extends IInterface> // E 必须是 AIDL 接口类型
int count = mListeners.beginBroadcast(); // 开始遍历 for (int i = 0; i < count; i++) { mListeners.getBroadcastItem(i).onProgress(50); } mListeners.finishBroadcast(); // 必须调用,否则会死锁并泄漏

(六)版本兼容性:Stable AIDL

AIDL 版本兼容性在系统级开发是一个非常重要的话题。当你的 AIDL 接口更新后,需要确保旧版本的客户端(比如已发布的 App)仍然能正常工作,不会因为接口变动而崩溃。

当前 Android 解决这个问题的标准方案是Stable AIDL,它提供了一套完整的版本管理机制。Stable AIDL 是 Android 在 Android 10引入的一种机制,它主要围绕几个核心概念运作:

  • 接口即契约 (Interface as a Contract):一个 AIDL 接口一旦发布,就成为一个永久、向后兼容的契约。这意味着不能修改或删除已发布版本中的任何方法,只能进行添加。
  • 冻结 (Freeze)版本控制 (Versioning):通过“冻结” (Freeze)操作,为 AIDL 接口创建一个不可变的快照。每一个快照对应一个版本。新版本的接口会与旧版本并存,系统会自动进行管理。
  • 版本号管理:每个 Stable AIDL 接口都在`aidl_api/<interface_name>/`目录下有自己的版本历史。构建系统通过 `versions_with_info` 等字段来管理这些版本。

三、Android系统权限安全校验

在 Android 系统中,运行着许多拥有极高权限、提供核心功能的系统服务(System Service),它们运行在独立的system_server进程中。例如,负责管理四大组件生命周期的ActivityManagerService (AMS),以及负责管理窗口的WindowManagerService (WMS)

客户端 App 通过 Binder 机制与这些系统服务进行跨进程通信(IPC),以调用其提供的功能。然而,系统服务级别的 AIDL 接口默认是没有权限屏障的——任何进程只要能拿到 Binder 引用,就能调用服务端暴露的所有方法。

因此,权限校验是系统服务的第一道安全防线。系统服务在执行敏感操作前,必须校验调用方的身份(UID/PID)和权限(Permission),确保调用者是合法的、有授权的。这套校验逻辑内置于 AMS、WMS 等核心系统服务中,是 Android 安全模型的重要组成部分。

客户端(App)不会主动去校验自己,在实际系统交互中,客户端想要敲开服务端的门,是需要先“出示证件”的:

  • 客户端必须在自己的AndroidManifest.xml里声明<uses-permission>(比如我要用ACCESS_MY_SERVICE权限)。

  • 当客户端发起 Binder 调用时,系统内核(Binder 驱动)会自动把客户端的UID(身份证号)PID(临时工号)附加在数据包上发给服务端。

  • 客户端无法伪造这个 UID(这是 Linux 内核干的活,App 没权限改)。

AIDL 服务端实现一套完整的身份认证与权限校验体系?

时间点动作关键说明操作人
开发阶段自定义系统权限AndroidManifest.xml中用<permission>声明权限名称和保护级别(normal/dangerous/signature)。服务端
运行时识别调用者身份onTransact()或 AIDL 接口方法实现中,通过Binder.getCallingUid()/Binder.getCallingPid()获取身份(UID 是校验首选)。服务端
运行时校验调用者权限onTransact()中使用checkCallingPermission(权限名)进行校验,确认调用方是否拥有相应权限。服务端
运行时拦截 / 放行

权限不足 → 抛出SecurityException("Permission denied")

权限充足 → 调用super.onTransact()继续执行业务逻辑。

服务端

四、Binder 连接池

Android 中一种用单个 Service 管理多个业务 Binder 的设计模式。客户端只需绑定一次,通过查询码获取不同业务的 Binder,从而解决多模块多 Service 带来的资源开销和代码耦合问题。

一个 Service 管理多个 Binder,新增模块只需在queryBinder中增加一个分支,无需新增 Service,实现模块解耦和代码精简。

问题:在传统 AIDL 使用方式中,一个 Service 只能绑定一个 AIDL 接口。当业务模块增多时,每个模块都需要独立的 Service,导致服务数量膨胀、资源开销增大、客户端绑定逻辑复杂。

解决方案:引入Binder 连接池模式。 服务端只暴露一个统一的 Service 和一个IBinderPool接口,客户端通过queryBinder(int code)方法,根据业务码获取对应的 Binder,再转为具体的业务接口使用。

五、结语

这几天的学习核心是AIDL

学习起点是 Linux 内核的进程隔离机制——不同 App 天然运行在不同进程;同一 App 内的不同组件(如 Activity、Service)默认运行在同一进程,但可通过android:process属性拆分到不同进程,从而实现进程隔离。

因为有进程隔离,进程间通信(IPC)就成了绕不开的课题。Android 提供了多种可直接调用的 IPC 方式,例如基于 Binder 机制的 Intent(跨进程场景)、Messenger、AIDL、ContentProvider,以及基于 Linux 内核的管道、Socket 等。

接着正式进入 AIDL 的学习:先明确什么场景下使用它,由此延伸到 Service 的两种启动形式——startService(后台长期运行)和bindService(页面实时操控)。bindService又对应三种跨进程通信方式:扩展 Binder 类(同 App 同进程)、Messenger(跨进程,轻量串行)、AIDL(跨进程,多线程并发)。随后补充了 Binder 机制原理、Parcelable 序列化规范、Android 权限体系等前置知识,并用一个简单 Demo 完成实操。

在 Demo 基础上,对 AIDL 进行了功能拓展:

  • 定向 Tag(in / out / inout):控制跨进程传递数据的流向;

  • Binder 死亡监听机制:客户端通过linkToDeath感知服务端进程死亡并容错重连;

  • 回调管理RemoteCallbackList:让服务端能安全地向多个客户端发送回调,并自动清理已死亡的客户端;

  • Android 系统权限校验:服务端在AndroidManifest.xml中用<permission>定义权限,客户端用<uses-permission>声明使用;服务端通过Binder.getCallingUid()识别调用者身份,结合checkCallingPermission()校验权限,校验失败则抛出SecurityException拦截;

  • Binder 连接池:服务端只暴露一个统一 Service 和一个IBinderPool接口,客户端通过queryBinder(int code)查询并获取不同业务模块的 Binder,实现多接口复用

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询