Android AIDL通信案例
一、什么是 AIDL?为什么需要它?
1.1 概念解析
AIDL(Android Interface Definition Language,Android 接口定义语言)是 Android 系统中专门用于实现跨进程通信(IPC) 的机制。简单来说,它就像两个不同应用(或进程)之间的 “通信协议”,定义了双方能理解的 “对话规则”—— 哪些方法可以调用、参数格式是什么、返回值类型如何。
在 Android 中,每个应用默认运行在独立的进程中,进程间的内存是相互隔离的(即 “沙箱机制”)。这意味着一个应用无法直接访问另一个应用的内存数据,而 AIDL 的作用就是打破这种隔离,让不同进程能安全、高效地交换数据和调用方法。
1.2 适用场景
当你遇到以下需求时,就需要用到 AIDL:
- 应用 A 需要调用应用 B 提供的功能(如支付、地图定位等)
- 单个应用需要拆分多个进程(如后台服务单独运行,避免主进程内存溢出)
- 多应用协作完成某个复杂功能(如多个应用共享同一套数据处理逻辑)
二、案例整体架构
我们将创建一个包含 “服务端” 和 “客户端” 的计算器应用:
服务端:提供add(加法)和subtract(减法)的计算逻辑,通过 Service 暴露给外部调用
客户端:提供用户界面(输入数字、点击按钮),通过 AIDL 绑定服务端,调用远程计算方法并展示结果
三、 step 1:创建 AIDL 接口文件
AIDL 文件是跨进程通信的 “核心协议”,必须在服务端和客户端保持完全一致(包名、文件名、方法定义),否则会通信失败。
3.1 新建 AIDL 文件
在build.gradle.kts
文件中添加AIDL支持配置:
buildFeatures {aidl = true
}
在 Android Studio 中操作步骤:右键main→ New → AIDL → AIDL File 文件名输入ICalculator(注意首字母大写,符合接口命名规范),点击Finish
此时会生成一个默认的 AIDL 文件,我们需要修改其内容,定义加法和减法方法:
// ICalculator.aidl
package com.example.aidldemo; // 包名必须与项目包名一致// 声明接口,定义跨进程可调用的方法
interface ICalculator {// 加法方法:接收两个int参数,返回计算结果int add(int num1, int num2);// 减法方法:接收两个int参数,返回计算结果int subtract(int num1, int num2);
}
3.2 编译生成 Java 文件
AIDL 文件本身不能直接使用,需要让 Android Studio 自动生成对应的 Java 代码(包含 Binder 通信逻辑)。
操作步骤:点击顶部菜单栏的 Build编译
编译完成后,会在app/build/generated/aidl_source_output_dir/
目录下生成ICalculator.java文件(无需手动修改)
四、 step 2:实现服务端 Service
服务端的核心是创建一个Service,并在其中实现 AIDL 接口定义的方法,通过Binder对象将服务暴露给客户端。
4.1 创建 CalculatorService 类
创建一个服务类
修改代码如下:
package com.example.aidldemo;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;public class CalculatorService extends Service {private static final String TAG = "CalculatorService";// 1. 创建AIDL接口的实现类(Stub是ICalculator.java中自动生成的内部类)private final ICalculator.Stub mBinder = new ICalculator.Stub() {// 实现加法方法@Overridepublic int add(int num1, int num2) throws RemoteException {Log.d(TAG, "收到加法请求:" + num1 + " + " + num2);return num1 + num2; // 实际计算逻辑}// 实现减法方法@Overridepublic int subtract(int num1, int num2) throws RemoteException {Log.d(TAG, "收到减法请求:" + num1 + " - " + num2);return num1 - num2; // 实际计算逻辑}};// 2. 当客户端绑定服务时,返回Binder对象(核心:让客户端获取通信入口)@Overridepublic IBinder onBind(Intent intent) {Log.d(TAG, "客户端绑定服务");return mBinder; // 返回实现好的Binder对象}
}
4.2 在 Manifest 中注册 Service
Service 必须在AndroidManifest.xml中注册,否则客户端无法找到服务。添加以下代码到标签内:
<serviceandroid:name=".CalculatorService"android:exported="true" <!-- 允许外部应用访问该服务 -->android:process=":remote"> <!-- 可选:让服务运行在独立进程(跨进程的关键) --><!-- 配置Intent过滤器,方便客户端通过Action绑定 --><intent-filter><action android:name="com.example.aidldemo.CALCULATOR_SERVICE" /><category android:name="android.intent.category.DEFAULT" /></intent-filter>
</service>
android:exported="true":必须设置为true,否则外部应用无法绑定该服务
android:process=":remote":可选配置,让服务运行在名为 “remote” 的独立进程(即使是同一应用,也能模拟跨进程场景)
五、 step 3:实现客户端界面与逻辑
客户端需要完成三个核心操作:绑定服务端 Service → 调用 AIDL 方法 → 展示计算结果。
5.1 创建布局文件(activity_main.xml)
客户端界面需要两个输入框(输入数字)、两个功能按钮(加法 / 减法)、两个服务控制按钮(绑定 / 解绑)和一个结果显示框。布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.AIDLDemo"><serviceandroid:name=".CalculatorService"android:process=":remote"android:enabled="true"android:exported="true"></service><activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>
5.2 实现 MainActivity 逻辑
MainActivity 是客户端的核心,需要处理服务绑定、AIDL 方法调用、用户交互等逻辑。代码如下:
package com.example.aidldemo;import androidx.appcompat.app.AppCompatActivity;import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;public class MainActivity extends AppCompatActivity {// 1. 声明核心变量private ICalculator mCalculator; // AIDL接口实例(用于调用远程方法)private EditText etNum1, etNum2; // 数字输入框private TextView tvResult; // 结果显示框// 2. 服务连接对象(监听服务绑定/断开状态)private ServiceConnection mConnection = new ServiceConnection() {// 服务绑定成功时调用@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 将IBinder对象转换为AIDL接口实例(关键步骤)mCalculator = ICalculator.Stub.asInterface(service);Toast.makeText(MainActivity.this, "服务绑定成功!", Toast.LENGTH_SHORT).show();}// 服务意外断开时调用(正常解绑不会触发)@Overridepublic void onServiceDisconnected(ComponentName name) {mCalculator = null; // 清空接口实例,避免空指针Toast.makeText(MainActivity.this, "服务意外断开!", Toast.LENGTH_SHORT).show();}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 3. 初始化控件initViews();// 4. 绑定按钮点击事件initClickEvents();}// 初始化控件private void initViews() {etNum1 = findViewById(R.id.et_num1);etNum2 = findViewById(R.id.et_num2);tvResult = findViewById(R.id.tv_result);}// 初始化按钮点击事件private void initClickEvents() {// 绑定服务按钮findViewById(R.id.btn_bind).setOnClickListener(v -> bindRemoteService());// 解绑服务按钮findViewById(R.id.btn_unbind).setOnClickListener(v -> unbindRemoteService());// 加法计算按钮findViewById(R.id.btn_add).setOnClickListener(v -> calculateAdd());// 减法计算按钮findViewById(R.id.btn_subtract).setOnClickListener(v -> calculateSubtract());}// 5. 绑定远程服务private void bindRemoteService() {// 方式1:通过Action绑定(需在Service的Manifest中配置intent-filter)Intent intent = new Intent();intent.setAction("com.example.aidldemo.CALCULATOR_SERVICE");intent.setPackage("com.example.aidldemo"); // 必须指定服务端包名(Android 11+强制要求)// 方式2:通过组件名绑定(需知道服务端的包名和Service类名)// Intent intent = new Intent();// intent.setComponent(new ComponentName(// "com.example.aidldemo", // 服务端包名// "com.example.aidldemo.CalculatorService" // 服务端Service类名// ));// 绑定服务:BIND_AUTO_CREATE表示服务未启动时自动创建bindService(intent, mConnection, BIND_AUTO_CREATE);}// 6. 解绑远程服务private void unbindRemoteService() {if (mCalculator != null) {unbindService(mConnection); // 解绑服务mCalculator = null; // 清空接口实例Toast.makeText(this, "服务已解绑!", Toast.LENGTH_SHORT).show();} else {Toast.makeText(this, "未绑定服务,无需解绑!", Toast.LENGTH_SHORT).show();}}// 7. 调用加法方法private void calculateAdd() {// 先判断服务是否已绑定if (mCalculator == null) {Toast.makeText(this, "请先绑定服务!", Toast.LENGTH_SHORT).show();return;}try {// 获取输入框的数字int num1 = Integer.parseInt(etNum1.getText().toString().trim());int num2 = Integer.parseInt(etNum2.getText().toString().trim());// 调用远程服务的add方法(跨进程调用)int result = mCalculator.add(num1, num2);// 显示结果tvResult.setText("计算结果:" + num1 + " + " + num2 + " = " + result);} catch (NumberFormatException e) {// 处理输入非数字的异常Toast.makeText(this, "请输入有效的数字!", Toast.LENGTH_SHORT).show();} catch (RemoteException e) {// 处理跨进程调用失败的异常(如服务崩溃、网络断开等)e.printStackTrace();Toast.makeText(this, "调用加法服务失败!", Toast.LENGTH_SHORT).show();}}// 8. 调用减法方法private void calculateSubtract() {if (mCalculator == null) {Toast.makeText(this, "请先绑定服务!", Toast.LENGTH_SHORT).show();return;}try {int num1 = Integer.parseInt(etNum1.getText().toString().trim());int num2 = Integer.parseInt(etNum2.getText().toString().trim());// 调用远程服务的subtract方法int result = mCalculator.subtract(num1, num2);// 显示结果tvResult.setText("计算结果:" + num1 + " - " + num2 + " = " + result);} catch (NumberFormatException e) {Toast.makeText(this, "请输入有效的数字!", Toast.LENGTH_SHORT).show();} catch (RemoteException e) {e.printStackTrace();Toast.makeText(this, "调用减法服务失败!", Toast.LENGTH_SHORT).show();}}// 9. Activity销毁时解绑服务(避免内存泄漏)@Overrideprotected void onDestroy() {super.onDestroy();unbindRemoteService();}
}
六、 step 4:测试跨进程通信效果
七、 不同应用间的客户端服务器通信
服务器与客户端必须使用完全一致的 AIDL 文件,这是确保二者能正确解析通信数据、实现功能交互的核心前提。
基于上述案例简单修改,将服务端放在otherapp上,客户端在app上,使用相同的AIDL文件,实现效果和上述案例一致
注意:AIDL文件包名和接口名称必须一致