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_916013748 小时前
iOS 26 系统流畅度检测 从视觉特效到帧率稳定的实战策略
android·macos·ios·小程序·uni-app·cocoa·iphone
秋月的私语8 小时前
如何快速将当前的c#工程发布成单文件
android·java·c#
、BeYourself13 小时前
Android 常见界面布局详解
android
weixin_4111918413 小时前
安卓Handler+Messenger实现跨应用通讯
android
咕噜企业签名分发-淼淼13 小时前
App防止恶意截屏功能的方法:iOS、Android和鸿蒙系统的实现方案
android·ios·harmonyos
Digitally14 小时前
如何将文件从电脑传输到安卓设备
android
游戏开发爱好者818 小时前
iOS 26 崩溃日志深度解读,获取方式、系统变动、定位策略
android·macos·ios·小程序·uni-app·cocoa·iphone
一直向钱18 小时前
android 基于okhttp 封装一个websocket管理模块,方便开发和使用
android·websocket·okhttp
小趴菜822719 小时前
安卓人机验证View
android·java·前端