Android AIDL 多客户端跨进程通信实战:从源码到二次开发
简介这份源码资源围绕 Android AIDL 跨进程通信展开面向需要实现「多个客户端调用同一服务端」的开发者尤其适合已掌握基础 Activity 与 Service、想进一步理解 Binder 机制的中级学习者。包内共 92 个文件以 class、java、xml、aidl 等源码与配置类型为主辅以 png 界面截图、apk 安装包、dex 与 jar 依赖压缩包约 235KB体积轻便便于直接导入 Android Studio 或 Eclipse 对照调试。资源通过 T1、Service、T2 三个模块分别演示客户端、服务端与另一客户端的完整调用链路涵盖 AIDL 接口定义、Stub 实现、bindService 绑定、onServiceConnected 回调以及 RemoteException 处理等关键环节并涉及多客户端并发调用时的线程安全与资源调度思路。已有 1050 人学习下载适合作为理解 AIDL 多客户端通信、排查跨进程调用问题的实操参考。1. 当多个 App 同时连一个 ServiceAIDL 跨进程通信到底怎么落地做过 Android 系统层或组件化通信的开发者大概率遇到过这个场景三四个客户端 App 要同时调用同一个后台 Service 里的数据接口有的查用户信息有的提交订单状态还有的要做实时回调。如果每个客户端各自 bindService 拿本地 Binder代码会迅速膨胀成意大利面。这时候 AIDLAndroid Interface Definition Language就是那个绕不开的解法——它把跨进程调用伪装成本地方法调用让多客户端共享一个 Service 变得有章可循。这份资源给的正是一套「多客户端调用同一服务器」的 AIDL 完整源码包含服务端 Service 实现、AIDL 接口定义、多个客户端调用示例以及回调监听的处理。适合两类人一是刚接触 Binder 机制、想跑通第一个跨进程 Demo 的新手二是已经在做多模块通信、需要一套可复用 AIDL 骨架的熟手。下面从目录结构一路拆到二次开发把这份代码讲透。2. 项目结构与 AIDL 通信骨架先看清服务端和客户端怎么分工拿到一份 AIDL 源码别急着点运行。先搞清楚它的模块划分和通信链路否则后面调不通只能靠玄学猜。这个项目的核心思路很清晰一个 Service 端进程持有真正的数据实现多个 Client 端进程通过 Binder 驱动拿到代理对象所有跨进程调用最终都落到 Service 的onTransact上。2.1 目录结构三个模块各司其职典型的 AIDL 多客户端工程会拆成三块这份源码也遵循同样的组织方式AidlMultiClient/ ├── app-server/ # 服务端模块提供 Service │ ├── src/main/ │ │ ├── aidl/com/example/aidl/ │ │ │ ├── IUserService.aidl # 主接口定义 │ │ │ └── IUserCallback.aidl # 回调接口定义 │ │ ├── java/com/example/server/ │ │ │ ├── UserService.java # Service 实现 │ │ │ └── UserServiceImpl.java # 真正的业务逻辑 │ │ └── AndroidManifest.xml # 声明 Service 与 action │ └── build.gradle ├── app-client-a/ # 客户端 A │ └── src/main/java/com/example/clienta/MainActivity.java ├── app-client-b/ # 客户端 B │ └── src/main/java/com/example/clientb/MainActivity.java └── build.gradle服务端模块负责定义.aidl文件并实现 Service客户端模块只需要拷贝同一份.aidl文件包名路径必须完全一致编译后系统会自动生成Stub和Proxy类。这里有个血泪经验AIDL 文件在两个模块里的包路径必须一字不差否则编译期就会报Cannot find symbol很多人第一次翻车就翻在这。2.2 通信链路一次调用从客户端到服务端经历了什么理解 AIDL 的关键是理解 Binder 代理模型。客户端拿到的IUserService其实不是真正的实现而是Proxy对象调用方法时参数被写入Parcel通过 Binder 驱动跨进程传到服务端的Stub.onTransact再解包分发给真正的实现方法。// 服务端UserService.java public class UserService extends Service { private final IUserService.Stub mBinder new IUserService.Stub() { Override public String getUserName(int userId) { // 真正的业务逻辑运行在服务端进程 return UserRepository.queryName(userId); } Override public void registerCallback(IUserCallback cb) { // 保存客户端回调注意用 RemoteCallbackList 管理 mCallbackList.register(cb); } }; Override public IBinder onBind(Intent intent) { return mBinder; // 返回 Binder 对象给客户端 } }mBinder是Stub的匿名子类它继承自Binder并实现了IUserService接口。onBind返回这个对象后客户端bindService成功时拿到的IBinder就是它。参数说明getUserName的userId是普通 int直接走 ParcelregisterCallback传的是另一个 AIDL 接口属于跨进程对象引用必须用RemoteCallbackList而不是普通 List否则客户端进程一销毁就会泄漏。2.3 多客户端并发为什么服务端要处理线程安全多个客户端同时调用时服务端的 Binder 方法默认运行在 Binder 线程池里不是主线程。这意味着两件事一是不能在 Binder 方法里直接更新 UI二是共享数据必须加锁或用线程安全容器。// 服务端共享数据用 ConcurrentHashMap 兜底 private final ConcurrentHashMapInteger, String mUserCache new ConcurrentHashMap(); Override public String getUserName(int userId) { String cached mUserCache.get(userId); if (cached ! null) return cached; String name UserRepository.queryName(userId); mUserCache.put(userId, name); return name; }常见做法是给共享状态套一层synchronized或换成ConcurrentHashMap。这份源码里如果用了普通HashMap多客户端压测时就会出现数据错乱属于典型的并发坑。参数上userId作为 key 天然适合做缓存索引但要注意缓存失效策略别让脏数据一直留在内存里。3. 跑起来从编译 AIDL 到多客户端同时 bindService光看结构不够得让它真跑起来。这一章按「编译 → 启动服务 → 客户端连接 → 验证多客户端」的顺序走一遍每一步都给出可抄的命令和代码。3.1 编译与安装AIDL 文件放对位置是关键AIDL 文件必须放在src/main/aidl/下且目录层级要和 package 声明一致。比如package com.example.aidl;就必须放在src/main/aidl/com/example/aidl/里。# 编译服务端 ./gradlew :app-server:assembleDebug # 编译两个客户端 ./gradlew :app-client-a:assembleDebug ./gradlew :app-client-b:assembleDebug # 依次安装服务端先装 adb install -r app-server/build/outputs/apk/debug/app-server-debug.apk adb install -r app-client-a/build/outputs/apk/debug/app-client-a-debug.apk adb install -r app-client-b/build/outputs/apk/debug/app-client-b-debug.apk编译成功后build/generated/aidl_source_output_dir/下会生成IUserService.java里面包含Stub和Proxy。如果这个文件没生成八成是 AIDL 路径写错了或者build.gradle里没开aidl支持。参数说明-r表示覆盖安装调试阶段必加否则会因签名冲突装不上。3.2 服务端声明exported 和 action 一个都不能少Service 要在 Manifest 里声明并且必须exportedtrue否则其他 App 根本绑不上。service android:name.UserService android:exportedtrue android:process:remote intent-filter action android:namecom.example.aidl.USER_SERVICE / /intent-filter /serviceandroid:process:remote让 Service 跑在独立进程这样才能真正验证跨进程通信如果省掉这行Service 和客户端在同一进程AIDL 就退化成普通接口调用测不出问题。action是客户端bindService时的匹配依据两边必须一致。3.3 客户端连接bindService 与 ServiceConnection客户端连接的核心是构造一个带 action 的 Intent然后bindService在onServiceConnected里把IBinder转成接口。private IUserService mService; private boolean mBound false; private final ServiceConnection mConn new ServiceConnection() { Override public void onServiceConnected(ComponentName name, IBinder service) { // 关键asInterface 把 IBinder 转成代理对象 mService IUserService.Stub.asInterface(service); mBound true; try { String userName mService.getUserName(1001); Log.d(ClientA, 拿到用户名: userName); } catch (RemoteException e) { // 跨进程调用随时可能因服务端崩溃抛异常 Log.e(ClientA, 调用失败, e); } } Override public void onServiceDisconnected(ComponentName name) { mBound false; mService null; // 服务端异常断开必须置空 } }; private void bindUserService() { Intent intent new Intent(com.example.aidl.USER_SERVICE); intent.setPackage(com.example.server); // Android 5.0 必须显式指定包名 bindService(intent, mConn, Context.BIND_AUTO_CREATE); }asInterface是 AIDL 生成的静态方法同进程时直接返回 Stub 本身跨进程时返回 Proxy。setPackage这行是 Android 5.0 之后隐式 Intent 绑服务的强制要求漏了会直接抛IllegalArgumentException。onServiceDisconnected里把mService置空是后悔药否则下次调用会拿到一个已经失效的代理直接崩。3.4 验证多客户端两个 App 同时连一个 Service把两个客户端都装上先启动服务端 App或让 Service 随 bind 自动拉起再分别打开客户端 A 和 B。观察 Logcatadb logcat -s ClientA:D ClientB:D UserService:D正常输出应该是两个客户端各自拿到用户名且服务端日志显示两次独立调用。如果客户端 B 连不上先检查服务端是否已被 A 绑定后仍在运行——BIND_AUTO_CREATE会在第一个客户端绑定时创建 Service后续客户端复用同一个实例这正是「多客户端共享一个服务器」的意义所在。4. 避坑与排查AIDL 多客户端最容易翻车的五个点AIDL 的坑大多不在语法而在进程边界和生命周期。下面五条是我在实际项目里踩过的按「现象 → 原因 → 解决」整理。现象一客户端 bindService 返回 false服务连不上。原因通常是 Intent 的 action 和服务端 Manifest 里声明的不一致或者 Android 5.0 没调setPackage。解决两边 action 字符串逐字符比对客户端补上intent.setPackage(服务端包名)。现象二调用方法时抛TransactionTooLargeException。原因是单次跨进程传输的数据超过 Binder 缓冲区上限约 1MB。解决大文件、大 Bitmap 不要直接走 AIDL改成传文件路径或分片传输列表数据做分页。现象三服务端回调客户端时客户端已退出导致崩溃。原因是回调对象没有正确注销服务端还在往一个死掉的进程发消息。解决用RemoteCallbackList管理回调客户端onDestroy时主动unregisterCallback服务端遍历时用beginBroadcast/finishBroadcast。现象四多客户端并发写同一份数据结果错乱。原因是服务端 Binder 方法运行在多线程环境共享变量没做同步。解决共享容器换ConcurrentHashMap复合操作加synchronized别在主线程和 Binder 线程之间裸传可变对象。现象五改了 AIDL 文件后客户端没生效。原因是客户端用的是旧的生成类或者两个模块的 AIDL 文件不同步。解决clean 后重新编译确保服务端和客户端的.aidl文件内容、包路径完全一致最好用软链接或统一放在公共模块里引用。提示调试 AIDL 时优先看adb logcat里的Binder和RemoteException关键字大部分问题都能定位到具体进程。5. 二次开发把这份 AIDL 骨架改造成你自己的通信层跑通之后真正有价值的是怎么把它改成能用的东西。这份源码给的是最小可用骨架二次开发时我一般从三个方向动手接口扩展、权限收紧、回调优化。5.1 扩展接口加方法时注意 Parcelable 支持要给IUserService.aidl加一个返回用户对象的方法得先定义一个Parcelable类并在 AIDL 里import它。// UserInfo.java 实现 Parcelable public class UserInfo implements Parcelable { public int id; public String name; protected UserInfo(Parcel in) { id in.readInt(); name in.readString(); } Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(name); } public static final CreatorUserInfo CREATOR new CreatorUserInfo() { Override public UserInfo createFromParcel(Parcel in) { return new UserInfo(in); } Override public UserInfo[] newArray(int size) { return new UserInfo[size]; } }; Override public int describeContents() { return 0; } }然后在 AIDL 里声明// IUserService.aidl import com.example.aidl.UserInfo; interface IUserService { String getUserName(int userId); UserInfo getUserInfo(int userId); // 新增返回 Parcelable void registerCallback(IUserCallback cb); void unregisterCallback(IUserCallback cb); }注意UserInfo.java和UserInfo.aidl都要存在且包路径一致。参数说明writeToParcel的写入顺序必须和构造函数里的读取顺序完全对应顺序错了不会编译报错但运行时会读到错位数据属于典型的「不报错但结果诡异」的坑。5.2 权限收紧别让任意 App 都能绑你的 Service默认exportedtrue意味着任何 App 都能绑。生产环境要加自定义权限!-- 服务端 Manifest -- permission android:namecom.example.server.ACCESS_USER_SERVICE android:protectionLevelsignature / service android:name.UserService android:exportedtrue android:permissioncom.example.server.ACCESS_USER_SERVICE ... /serviceprotectionLevelsignature表示只有相同签名的 App 才能申请该权限这样就把调用方限制在自己的应用族里。客户端 Manifest 里加uses-permission即可。这一步在组件化项目里几乎是标配否则就是给别人留了后门。5.3 回调优化用 RemoteCallbackList 做批量通知多客户端场景下服务端经常需要主动通知所有已连接的客户端。RemoteCallbackList是专门为此设计的它能自动处理客户端进程死亡。private final RemoteCallbackListIUserCallback mCallbackList new RemoteCallbackList(); public void notifyAllClients(String msg) { int count mCallbackList.beginBroadcast(); try { for (int i 0; i count; i) { mCallbackList.getBroadcastItem(i).onUserChanged(msg); } } catch (RemoteException e) { Log.e(UserService, 通知失败, e); } finally { mCallbackList.finishBroadcast(); // 必须成对调用 } }beginBroadcast和finishBroadcast必须成对出现中间不能有耗时操作否则会阻塞其他 Binder 调用。getBroadcastItem返回的是当前快照里的回调即使遍历过程中有客户端断开也不会崩。5.4 验证改造写一个最小回归用例每次改完 AIDL我都会强制走一遍这个验证流程先装服务端再装两个客户端用adb shell am force-stop杀掉客户端 B观察服务端是否还能正常给客户端 A 返回数据以及回调列表是否自动剔除了 B。这个用例能同时验证连接复用、进程死亡处理和回调管理三件事。从那以后我每次改 AIDL 接口都强制先跑一遍「双客户端 杀进程」的回归确认没有RemoteException和内存泄漏再提交。这套骨架不复杂但把多客户端共享 Service 的边界问题都暴露出来了照着改能省不少调试时间。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

不选模型选路由:三层动态分流中枢,让大模型API成本直降40%

不选模型选路由:三层动态分流中枢,让大模型API成本直降40%

最近大模型评测榜一出来,群里直接炸锅了。AA智能指数10月榜单洗牌幅度确实大,闭源旗舰位置换了人,开源阵营也冒出了几个能打的选手,不少只用单模型的朋友开始焦虑,觉得是不是自己的技术栈一夜之间就过时了。但我看到这…

2026/10/19 3:20:06 阅读更多 →
Claude Code团队级AI Agent开发框架:配置、协同与避坑实践

Claude Code团队级AI Agent开发框架:配置、协同与避坑实践

Claude Code在个人开发者手里很好用,日常写脚本、改bug、跑测试都能hold住。但一旦把个人用法直接搬到团队,十有八九会翻车:每个人各写各的prompt,各存各的配置,Agent的行为完全不可控,代码风格快速分裂&am…

2026/10/19 3:19:06 阅读更多 →
AI工具写论文全流程指南:专科生从选题到定稿的10个工具搭配方案

AI工具写论文全流程指南:专科生从选题到定稿的10个工具搭配方案

每年到了四五月份,总有学弟学妹在微信上找我,开口第一句基本都是:“学长,论文真的写不完了,救救我。”专科生毕业论文虽然不像本科那样动辄要求两三万字,但也不少,加上很多同学大三还在实习、备…

2026/10/19 3:19:06 阅读更多 →

最新新闻

制造业AI落地复盘(一):为什么大多数项目死在‘第一步‘

制造业AI落地复盘(一):为什么大多数项目死在‘第一步‘

【本文为个人实操经验总结,非商业推广。文中数据来源于多个制造业信息化项目的观察,仅代表个体项目情况。】过去两年,接手过十几家制造企业的AI项目。复盘下来,大部分项目失败的原因惊人地一致——不是技术选错了,而是…

2026/10/19 4:14:37 阅读更多 →
C++入门路线:从环境搭建到内存管理避坑指南

C++入门路线:从环境搭建到内存管理避坑指南

C入门这四个字,在外行眼里是兴趣的开始,在行家眼里却是“劝退率最高”的起点之一。作为一门老牌语言,C既要处理底层内存,又要支持面向对象和泛型编程,新手很容易在指针和编译错误里耗光耐心。但反过来看,只…

2026/10/19 4:14:37 阅读更多 →
3节点Hadoop+HBase分布式集群搭建实战与避坑指南

3节点Hadoop+HBase分布式集群搭建实战与避坑指南

1. 为什么今天还要亲手搭一套Hadoop加HBase先把结论摆在前面:如果你只是想跑个WordCount或者存几张测试表,用单机伪分布式五分钟就能糊弄过去。但只要你碰的是真实业务量级的数据——比如每天几千万条日志、需要按行键做毫秒级随机读写、还要保证节点挂了…

2026/10/19 4:14:37 阅读更多 →
Oracle expdp异地导出:核心机制、方案选型与避坑指南

Oracle expdp异地导出:核心机制、方案选型与避坑指南

做过Oracle数据库迁移的朋友,基本都碰到过这类需求:业务库在A机房,灾备库或报表库在B机房,甚至云上测试环境需要一份生产数据。大家第一反应往往是“远程敲一条expdp命令,直接把数据导到另一台机器上”。这个想法听着很…

2026/10/19 4:14:37 阅读更多 →
Oracle expdp异地导出全攻略:从参数配置到避坑指南

Oracle expdp异地导出全攻略:从参数配置到避坑指南

做数据库运维的人,基本都接过这种需求:生产库在A机房,分析平台或者灾备环境在B机房,业务方丢过来一句话,“把这几套核心业务schema的数据导到那边去”,然后留下一堆待办就消失了。这种工作落到执行层&#…

2026/10/19 4:14:37 阅读更多 →
ObjectARX自定义实体开发:从绘制、夹点到对象捕捉的完整实现

ObjectARX自定义实体开发:从绘制、夹点到对象捕捉的完整实现

简介:ObjectARX自定义实体简单实例是一套面向AutoCAD二次开发者的C示例工程,聚焦ObjectARX自定义实体的核心开发流程。其中以一条自定义直线实体为主线,完整演示了实体类继承、DXF/DWG数据读写、worldDraw屏幕绘制、数据库注册以及扩展命令调…

2026/10/19 4:13:37 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →