Android | AIDL通信
一、前置知识一进程间隔离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简单易用数据量小1MBActivity 跳转传参基于 BinderMessengerBundle 类型轻量串行队列处理低并发消息推送AIDL几乎所有类型功能强支持 RPC并发多进程音乐服务ContentProvider结构化数据Cursor标准数据共享接口系统通讯录访问Socket (UDS/TCP)任意字节流跨网络、全双工、可传输超大文件Zygote 孵化 App、adb 调试、聊天室Linux 网络栈三Binder机制Binder是Android专属的IPC内核机制是Android Framework所有高阶IPC的底层基座基于Linux内核驱动实现C/S客户端/服务端架构是Android系统最核心、使用最广泛的IPC方案。核心优势一次内存拷贝、支持RPC远程调用、自带权限校验、统一服务管理、线程池调度、并发安全可控。层级简单理解运行态核心东西最简单作用人话应用层JavaApp普通代码用户态权限低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对比二者都属于序列化作用都是把对象转成可传输的二进制字节流ParcelableAndroid 专属SerializableJava 原生实现复杂度较复杂需手动写writeToParcel等极简单仅实现空接口序列化速度极快无反射直接操作二进制极慢依赖反射开销大内存占用紧凑仅存字段值臃肿存大量类描述信息定向 Tag完美支持in/out/inout不支持无法精细控制流向AIDL✅唯一正确选择⚠️ 技术上可行但严禁使用Parcelable 是 Android 专属的“快速打包/拆包”协议。当你要把一个 Java/Kotlin 对象比如User对象里面有name和age从一个进程传到另一个进程时内存里的对象是散落的引用指向不同地址。不能直接扔过去必须把它“打包”成二进制字节流对方收到后再“拆包”。打包序列化调用writeToParcel()把name和age按顺序写进Parcel快递盒。拆包反序列化调用构造函数createFromParcel()按顺序从Parcel里读出name和age组装成新对象。Parcelable 就是 Android 为了这个“打包/拆包”过程给你定的一套必须实现的接口规则。java的实体类 kotlin数据类五ServiceService 后台隐形打工人分两种上班模式基本上分为两种形式 启动服务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:namecom.example.myapp.ACCESS_SECURE_DATA android:protectionLevelnormal /android:name权限的唯一标识通常用包名作为前缀。android:protectionLevel权限的保护级别常见的有normal低风险系统会自动授予。dangerous高风险需要用户运行时确认。signature仅当调用方的 APK 与服务端 APK 使用相同签名时才会授予。这是系统服务间通信最常用的级别安全性最高。二、AIDLAndroid接口定义语言一AIDL操作步骤1. 合同跨进程接口协议新建后缀为.aidl的文件IMyAidlInterface.aidl定义服务的编程接口Android SDK自带的工具会解析该文件自动生成对应的抽象类这个抽象类可以完成接口实现与进程间通信IPC的底层处理工作2. 服务端MyAidlService在服务中扩展继承extends/实现implements抽象类实现具体的业务逻辑3.客户端Activity二数据流向定向 TagAIDL 参数定向标记作用告知 Binder 驱动数据传输方向标记适用对象基础类型是否可用流向逻辑inParcelable / 集合 / 基础类型可用默认客户端→服务端修改不回传out仅 Parcelable、集合不可用客户端传值丢弃服务端填充数据传回inout仅 Parcelable、集合不可用客户端原始数据传给服务端修改后同步返回三调用模式oneway关键字AIDL 中一个用来改变跨进程调用IPC行为的关键字。((同进程调用时oneway不生效))核心是将默认的同步阻塞调用“打电话”变为异步非阻塞调用“发短信”。1. 使用与限制返回值因为不等待结果被oneway修饰的方法返回值必须是void参数oneway不影响参数传递方向参数仍需配合in、out、inout使用位置可以单独修饰方法也可以修饰整个接口使其所有方法都变成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 死亡监听机制在真实系统中服务端进程可能因为以下原因突然死亡进程崩溃服务端代码出现未捕获异常进程被系统杀死。系统内存不足系统Low Memory Killer回收后台进程服务端进程被杀死。用户手动停止用户在“设置 → 应用”中强行停止应用。系统重启手机重启所有进程被清空。在 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为 nulllinkToDeath可能抛出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 RemoteCallbackListE extends IInterface // E 必须是 AIDL 接口类型int count mListeners.beginBroadcast(); // 开始遍历 for (int i 0; i count; i) { mListeners.getBroadcastItem(i).onProgress(50); } mListeners.finishBroadcast(); // 必须调用否则会死锁并泄漏六版本兼容性Stable AIDLAIDL 版本兼容性在系统级开发是一个非常重要的话题。当你的 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 进行了功能拓展定向 Tagin / out / inout控制跨进程传递数据的流向Binder 死亡监听机制客户端通过linkToDeath感知服务端进程死亡并容错重连回调管理RemoteCallbackList让服务端能安全地向多个客户端发送回调并自动清理已死亡的客户端Android 系统权限校验服务端在AndroidManifest.xml中用permission定义权限客户端用uses-permission声明使用服务端通过Binder.getCallingUid()识别调用者身份结合checkCallingPermission()校验权限校验失败则抛出SecurityException拦截Binder 连接池服务端只暴露一个统一 Service 和一个IBinderPool接口客户端通过queryBinder(int code)查询并获取不同业务模块的 Binder实现多接口复用

相关新闻