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支持配置:

kotlin 复制代码
buildFeatures {
    aidl = true
}

在 Android Studio 中操作步骤:右键main→ New → AIDL → AIDL File 文件名输入ICalculator(注意首字母大写,符合接口命名规范),点击Finish

此时会生成一个默认的 AIDL 文件,我们需要修改其内容,定义加法和减法方法:

cpp 复制代码
// 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 类

创建一个服务类

修改代码如下:

cpp 复制代码
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() {
        // 实现加法方法
        @Override
        public int add(int num1, int num2) throws RemoteException {
            Log.d(TAG, "收到加法请求:" + num1 + " + " + num2);
            return num1 + num2; // 实际计算逻辑
        }

        // 实现减法方法
        @Override
        public int subtract(int num1, int num2) throws RemoteException {
            Log.d(TAG, "收到减法请求:" + num1 + " - " + num2);
            return num1 - num2; // 实际计算逻辑
        }
    };

    // 2. 当客户端绑定服务时,返回Binder对象(核心:让客户端获取通信入口)
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "客户端绑定服务");
        return mBinder; // 返回实现好的Binder对象
    }
}

4.2 在 Manifest 中注册 Service

Service 必须在AndroidManifest.xml中注册,否则客户端无法找到服务。添加以下代码到标签内:

cpp 复制代码
<service
    android: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)

客户端界面需要两个输入框(输入数字)、两个功能按钮(加法 / 减法)、两个服务控制按钮(绑定 / 解绑)和一个结果显示框。布局代码如下:

cpp 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android: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">
        <service
            android:name=".CalculatorService"
            android:process=":remote"
            android:enabled="true"
            android:exported="true"></service>

        <activity
            android: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 方法调用、用户交互等逻辑。代码如下:

cpp 复制代码
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() {
        // 服务绑定成功时调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 将IBinder对象转换为AIDL接口实例(关键步骤)
            mCalculator = ICalculator.Stub.asInterface(service);
            Toast.makeText(MainActivity.this, "服务绑定成功!", Toast.LENGTH_SHORT).show();
        }

        // 服务意外断开时调用(正常解绑不会触发)
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mCalculator = null; // 清空接口实例,避免空指针
            Toast.makeText(MainActivity.this, "服务意外断开!", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected 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销毁时解绑服务(避免内存泄漏)
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindRemoteService();
    }
}

六、 step 4:测试跨进程通信效果

七、 不同应用间的客户端服务器通信

服务器与客户端必须使用完全一致的 AIDL 文件,这是确保二者能正确解析通信数据、实现功能交互的核心前提。

基于上述案例简单修改,将服务端放在otherapp上,客户端在app上,使用相同的AIDL文件,实现效果和上述案例一致

注意:AIDL文件包名和接口名称必须一致

相关推荐
2501_916008894 小时前
Web 前端开发常用工具推荐与团队实践分享
android·前端·ios·小程序·uni-app·iphone·webview
我科绝伦(Huanhuan Zhou)5 小时前
MySQL一键升级脚本(5.7-8.0)
android·mysql·adb
怪兽20146 小时前
Android View, SurfaceView, GLSurfaceView 的区别
android·面试
龚礼鹏7 小时前
android 图像显示框架二——流程分析
android
消失的旧时光-19437 小时前
kmp需要技能
android·设计模式·kotlin
帅得不敢出门8 小时前
Linux服务器编译android报no space left on device导致失败的定位解决
android·linux·服务器
雨白8 小时前
协程间的通信管道 —— Kotlin Channel 详解
android·kotlin
TimeFine10 小时前
kotlin协程 容易被忽视的CompletableDeferred
android
czhc114007566311 小时前
Linux1023 mysql 修改密码等
android·mysql·adb
GOATLong12 小时前
MySQL内置函数
android·数据库·c++·vscode·mysql