ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Android AIDL跨进程通信:从原理到工程搭建的完整指南

Android AIDL跨进程通信:从原理到工程搭建的完整指南 1. 项目概述为什么AIDL工程搭建是Android进阶的必修课如果你在Android开发中已经熟练掌握了Activity、Service、Broadcast这些基础组件但一听到“跨进程通信IPC”就觉得头大或者面对系统服务、多应用数据共享的需求时感到无从下手那么今天这个关于AIDL工程搭建的分享就是为你准备的。AIDL全称Android Interface Definition Language是Android系统提供的一种用于定义跨进程通信接口的语言。它听起来有点“高级”但本质上它就是一种让两个运行在不同进程中的组件比如你App里的一个服务和另一个独立App能够像调用本地方法一样进行对话的机制。我见过很多开发者包括几年前的我对AIDL抱有畏惧心理总觉得它复杂、难调试。但当你真正动手搭建过一个完整的AIDL工程后你会发现它的核心逻辑非常清晰。掌握AIDL意味着你打开了Android开发的一扇新大门你可以实现类似系统相册选择图片、音乐播放器控制、甚至是自己封装一个后台常驻的核心服务供多个应用调用。这不仅是面试中的高频考点更是实际项目中构建稳定、解耦架构的利器。本文我将以一个资深“踩坑者”的身份带你从零开始手把手搭建一个标准的AIDL工程并深入每一个细节告诉你为什么这么做以及如何避开那些新手必掉的“坑”。2. AIDL工程核心设计与思路拆解在动手写代码之前我们必须先想清楚AIDL工程的整体架构。一个典型的AIDL应用场景是“客户端-服务端”模型服务端运行在一个独立的进程提供某些能力比如计算服务、数据查询服务客户端则通过绑定服务来调用这些能力。2.1 跨进程通信的本质与AIDL的角色为什么需要AIDLAndroid中每个应用默认运行在自己的“沙盒”进程里内存不共享。直接传递一个Java对象引用是行不通的因为对方进程根本没有这个对象的内存地址。AIDL的作用就是定义一个双方都能理解的“合同”接口然后由Android系统底层Binder机制负责将客户端的调用请求打包序列化传输到服务端解包反序列化执行实际方法再将结果打包传回客户端。这个过程对开发者是透明的你只需要关心接口定义和方法调用。所以搭建AIDL工程的核心思路就三步定义接口.aidl文件、实现接口Service端、绑定并调用接口Client端。听起来简单但每一步都有大量细节决定成败。2.2 工程结构规划与模块化思考一个健壮的AIDL工程我强烈建议采用模块化结构尤其是在商业项目中。常见的做法是创建一个独立的Android Library模块例如命名为:service-api或:aidl-interface专门存放AIDL接口文件以及相关的数据模型Parcelable对象。这样做的好处非常明显接口统一管理服务端和客户端都依赖同一个接口模块确保接口定义绝对一致避免因拷贝失误导致版本不一致的灾难性问题。依赖清晰客户端只需要依赖接口模块而不需要知道服务端的具体实现符合面向接口编程的原则耦合度低。易于维护与升级当需要更新接口时只需修改接口模块然后分别更新服务端和客户端的依赖版本即可。在我们的示例工程中我们将采用这种结构一个App模块作为服务端:app另一个App模块作为客户端:client以及一个公共的Android Library模块:aidl-common来存放共享的AIDL定义。2.3 选择Service类型Started Service vs. Bound ServiceAIDL服务通常通过Service组件来承载。这里有一个关键选择使用Started Service还是Bound Service对于纯粹的AIDL远程调用场景我们几乎总是选择Bound Service绑定服务。Started Service通过startService()启动主要用于执行一个独立的后台任务不直接与组件交互。它不适合作为AIDL的载体。Bound Service通过bindService()绑定允许组件如Activity与之进行交互即IPC调用。当所有绑定方都解绑后服务通常会停止。这完美契合了AIDL客户端调用服务端功能的需求。在我们的服务端我们将创建一个继承自Service的类并在onBind()方法中返回实现了AIDL接口的Binder对象。3. 实操第一步创建与定义AIDL接口文件这是整个工程的基石也是最容易出错的第一步。我们将在公共模块:aidl-common中完成。3.1 创建AIDL目录与文件在:aidl-common模块的src/main目录下新建一个名为aidl的目录与java目录平级。这是Android Studio识别和编译AIDL文件的固定位置。在aidl目录下按照你的Java包名结构创建子目录。例如我们创建com/example/aidldemo。在该目录下新建一个文件命名为IMyAidlInterface.aidl。AIDL文件的后缀必须是.aidl。注意AIDL文件的包名由文件所在路径决定至关重要客户端和服务端在绑定服务时需要根据这个完整的包名来定位接口。务必确保所有模块中的路径一致。3.2 编写AIDL接口内容打开IMyAidlInterface.aidl文件开始定义接口。AIDL语法类似于Java接口但更简单只支持方法声明。// IMyAidlInterface.aidl package com.example.aidldemo; // 声明任何需要导入的非基本类型 // import com.example.aidldemo.DataModel; interface IMyAidlInterface { /** * 一个基础的测试方法返回一个字符串。 */ String getBasicInfo(); /** * 演示基本类型参数传递。 * param anInt 一个整数 * param aLong 一个长整数 * return 计算后的结果 */ int calculate(int anInt, long aLong); /** * 演示传递自定义Parcelable对象。 * 注意即使DataModel定义在同一个包也需要import。 * param data 输入的数据模型 * return 处理后的数据模型 */ // DataModel processData(in DataModel data); /** * 演示单向调用oneway。 * 客户端调用后立即返回不等待服务端执行完毕。 * 适用于不关心结果的通知型操作。 */ oneway void sendNotification(String msg); }关键点解析包名package必须与文件在aidl目录下的路径完全对应。导入import即使自定义的Parcelable类在同一个Java包下在AIDL文件中也必须使用import语句显式导入。这是AIDL编译器的一个要求。参数定向标签in, out, inout这是AIDL的难点和重点。在上面的注释方法中in DataModel data的in就是定向标签。in表示数据从客户端流向服务端。服务端收到的是客户端数据的副本修改这个参数不会影响客户端的原始对象。对于基本类型int, long, String等默认就是in且只能为in。out表示数据从服务端流向客户端。客户端传入的其实是一个“空壳”服务端负责填充这个对象然后将数据传回客户端。inout双向流通。客户端传入初始值服务端可以修改修改后的值会传回客户端。实操心得90%的情况下对于自定义对象使用in就足够了。除非你明确需要服务端填充或修改客户端传入的对象否则不要轻易使用out或inout它们会带来额外的序列化开销和复杂度。String比较特殊它虽然是对象但在AIDL中被特殊处理始终是in类型。oneway 关键字修饰一个void方法。表示这是一个异步调用客户端调用后会立即返回而不会阻塞等待服务端方法执行完成。适用于发送通知、日志等不要求即时结果的操作。非void方法不能使用oneway。3.3 定义自定义Parcelable数据模型跨进程传递自定义对象该对象必须实现Parcelable接口。我们同样在公共模块:aidl-common的java目录下创建这个类。在com.example.aidldemo包下创建DataModel.java。实现Parcelable接口。Android Studio可以自动生成大部分模板代码。// DataModel.java package com.example.aidldemo; import android.os.Parcel; import android.os.Parcelable; public class DataModel implements Parcelable { private int id; private String name; private float value; // 构造方法、getter、setter 省略... // ---------- Parcelable 实现部分 ---------- protected DataModel(Parcel in) { id in.readInt(); name in.readString(); value in.readFloat(); } Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(name); dest.writeFloat(value); } Override public int describeContents() { return 0; } public static final CreatorDataModel CREATOR new CreatorDataModel() { Override public DataModel createFromParcel(Parcel in) { return new DataModel(in); } Override public DataModel[] newArray(int size) { return new DataModel[size]; } }; }关键一步创建同名的.aidl声明文件。要让AIDL编译器认识这个Parcelable类你必须在aidl目录下相同的包路径里创建一个声明文件DataModel.aidl。// DataModel.aidl package com.example.aidldemo; parcelable DataModel;这个文件的内容就是一行告诉AIDL编译器com.example.aidldemo.DataModel是一个实现了Parcelable接口的类。踩坑记录忘记创建Parcelable类的.aidl声明文件是导致编译错误“找不到类”的最常见原因。务必记住每个跨进程传递的自定义Parcelable类都需要一个对应的.aidl声明文件。4. 服务端Server实现详解服务端的核心任务是实现我们定义的AIDL接口并将其通过一个Service暴露出去。4.1 创建远程Service在服务端App模块:app中创建一个MyAidlService继承自Service。// MyAidlService.java package com.example.server; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import com.example.aidldemo.IMyAidlInterface; public class MyAidlService extends Service { private static final String TAG MyAidlService; // 实现AIDL接口定义的方法 private final IMyAidlInterface.Stub mBinder new IMyAidlInterface.Stub() { Override public String getBasicInfo() throws RemoteException { Log.d(TAG, getBasicInfo called in server process.); return Hello from AIDL Server! PID: android.os.Process.myPid(); } Override public int calculate(int anInt, long aLong) throws RemoteException { Log.d(TAG, calculate called with: anInt , aLong); // 简单的计算示例 return (int) (anInt * 2 aLong); } Override public void sendNotification(String msg) throws RemoteException { // 因为是oneway这里即使耗时也不会阻塞客户端 Log.d(TAG, Received notification: msg); // 模拟一些处理 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(TAG, Notification processed: msg); } }; Override public IBinder onBind(Intent intent) { Log.d(TAG, Service is bound.); // 返回Stub对象的IBinder接口 return mBinder; } Override public void onCreate() { super.onCreate(); Log.d(TAG, Service onCreate.); } Override public void onDestroy() { super.onDestroy(); Log.d(TAG, Service onDestroy.); } }代码解析IMyAidlInterface.Stub这是Android SDK在编译AIDL文件后自动生成的抽象类在build/generated/aidl_source_output_dir下可以找到。它继承了Binder并实现了IMyAidlInterface接口。服务端需要继承这个Stub类并实现所有抽象方法。mBinder 我们创建了Stub的匿名实现对象它就是我们真正的服务实现。onBind(): 当客户端绑定服务时系统会调用此方法。我们必须返回mBinder因为Stub本身是Binder的子类而Binder实现了IBinder接口。4.2 在AndroidManifest.xml中声明Service为了让系统能识别和启动我们的服务必须在清单文件中声明并为其指定一个独立的进程可选但通常是跨进程通信的目的。!-- 在服务端App的AndroidManifest.xml中 -- service android:name.MyAidlService android:enabledtrue android:exportedtrue !-- 允许其他应用组件调用 -- android:process:remote !-- 指定服务运行在独立进程 -- intent-filter !-- 这个action是客户端绑定服务时用来识别的关键 -- action android:namecom.example.aidldemo.ACTION_BIND_AIDL_SERVICE / /intent-filter /service关键属性说明android:exportedtrue 必须设置为true否则其他应用包括我们自己的客户端App无法绑定此服务。android:process:remote 冒号开头表示这是一个私有进程名字为包名:remote。这强制Service运行在与主App不同的进程里这是我们演示跨进程通信的前提。如果不指定则默认运行在主进程。intent-filter 我们定义了一个自定义的Action。客户端将通过这个Action来定位我们的服务。Action的命名建议包含包名以避免冲突。5. 客户端Client绑定与调用客户端的目标是连接到服务端获取AIDL接口的代理对象然后进行远程调用。5.1 建立ServiceConnection与绑定服务在客户端的Activity或Fragment中我们需要实现ServiceConnection回调接口。// ClientActivity.java package com.example.client; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import com.example.aidldemo.IMyAidlInterface; public class ClientActivity extends AppCompatActivity { private static final String TAG ClientActivity; private IMyAidlInterface mAidlInterface; private boolean mIsServiceBound false; // 定义ServiceConnection private final ServiceConnection mServiceConnection new ServiceConnection() { Override public void onServiceConnected(ComponentName name, IBinder service) { // 绑定成功回调 Log.d(TAG, onServiceConnected: name); // 关键步骤将IBinder对象转换为AIDL接口 mAidlInterface IMyAidlInterface.Stub.asInterface(service); mIsServiceBound true; Toast.makeText(ClientActivity.this, AIDL服务连接成功, Toast.LENGTH_SHORT).show(); // 连接成功后可以尝试调用方法 performAidlCall(); } Override public void onServiceDisconnected(ComponentName name) { // 与服务端的连接意外断开时回调如服务端进程崩溃 Log.e(TAG, onServiceDisconnected: name); mIsServiceBound false; mAidlInterface null; Toast.makeText(ClientActivity.this, AIDL服务连接断开, Toast.LENGTH_SHORT).show(); } Override public void onBindingDied(ComponentName name) { // Android 8.0引入当承载服务的进程消失但可能重启时调用 super.onBindingDied(name); Log.e(TAG, onBindingDied: name); mIsServiceBound false; // 可以在这里尝试重新绑定 attemptToRebind(); } }; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_client); // 绑定服务 bindAidlService(); } private void bindAidlService() { Intent intent new Intent(); // 设置服务端的Action intent.setAction(com.example.aidldemo.ACTION_BIND_AIDL_SERVICE); // 从Android 5.0 (API 21) 开始必须显式设置包名 intent.setPackage(com.example.server); // 服务端App的包名 // 或者使用ComponentName // intent.setComponent(new ComponentName(com.example.server, com.example.server.MyAidlService)); boolean bindResult bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); if (bindResult) { Log.d(TAG, bindService initiated successfully.); } else { Log.e(TAG, bindService failed!); Toast.makeText(this, 绑定服务失败请检查服务端是否安装并运行, Toast.LENGTH_LONG).show(); } } private void performAidlCall() { if (!mIsServiceBound || mAidlInterface null) { Toast.makeText(this, 服务未连接, Toast.LENGTH_SHORT).show(); return; } new Thread(() - { try { // 调用远程方法 String info mAidlInterface.getBasicInfo(); Log.d(TAG, Received from server: info); runOnUiThread(() - Toast.makeText(ClientActivity.this, info, Toast.LENGTH_LONG).show()); int result mAidlInterface.calculate(5, 100L); Log.d(TAG, Calculate result: result); runOnUiThread(() - Toast.makeText(ClientActivity.this, 计算结果: result, Toast.LENGTH_LONG).show()); // 调用oneway方法 mAidlInterface.sendNotification(Client notification at System.currentTimeMillis()); Log.d(TAG, Oneway call sent, UI thread is not blocked.); } catch (RemoteException e) { Log.e(TAG, Remote call failed, e); runOnUiThread(() - Toast.makeText(ClientActivity.this, 远程调用异常: e.getMessage(), Toast.LENGTH_LONG).show()); } }).start(); } Override protected void onDestroy() { super.onDestroy(); if (mIsServiceBound) { unbindService(mServiceConnection); mIsServiceBound false; mAidlInterface null; Log.d(TAG, Service unbound.); } } }绑定过程深度解析构建Intent这是定位服务的关键。我们使用了隐式Intent通过Action来匹配。从Android 5.0开始使用隐式Intent绑定服务必须调用setPackage()或setComponent()否则会抛出IllegalArgumentException。这是为了增强系统安全性。setPackage(“com.example.server”)限定了只在指定包名的应用中寻找匹配的服务。调用bindService()第三个参数Context.BIND_AUTO_CREATE表示如果服务未运行则创建它。这是一个常用标志。onServiceConnected回调当绑定成功系统会回调此方法并传入一个IBinder对象service。这个service对象是系统提供的Binder代理。通过IMyAidlInterface.Stub.asInterface(service)我们将其转换成了我们熟悉的AIDL接口对象mAidlInterface。此后所有对mAidlInterface的方法调用都会被系统转发到服务端进程执行。异步调用注意我在performAidlCall()中将远程调用放在了子线程中。虽然AIDL调用本身是同步的会阻塞直到服务端返回但为了不阻塞UI线程通常建议在后台线程中进行。特别是如果服务端方法执行较慢UI线程会被卡住。资源释放在onDestroy()中解绑服务 (unbindService) 是良好的习惯避免资源泄漏。5.2 处理Oneway调用与线程模型在上面的客户端代码中我们调用了sendNotification这个oneway方法。你会在Logcat中观察到客户端调用这行代码后立即打印了“Oneway call sent, UI thread is not blocked.”而服务端在2秒后才打印处理完成的日志。这验证了oneway的异步特性。关于AIDL的线程模型你需要知道客户端从客户端线程发起的调用会在客户端的Binder线程池中的一个线程上被发送出去。客户端的回调如果需要也发生在Binder线程池的线程上而不是你发起调用的原始线程。这就是为什么我们通常需要在回调中切换回UI线程来更新界面。服务端服务端Stub中的方法实现默认是在服务端的Binder线程池中被调用的。这意味着你的服务端方法实现必须是线程安全的因为可能会有多个客户端同时调用。如果服务端方法需要执行长时间操作你应该在方法内部启动自己的工作线程而不是阻塞Binder线程。6. 编译、运行与调试全流程6.1 项目依赖配置确保模块间的依赖关系正确。公共模块 (:aidl-common) 不依赖其他模块。服务端模块 (:app) 在build.gradle的dependencies中添加implementation project(‘:aidl-common’)。客户端模块 (:client) 同样添加implementation project(‘:aidl-common’)。同步Gradle项目。同步成功后你会在各个模块的build/generated/aidl_source_output_dir/目录下看到Android Studio自动生成的Java文件如IMyAidlInterface.java。这些文件包含了Stub、Proxy等辅助类是AIDL通信的桥梁但通常我们不需要直接修改它们。6.2 运行顺序与观察日志先运行服务端App将服务端模块 (:app) 部署到设备或模拟器上。即使退出App界面只要Service还在运行在独立进程:remote中服务就可用。再运行客户端App将客户端模块 (:client) 部署到同一台设备。启动客户端Activity它会尝试绑定服务。观察Logcat使用Android Studio的Logcat工具分别过滤服务端进程MyAidlService和客户端进程ClientActivity的日志。你应该能看到绑定成功、方法调用、结果返回等一系列日志。验证跨进程查看服务端getBasicInfo方法返回的PID和客户端通过android.os.Process.myPid()获取的PID它们应该是不同的这证明了跨进程通信正在发生。6.3 调试技巧调试跨进程通信比调试单进程复杂但依然可行。调试服务端在Android Studio中你可以像调试普通应用一样在服务端代码MyAidlService中的方法里打上断点。然后以调试模式Debug ‘app’运行服务端App。当客户端发起调用时调试器会命中服务端的断点。关键是要确保调试器附加到了服务端进程:remote。有时你可能需要在“Attach to Process”中选择正确的进程。日志是生命线在AIDL开发中在关键节点如onBind、onServiceConnected、每个方法入口添加详细的Log输出是排查问题最有效的手段。检查权限如果服务端和客户端是不同的应用且服务设置了android:exported”true”从Android 11开始可能需要考虑包可见性等权限问题但本例中我们使用setPackage明确指定通常没问题。7. 进阶议题与深度优化一个基础的AIDL工程跑通后我们可以考虑更实际、更复杂的情况。7.1 接口版本兼容与更新策略随着业务发展AIDL接口可能需要增加新方法或修改现有方法。AIDL接口的变更必须向后兼容否则已安装的旧客户端将无法绑定到新服务。安全更新策略只增不减永远不要删除已有的方法或修改其签名方法名、参数类型、返回类型。只能添加新的方法。默认实现在服务端的Stub实现中为新添加的方法提供合理的默认实现或抛出RemoteException以兼容尚未更新的旧客户端。版本号管理可以在接口中定义一个getVersion()方法返回当前接口版本号。客户端绑定后可以先查询版本再决定调用哪些功能。Fallback机制对于关键功能考虑在旧方法中保留核心逻辑新方法提供增强功能。7.2 传递复杂数据与List/MapAIDL支持传递List和Map但其中的元素类型必须是AIDL支持的类型基本类型、String、实现了Parcelable或AIDL接口的对象等。List和Map本身在AIDL中是用in方式传递的但为了灵活性通常使用List的子类ArrayList和Map的子类HashMap。// 在AIDL接口中定义 import java.util.List; import java.util.Map; void processList(in ListString stringList); void processMap(in MapInteger, DataModel dataMap);在Java实现中参数类型就是ListString和MapInteger, DataModel。7.3 死亡通知与连接重试客户端需要感知服务端进程是否意外死亡。我们已经实现了ServiceConnection.onServiceDisconnected回调它会在连接意外断开时被调用。但更健壮的做法是使用IBinder.linkToDeath注册一个死亡通知。// 在客户端 onServiceConnected 中 private IBinder.DeathRecipient mDeathRecipient new IBinder.DeathRecipient() { Override public void binderDied() { Log.e(TAG, Remote service died.); // 清除引用 mAidlInterface null; mIsServiceBound false; // 在主线程尝试重新绑定 runOnUiThread(() - { Toast.makeText(ClientActivity.this, 服务已终止尝试重连..., Toast.LENGTH_SHORT).show(); attemptToRebind(); }); } }; Override public void onServiceConnected(ComponentName name, IBinder service) { mAidlInterface IMyAidlInterface.Stub.asInterface(service); mIsServiceBound true; try { // 注册死亡通知 service.linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { e.printStackTrace(); } // ... } Override protected void onDestroy() { if (mAidlInterface ! null) { // 解绑时移除死亡通知 IBinder binder ((IMyAidlInterface.Stub) mAidlInterface).asBinder(); binder.unlinkToDeath(mDeathRecipient, 0); } super.onDestroy(); }7.4 性能与安全考量性能跨进程调用是有开销的序列化/反序列化、内核调用。避免频繁进行大量数据的IPC调用。对于需要频繁交换的数据考虑使用ContentProvider、SharedPreferences跨进程模式或MemoryFile等共享机制。安全权限校验在服务端的onBind()方法中可以通过Binder.getCallingUid()和Binder.getCallingPid()获取调用方的身份进行权限校验。自定义权限在AndroidManifest.xml中定义自定义权限并在服务声明中通过android:permission属性来要求客户端必须拥有该权限才能绑定。不暴露敏感服务除非必要不要将android:exported设置为true。如果只需要应用内通信可以使用Intent显式指定组件名并将exported设为false。8. 常见问题排查与实战避坑指南根据我多年的经验以下是新手搭建AIDL工程时最容易遇到的“坑”及其解决方案。问题现象可能原因排查步骤与解决方案编译错误cannot find symbol class ...1. 自定义Parcelable类缺少对应的.aidl声明文件。2. AIDL文件中import语句路径错误或缺失。3. 模块依赖未正确配置。1. 检查aidl目录下是否有YourClass.aidl声明文件。2. 检查AIDL文件中的import语句包名和类名是否正确。3. 检查服务端和客户端模块的build.gradle是否都正确依赖了公共接口模块。客户端绑定失败bindService返回false1. 服务端App未安装或未运行。2. Intent的Action或包名设置错误。3. 服务端Service的android:exported”false”。4. Android 5.0未使用setPackage()或setComponent()。1. 确保服务端App已安装并至少运行过一次以注册服务。2. 核对客户端Intent的Action和服务端Manifest中的Filter是否完全一致包名是否正确。3. 检查服务端Service声明确保exported”true”。4. 确保在隐式Intent中调用了intent.setPackage(“服务端包名”)。ClassCastException: android.os.BinderProxy cannot be cast to ...客户端和服务端使用的AIDL接口类不一致来自不同的编译版本或模块。确保客户端和服务端都依赖同一个接口模块:aidl-common。绝对不要将AIDL文件分别拷贝到两个模块中。调用远程方法时抛出TransactionTooLargeException通过Binder传递的数据量超过了限制通常约为1MB。避免在单个IPC调用中传递过大的数据如巨大的Bitmap或列表。考虑分页传输、使用文件共享或ContentProvider。服务端方法被调用但客户端收不到返回值或卡住1. 服务端方法抛出了未捕获的异常。2. 客户端在UI线程进行同步IPC调用服务端方法耗时过长导致ANR。1. 检查服务端方法实现用try-catch包裹确保不会抛出RuntimeException到Binder层。2.务必在客户端使用子线程进行AIDL同步调用或使用oneway异步调用。oneway方法调用后服务端似乎没执行oneway方法内部抛出了异常。由于是异步的异常不会传递给客户端但可能导致服务端方法执行中断。在服务端的oneway方法内部也要做好异常捕获和日志记录否则问题难以察觉。服务端进程被杀后客户端状态不同步客户端未正确处理连接断开onServiceDisconnected或死亡通知linkToDeath。实现ServiceConnection.onServiceDisconnected和IBinder.linkToDeath回调并在其中更新UI状态和尝试重连逻辑。最后的个人体会AIDL工程的搭建初看步骤繁多但本质上是一个“定义合同-实现合同-使用合同”的标准化过程。最大的障碍往往来自于对细节的疏忽.aidl文件的位置、Parcelable的声明、Intent的精确匹配、以及线程模型的理解。我建议在真正投入项目前严格按照本文的步骤亲手搭建并运行这个示例工程。当你看到来自另一个进程的字符串成功显示在客户端Toast上时你对Android系统组件间通信的理解会上一个坚实的台阶。之后再根据实际业务需求去探索权限控制、异步回调、大数据传输这些进阶话题就会从容得多。记住清晰的日志和模块化的接口设计是维护一个复杂AIDL系统的两大法宝。
返回列表