简介这份源码资源围绕 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 的边界问题都暴露出来了照着改能省不少调试时间。希望帮到你。本文还有配套的精品资源点击获取