Android AIDL跨进程通信全解析:从原理到实战
做Android跨进程通信这些年绕不开的一个东西就是AIDL。我刚入行那会儿被Binder和AIDL搞得一头雾水光是搞清楚in、out、inout的区别就花了好几天。后来在项目里做过音乐播放器、消息推送、后台定位几乎每个涉及到Service通信的场景都要跟AIDL打交道踩过的坑多了才慢慢摸清它的脾气。这篇文章就把我对AIDL的理解、实战经验、还有那些文档里不会明说的细节一次性讲清楚。不管你是刚接触Android的初学者还是做了几年开发想系统梳理一下这块知识的老手这篇文章应该都能给你一些参考。1. AIDL到底解决了什么问题1.1 进程隔离与通信的本质需求先聊一个最基础的问题为什么需要AIDLAndroid系统里每个App默认运行在独立的进程里进程之间是相互隔离的。这就好比两个人在不同的房间各自关着门想递个东西过去不能直接伸手递得通过一个“窗口”来传递。这个“窗口”在Android里就是Binder机制而AIDL就是我们描述这个“窗口”上应该有哪些动作的一种语言。之所以需要跨进程通常有这几个场景同一个App里某些组件比如后台定位Service单独跑在一个进程里主进程需要跟它通信。不同App之间需要共享数据或调用能力比如微信支付跳过去再回调结果。系统级服务比如ActivityManager、WindowManager它们运行在系统进程里App要调用系统功能本质上也是跨进程通信。如果没有跨进程通信以上这些场景全都做不了。而AIDL就是Android官方提供的一套标准化的跨进程通信接口定义工具。1.2 为什么是AIDL而不是其他方案很多人会问Android跨进程通信的方式不止AIDL一种还有广播BroadcastReceiver、ContentProvider、Socket、Messenger为什么很多场景偏偏选AIDL我个人的理解是这样的Intent Bundle只能携带有限类型的Bundle数据适合简单传参做不到高频率、双向的、方法级的调用。广播是单向的、松耦合的适合事件通知不适合一问一答式的请求响应。而且广播性能一般全局广播还容易被别的高耗电应用监听导致安全问题。ContentProvider主要解决数据共享的问题面向的是SQLite或者文件这类数据源接口也比较固定query、insert、update、delete不适合传递自定义的复杂业务方法。Messenger底层其实封装的也是AIDL但它只能支持Message这种格式的消息传递发过去发回来都是消息处理复杂逻辑就要自己拼协议比较繁琐。Socket通用性强但性能开销大在Android的进程通信里属于“重武器”平时不太用得上。所以当你需要跨进程调用另一个进程里的“方法”并且希望调用方式跟本地调用差不多参数和返回值都可以是自定义对象同时还要有同步/异步、双向通信这些能力的时候AIDL就是最合适的选择。1.3 AIDL与Binder的关系这里必须把AIDL和Binder的关系理清楚否则后面看任何代码都会一头雾水。Binder是Android系统底层的进程通信机制它负责数据在两个进程之间真正地传输。而AIDL是一套“接口描述语言”它本身不参与数据传输它的作用是帮你生成一堆Java代码这堆代码封装好了Binder通信的所有细节。你可以把Binder理解为一条“管道”AIDL就是管道的“图纸”。我们做的事情其实是先画一张图纸写.aidl文件然后交给构建工具去施工生成Java代码最后拿着施工好的管道Binder对象去连接两个进程。提示AIDL文件编译后生成的代码通常在build/generated/aidl_source_output_dir目录下。如果找不到生成的文件多半是构建没有成功或者.aidl文件放错了位置。这个目录可以作为排查问题的第一个突破口。2. AIDL的核心语法与数据类型2.1 支持的数据类型清单AIDL不是什么数据类型都能用它有一套自己的“白名单”这一点很多初学者第一次写的时候就栽了跟头。基础支持的类型如下Java基本类型int、long、boolean、float、double、byte、char、short。String。CharSequence。List里面的元素必须是AIDL支持的类型可以是泛型声明比如ListString。Map同样要求key和value都是AIDL支持的类型。Parcelable对象必须是实现了Parcelable接口的类并且在AIDL文件里要通过import引入。IBinder对象可以用来传递Binder引用实现把另一个Binder对象传给对端。不支持的类型包括Integer、Long这些包装类型虽然在方法里可以用但AIDL文件里写的时候要用基本类型java.util.Date、JSONObject这类对象直接传是跑不起来的需要自己转成Parcelable或者序列化成字符串再传。File、Bitmap这些类也不能直接用要么转成byte数组要么实现Parcelable。2.2 in、out、inout方向标记及其原理这一块是面试最爱考的点也是实际开发中特别容易出问题的地方。AIDL方法里的非基本类型参数必须指定方向标记三种标记的含义分别是in客户端把数据传过去给服务端。数据只是单向流动服务端对参数做的修改不会回传给客户端。out客户端不传数据给服务端而是服务端负责填充数据调用结束后客户端可以拿到服务端填好的值。相当于服务端“向外输出”。inout双向的。客户端传数据过去服务端可以修改修改后的结果会传回客户端。原理上in方向的参数会调用Parcel.writeToParcel()把你传入的对象完整写进Parcel里服务端拿到的是一个全新的反序列化对象out方向的参数则对应数据从服务端写入Parcel返回回来inout就是两头都写代价是数据拷贝两次效率最低。实操中我的建议是能少用就少用inout尤其是大对象。inout会让系统做两次序列化和反序列化性能开销直接翻倍。大部分业务场景其实in就够了。如果服务端需要返回结果直接写在返回值里更清晰。2.3 oneway关键字与异步调用在接口方法前面加上oneway关键字表示这个调用是异步的。什么意思呢客户端调用这个方法时不会阻塞等待服务端返回而是立刻返回。这个方法适合用在那些耗时操作、或者你根本不关心返回结果的通知型调用上。需要注意两点oneway方法不能有返回值也不能有out或inout方向的参数因为客户端不等结果就没法接收返回的东西。oneway只是让客户端不等待但服务端处理该请求仍然是在Binder线程池中执行的不意味着服务端会开启新线程。它只解决客户端阻塞的问题。场景举例音乐播放器里通知播放服务“暂停”“下一曲”这种操作就不需要阻塞用oneway再合适不过。但如果要查询当前播放进度那就必须用同步方法等着返回值。3. 从零到一手写一个完整的AIDL通信Demo3.1 定义接口与Parcelable对象空谈原理没意思直接跑一个Demo。我这里的场景是做一个简单的音乐播放助手客户端比如一个Activity向播放服务发起播放请求服务端执行播放、上报进度、还可以接收客户端的主动查询。第一步新建一个MusicData类实现Parcelable接口。这个类用来封装歌曲信息比如歌名、歌手、时长、当前进度。注意AIDL跨进程传输对象时只能是Parcelable不能是Serializable。为什么因为Binder底层本身就是基于Parcel进行内存共享和拷贝的Parcelable在效率和设计上更契合这套机制。public class MusicData implements Parcelable { private String songName; private String artist; private int duration; private int progress; public MusicData(String songName, String artist, int duration) { this.songName songName; this.artist artist; this.duration duration; } protected MusicData(Parcel in) { songName in.readString(); artist in.readString(); duration in.readInt(); progress in.readInt(); } Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(songName); dest.writeString(artist); dest.writeInt(duration); dest.writeInt(progress); } Override public int describeContents() { return 0; } public static final CreatorMusicData CREATOR new CreatorMusicData() { Override public MusicData createFromParcel(Parcel in) { return new MusicData(in); } Override public MusicData[] newArray(int size) { return new MusicData[size]; } }; // getter和setter省略 }字段的写入和读取顺序必须完全一致writeString对应readStringwriteInt对应readInt顺序错了数据全乱。第二步创建AIDL文件。这个文件的位置有讲究在Android Studio里默认应该放在src/main/aidl目录下包名必须跟MusicData所在包名一致这样才能在AIDL文件中通过import找到它。创建IMusicAidlInterface.aidl// IMusicAidlInterface.aidl package com.example.musicdemo; import com.example.musicdemo.MusicData; interface IMusicAidlInterface { void playMusic(MusicData music); void pauseMusic(); void stopMusic(); MusicData queryMusicStatus(); void registerCallback(IMusicCallback callback); void unregisterCallback(IMusicCallback callback); }这里我故意加了一个回调——跨进程双向通信。客户端注册一个回调对象服务端播放状态变化时主动通知客户端。这个能力在真实项目中非常常用协议上就是AIDL支持把Binder对象作为参数传进接口方法。同步创建一个IMusicCallback.aidl// IMusicCallback.aidl package com.example.musicdemo; interface IMusicCallback { void onMusicProgressChanged(int progress); void onMusicPlayStateChanged(boolean isPlaying); }注意回调接口里没有MusicData所以不需要import直接定义即可。3.2 服务端实现与注册服务端是一个Service核心逻辑是把AIDL接口定义的Stub实现出来然后通过onBind返回给客户端。重建一个MusicServicepublic class MusicService extends Service { private boolean isPlaying false; private int currentProgress 0; private final CopyOnWriteArrayListIMusicCallback callbackList new CopyOnWriteArrayList(); private final IMusicAidlInterface.Stub binder new IMusicAidlInterface.Stub() { Override public void playMusic(MusicData music) throws RemoteException { // 模拟播放逻辑 isPlaying true; currentProgress 0; new Thread(new Runnable() { Override public void run() { try { while (isPlaying currentProgress music.getDuration()) { Thread.sleep(1000); currentProgress 1000; for (IMusicCallback callback : callbackList) { callback.onMusicProgressChanged(currentProgress); } } isPlaying false; for (IMusicCallback callback : callbackList) { callback.onMusicPlayStateChanged(false); } } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } Override public void pauseMusic() { isPlaying false; } Override public void stopMusic() { isPlaying false; currentProgress 0; } Override public MusicData queryMusicStatus() throws RemoteException { MusicData data new MusicData(Current Song, Unknown Artist, 240000); data.setProgress(currentProgress); return data; } Override public void registerCallback(IMusicCallback callback) { if (callback ! null !callbackList.contains(callback)) { callbackList.add(callback); } } Override public void unregisterCallback(IMusicCallback callback) { if (callback ! null) { callbackList.remove(callback); } } }; Override public IBinder onBind(Intent intent) { return binder; } }这里我用了一个CopyOnWriteArrayList来存储回调对象因为可能有多个客户端注册回调而且Binder回调是在Binder线程池中触发的涉及线程安全。CopyOnWriteArrayList在写少读多的场景下性能不错同时迭代时不会抛ConcurrentModificationException。然后在AndroidManifest.xml里注册Service并跨App访问时设置android:exportedtrue如果只是本App内部使用建议falseservice android:name.MusicService android:exportedtrue android:process:remote intent-filter action android:namecom.example.musicdemo.MusicService / /intent-filter /service指定android:process:remote是为了让Service跑在独立进程里这样才是真正意义上的跨进程通信。如果不单独指定进程默认跑在App主进程里虽然也能用AIDL但就失去了跨进程的意义。3.3 客户端绑定与调用客户端绑定Service的标准流程先bindService然后在ServiceConnection的回调里拿到IBinder再通过IMusicAidlInterface.Stub.asInterface()转成接口对象。public class MainActivity extends AppCompatActivity { private IMusicAidlInterface musicService; private IMusicCallback callback new IMusicCallback.Stub() { Override public void onMusicProgressChanged(int progress) { runOnUiThread(new Runnable() { Override public void run() { // 更新UI进度条 progressBar.setProgress(progress); } }); } Override public void onMusicPlayStateChanged(boolean isPlaying) { runOnUiThread(new Runnable() { Override public void run() { // 更新播放按钮状态 } }); } }; private ServiceConnection connection new ServiceConnection() { Override public void onServiceConnected(ComponentName name, IBinder service) { musicService IMusicAidlInterface.Stub.asInterface(service); try { musicService.registerCallback(callback); } catch (RemoteException e) { e.printStackTrace(); } } Override public void onServiceDisconnected(ComponentName name) { musicService null; } }; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 绑定服务 Intent intent new Intent(com.example.musicdemo.MusicService); intent.setPackage(getPackageName()); bindService(intent, connection, Context.BIND_AUTO_CREATE); } Override protected void onDestroy() { super.onDestroy(); if (musicService ! null) { try { musicService.unregisterCallback(callback); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(connection); } }回调对象为什么放在客户端而不是直接匿名创建因为回调跟Service连接是绑定的而且需要保证在unregisterCallback时传的是同一个对象。如果你在registerCallback里new了一个匿名对象然后在unregisterCallback里又new一个服务端的CopyOnWriteArrayList里找不到相等的对象就会移除失败。这是新手小白最容易踩的坑。另外需要注意从Android 8.0API 26开始bindService时如果使用的是隐式Intent必须同时设置setPackage()否则会报IllegalArgumentException。Android 5.0以上隐式绑定Service还需要在Intent里明确指定包名或组件名。3.4 AIDL文件生成失败与编译异常的排查这个主题在开发中简直太常见了。我遇到过几种典型情况这里专门列出来给读者排查AIDL文件里的包名与import路径不匹配。AIDL里package声明、import路径、以及Parcelable对象实际所在的包名三者必须完全一致。如果不一致构建报错生成目录里看不到任何以Stub结尾的Java文件。AIDL文件没有放在src/main/aidl目录下。老版本AS支持放java目录旁边新版本推荐放aidl目录。如果放错了Gradle不会扫描到导致编译过但运行时找不到类或者直接编译失败。Parcelable类没有实现Parcelable接口或者CREATOR字段缺失。AIDL生成的代码会依赖这个字段来反序列化对象。方法里使用了不支持的类型比如传了Integer用包装类型、JSONObject、ArrayListObject。编译时会报“unsupported type”之类的错误。Android Studio缓存问题改了.aidl文件但编译结果没有变化尝试Build - Clean Project再Build - Rebuild Project。如果还不行关掉AS删除.gradle和build目录重新打开再编译。Kotlin项目里的AIDL如果项目用了KotlinAIDL生成的是Java代码不影响调用。但在Kotlin里引用Java生成的静态字段如Stub.asInterface语法上不用额外处理直接调用即可。这些坑每一条我都在实际项目中踩过报错信息五花八门根源多半是路径或类型问题。建议写了AIDL文件之后先把项目整个Rebuild一遍确认生成目录里出现了对应的Java文件再接下去写逻辑能省很多莫名其妙的调试时间。4. AIDL的进阶用法连接池、死亡回调与权限校验4.1 Binder连接池BinderPool随着业务变大AIDL接口会越来越多。如果每个接口都对应一个Service那资源开销太大了。更合理的做法是使用Binder连接池模式一个Service管理多个Binder对象客户端通过一个统一的query接口传入业务标识取出对应的Binder再转换成对应的AIDL接口。我举个例子一个App里有下载模块和支付模块。下载需要跨进程AIDL支付也需要跨进程AIDL。如果不做连接池就要建两个Service、绑定两次。做成连接池则只需要一个Service它内部维护各个模块的Binder客户端根据binderCode查询即可。核心实现大致是这样的public class BinderPoolService extends Service { public static final int BINDER_CODE_MUSIC 1; public static final int BINDER_CODE_DOWNLOAD 2; private final IBinder binderPool new BinderPool.Stub() { Override public IBinder queryBinder(int binderCode) { if (binderCode BINDER_CODE_MUSIC) { return new MusicBinder(); } else if (binderCode BINDER_CODE_DOWNLOAD) { return new DownloadBinder(); } return null; } }; Override public IBinder onBind(Intent intent) { return binderPool; } }queryBinder的返回值是IBinder这其实是把Binder对象直接暴露给了客户端客户端再通过Stub.asInterface()转成具体接口。这样做的好处是Service的数量始终是一个而且每个业务模块的AIDL编写方式保持不变只是入口收敛了。实现连接池的时候客户端侧最好也做一个单例管理避免每次都去bindService。绑定一次Service之后用SparseArrayIBinder缓存查询到的Binder对象后续直接用缓存的接口调用。同时要处理Service连接断开的情况重连之后清空缓存重新绑定。4.2 客户端与服务端死亡回调DeathRecipient跨进程通信有一个很现实的问题进程会因为各种原因死掉。当服务端进程挂了客户端还持有一个无效的Binder引用这个时候调用任何方法都会抛DeadObjectException应用甚至可能直接崩溃。正确的做法是给Binder注册一个DeathRecipient当Binder连接的进程死亡时系统会回调binderDied()。在这个回调里你可以重新绑定服务或者通知UI刷新状态。代码示例private IBinder.DeathRecipient deathRecipient new IBinder.DeathRecipient() { Override public void binderDied() { if (musicService null) return; musicService.asBinder().unlinkToDeath(deathRecipient, 0); musicService null; // 重新绑定服务 bindService(new Intent(com.example.musicdemo.MusicService), connection, Context.BIND_AUTO_CREATE); } }; // 绑定成功后注册 Override public void onServiceConnected(ComponentName name, IBinder service) { musicService IMusicAidlInterface.Stub.asInterface(service); try { service.linkToDeath(deathRecipient, 0); musicService.registerCallback(callback); } catch (RemoteException e) { e.printStackTrace(); } }linkToDeath注册后一定要记得在合适的时机unlinkToDeath否则会造成内存泄露而且重复绑定后回调可能被触发多次。4.3 服务端权限校验AIDL接口默认情况下是开放的任何App只要知道Service的action和包名就能绑定并调用。如果不是希望对外暴露的服务一定要做权限校验。常用的校验方式有两种自定义权限在AndroidManifest.xml里定义权限服务端onBind时检查调用方的权限。但自定义权限比较麻烦需要客户端也声明相应权限部署成本较高。UID校验getCallingUid()拿到调用方进程的UID然后在服务端通过PackageManager查询该UID对应的包名跟白名单比对。这是最常用也最简单的做法。示例String[] allowedPackages {com.example.musicdemo}; Override public IBinder onBind(Intent intent) { int callingUid Binder.getCallingUid(); String callingPackage getPackageManager().getNameForUid(callingUid); for (String pkg : allowedPackages) { if (pkg.equals(callingPackage)) { return binder; } } return null; }getCallingUid()很关键它获取的是真正发起调用的那个进程的UID而不是当前Service进程自己的UID。如果不区分权限校验就是形同虚设。5. 实战中的高频问题与排查技巧5.1 跨进程传递大数据对象时的性能瓶颈AIDL底层的数据传递本质上是把对象序列化到Parcel里然后在内核空间和用户空间之间进行拷贝。传递几十KB的小对象问题不大但如果要传几MB的Bitmap或者长字符串那就会非常慢甚至导致TransactionTooLargeException。这个异常是AIDL开发中最让人头疼的问题之一。Binder事务的缓冲区在Android系统里是有上限的通常在1MB左右但实际安全范围更小。如果你往AIDL方法里塞了大对象比如一张高分辨率图片的byte数组调用时八成会崩在这里。我的经验是大数据的跨进程传输尽量不要走AIDL方法参数这条路。替代方案可以是先把大数据写到App私有目录或缓存文件里然后通过AIDL传递文件路径对端自己读取文件。用MemoryFile或者SharedMemory共享内存来传递大块数据但实现复杂一般场景用不上。5.2 多线程并发与Binder线程池的竞争问题服务端AIDL方法的调用并不是在主线程执行而是在Binder线程池里的多个线程中执行。这意味着如果你在AIDL方法里直接修改了共享的成员变量比如播放器的状态就可能出现线程安全问题。解决思路对共享状态加锁比如用synchronized或者ReentrantLock。状态尽量放在线程安全的容器里比如ConcurrentHashMap、CopyOnWriteArrayList。不要在AIDL接口方法里做耗时操作。虽然是Binder线程池但它有上限通常默认是16个线程方法阻塞时间过长会耗尽线程池导致后续调用排队。客户端这边也要注意调用同步的AIDL方法时主线程会被阻塞直到服务端返回。如果服务端方法耗时超过5秒还可能导致系统弹ANR。因此耗时接口在客户端也应该放到子线程去调用。5.3 自定义Parcelable类如何放到独立库模块工程复杂度上来之后很多团队会把数据模型抽到独立的Library模块AIDL接口也可能会分散在不同模块里。这时候需要注意AIDL文件必须与其引用的Parcelable类在编译期是可见的。如果Parcelable类在Library模块里AIDL文件要么也放在那个模块要么确认该模块以api方式被主模块依赖使得类对主模块的AIDL编译器可见。多个模块都定义了相同包名的AIDL接口会导致duplicate class冲突。建议每个模块用不同的包名后缀来隔离。5.4 常见问题速查表我在下面整理了一张速查表方便你在实际开发时快速定位问题。现象可能原因排查与解决编译报AIDL文件找不到类型包名不一致、import路径错误、Parcelable类不在编译路径检查.aidl里的package和import与类的实际包名逐一比对生成目录下没有Stub类AIDL文件没放在src/main/aidl移到正确目录Build - Rebuild运行时抛DeadObjectException服务端进程挂了客户端还持有Binder引用注册DeathRecipient死亡后重新绑定调用AIDL方法卡死服务端方法耗时太长Binder线程池被打满避免在服务端执行耗时操作或者换成oneway异步接口传大对象直接崩溃超过Binder事务大小上限换成文件路径传递或者用共享内存方案客户端与服务端数据的字段乱掉Parcelable的写入顺序和读取顺序不一致检查writeToParcel和Parcel构造函数里的读写顺序6. 从使用到原理AIDL生成代码的核心机制6.1 生成的Stub、Proxy、asInterface分别是什么很多读者用AIDL很熟练但没看过生成的代码长什么样这会导致面试或者排查复杂问题时心里没底。实际上.aidl文件编译后会生成一个同名的Java文件里面有三个核心组成部分Stub继承Binder的抽象类里面实现了onTransact()方法。服务端继承Stub并实现业务方法。onTransact()根据方法ID分发到对应的业务方法。Proxy内部类是客户端调用时的代理。Proxy持有IBinder引用每次调用方法时把参数写入Parcel然后调用mRemote.transact()等待服务端返回。asInterface()静态方法根据返回的IBinder是本地进程还是远程进程决定直接返回Stub对象还是Proxy对象。如果Binder对象在同一个进程直接强转返回省去代理这一层。理解这个机制后你就明白“进程内调用AIDL接口没走Binder传输但代码路径跟跨进程不一样”这句话的含义了。6.2 Binder驱动的数据拷贝过程Binder通信的底层机制相比传统IPC省了一次内存拷贝。传统IPC比如管道、Socket通常需要两次拷贝用户空间到内核空间再从内核空间到另一个用户空间。Binder利用内核里的映射机制只做一次拷贝。一次完整的数据流程是这样的客户端通过Proxy把方法调用数据写到Parcel然后调用transact()。系统调用binder_ioctl进入内核态Binder驱动把客户端进程里的数据拷贝到内核缓冲区。服务端进程通过mmap映射同一块物理内存直接读取到数据不需要再从这个缓冲区拷贝到自己的用户空间这里准确说是一侧拷贝另一侧通过映射读取。服务端处理完毕返回结果时同理。这也是为什么AIDL在Android里会成为跨进程通信首选的原因之一性能足够好。6.3 手动实现Binder接口而不写AIDLAIDL不是必须的。你可以手动定义类直接继承Binder然后重写onTransact()实现服务端客户端继承BinderProxy或者使用Binder的引用直接调用transact()。但现实是手动实现容易出错尤其是Parcel的读写顺序、事务码的管理、异步事务的支持都要自己维护。AIDL帮你把所有这些逻辑生成了代码等于官方把最笨重、最容易出错的部分替你写好了。除非是系统级开发或者特殊性能要求的场景否则我都建议直接使用AIDL。7. 关于AIDL的一些心得与实用建议这几年做Android越来越觉得AIDL其实是一门被低估的“基本功”。很多人觉得它只是面试题实际项目中都用LiveData、协程跨进程通信好像用不到了。但只要你做插件化、后台保活、多进程架构、系统级开发或者对接硬件厂商的SDKAIDL就是绕不开的底层能力。给各位几个建议第一写AIDL文件时一定要先想好方向标记。in、out、inout不只是语法标记它直接影响底层数据拷贝的次数和性能。默认能用in就用in别图省事全都写inout。第二回调接口务必处理好反注册。客户端销毁时如果忘了unregisterCallback服务端持有的回调对象会一直存在造成服务端内存泄漏严重时客户端进程都被服务端强引用无法释放。第三调试AIDL的时候多利用生成代码。遇到奇怪问题直接去看build/generated下的Stub类逐步推演数据的读写流程往往比瞎猜效率高得多。第四权限校验不要省。即便你的Service声明了exportedfalse在部分系统版本上通过隐式Intent仍可能被恶意应用探测。内部服务也建议在onBind里用UID校验一下调用方包名。AIDL这套东西表面上是写好一个.aidl文件编译然后Stub.asInterface()一调就完事了。但深层的工作量都在细节里比如序列化性能、并发安全、生命周期管理、权限控制。把这些问题都处理好了你的跨进程通信模块才算是真正稳了。