什么是AIDL?

AIDL(Android Interface Definition Language)是Android系统专门为跨进程通信设计的一种接口定义语言。

为什么需要AIDL?

Android 中每个应用默认运行在独立的进程中(沙箱机制),进程间内存隔离。

当需要在不同进程间传递复杂数据或调用方法时(如:应用与系统服务、不同应用间、同一应用的多进程组件间),必须通过 IPC 机制。

AIDL 是 Android 提供的 IPC 解决方案之一,允许定义跨进程的接口,自动生成序列化/反序列化代码。

核心概念四要素

  1. AIDL 接口定义文件 (*.aidl)
  2. 服务端实现 (Service)
  3. 客户端调用 (Activity/Fragment 等)

AIDL 接口定义文件
作用:定义跨进程通信的“契约”(Contract)。
内容:声明服务端暴露给客户端调用的方法、参数和返回值类型。
规则:
支持基本数据类型(int, long, boolean 等)、String、CharSequence、List、Map。
自定义对象必须实现 Parcelable 接口,并单独定义 .aidl 文件声明。
可以嵌套定义其他 AIDL 接口作为参数(用于回调)。

服务端实现 (Service)
作用:具体实现 AIDL 接口定义的功能。
关键类:
Service:承载 IPC 服务的组件。
Stub 类:AIDL 编译器自动生成的抽象类,继承自 Binder 并实现了 AIDL 接口,服务端需要继承
Stub 并实现方法。
实现要点:
在 Service 的 onBind() 方法中返回 Stub 的实现类实例。
注意 线程安全:AIDL 方法可能被多个客户端同时调用,需考虑同步。
管理客户端注册的回调(监听器)时,使用 RemoteCallbackList 代替普通集合。

客户端调用 (Activity/Fragment 等)
作用:绑定服务并调用远程方法。
关键步骤:
绑定服务:通过 bindService() 连接服务端。
转换接口:将服务端返回的 IBinder 对象转换为 AIDL 接口类型(通过 Stub.asInterface())。
调用方法:像调用本地方法一样使用 AIDL 接口。
处理断开连接:实现 ServiceConnection 回调。
注意:
所有 AIDL 方法调用都是同步的(会阻塞客户端线程),应在子线程中调用或使用 oneway 异步调用。
客户端需要捕获 RemoteException(跨进程调用可能失败)。

第四要素(高级)
在实际开发中,还需考虑 数据序列化(Parcelable)和 回调机制:
自定义 Parcelable 对象:跨进程传递的对象必须实现 Parcelable 接口。
双向通信(回调):服务端主动通知客户端,需定义另一个 AIDL 回调接口,客户端实现后注册到服务端。

手把手实战

手把手实现两个应用之间的跨进程通信,广播相对简单,不做概述,主要来实战aidl
首先我们的愿景是aidl的服务端直接提供一个aar包给客户端调用,服务端只需要关注方法的调用就行,无需关注service的绑定,减低客户端对接的成本,其实无论我们做什么功能,给到别人用的,优先原则就是上层使用越简单越好!
现在直接上干货,如何定义一个aar包
在这里插入图片描述
上图在示例demo项目当中新建一个lib库(demoaar)
在demoaar当中新建了一个aidl文件DemoService,里面定义了相关方法给到第三方应用使用


plugins {
    id 'com.android.library'
}

android {
    namespace 'com.auto.sample'
    compileSdk 33

    defaultConfig {
        minSdk 26

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }

    android.libraryVariants.all{ variant ->
        variant.outputs.all {
            def fileName = "car.demo.sdk.v1.0.7.aar"
            outputFileName = fileName
        }
    }
}

dependencies {

}

上面是demoaar的build.gradle文件
其中打包出来的aar的名字就是car.demo.sdk.v1.0.7.aar,这个版本号可以根据自己的情况定,一般初始版本可以定为car.demo.sdk.v1.0.0.aar
这样构建这个lib的时候就会打包出来
在这里插入图片描述
在定义aidl的文件有个特别重要的点要注意 那就是包名

这个aidl 的包名是com.auto.sample
项目app模块的包名一定也是com.auto.sample 不然跨进程通信的是调不通的

此外我们还需要app模块下也要建立一个跟aarLib库一样的aidl文件
在这里插入图片描述
上面是app模块下的aidl
接下来我们就来看实际案例
假设现在有个需求,我需要拿到服务端app的一条对象数据,这个对象我在服务端定义成DemoData

public class DemoData implements Parcelable {
    public String name;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
    }

    public void readFromParcel(Parcel source) {
        this.name = source.readString();
    }

    public DemoData() {
    }

    protected DemoData(Parcel in) {
        this.name = in.readString();
    }

    public static final Creator<DemoData> CREATOR = new Creator<DemoData>() {
        @Override
        public DemoData createFromParcel(Parcel source) {
            return new DemoData(source);
        }

        @Override
        public DemoData[] newArray(int size) {
            return new DemoData[size];
        }
    };
}

想要DemoData能够正确下发到客户端,那这个数据一定要是序列化的。
对于这个DemoData 也需要定义好aidl
在这里插入图片描述
同样app模块一样需要定义
在这里插入图片描述
注意包名 也是需要各个模块一样保持一致
app模块:com.auto.data
demoaar模块:com.auto.data
data模块:com.auto.data
定义好DemoData好后,就需要上面DemoData的bean对象,这个DemoData包名也是com.auto.data
data模块看下面图
在这里插入图片描述
做好这些工作之后,在app模块来实现代理service实现aidl的方法

public class DemoService extends Service {
    private RemoteCallbackList<DemoServiceCallback> mCallbackList = new RemoteCallbackList<>();

    /**
     * 同步锁
     */
    private final ReentrantLock lock = new ReentrantLock();

    private final IBinder mDemoIBinder = new com.auto.sample.DemoService.Stub() {
        @Override
        public DemoData getDemoData() throws RemoteException {
            try {
                DemoData demoData = new DemoData();
                demoData.name = "这是服务端的数据";
                return demoData;
            } catch (Exception e) {
                Log.e("DemoService", "DemoService:getDemoData error : " + e.getMessage());
                return null;
            }
        }
    };


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mDemoIBinder;
    }
}

在 AndroidManifest里面进行注册这个service

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.auto.sample">

    <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.Common"
        tools:targetApi="31">

        <service
            android:name=".service.DemoService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.autolink.account.service" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

    </application>

</manifest>

从上面的代理的DemoService的类中看到我们实现了这个getDemoData这个方法

@Override
        public DemoData getDemoData() throws RemoteException {
            try {
                DemoData demoData = new DemoData();
                demoData.name = "这是服务端的数据";
                return demoData;
            } catch (Exception e) {
                Log.e("DemoService", "DemoService:getDemoData error : " + e.getMessage());
                return null;
            }
        }

这意味着客户端调用这个方法就会返回这个实例,其中这个name的字段就是这是服务端的数据。
现在还需要在aar当中写个manager类,提供直接调用这个方法能力,这样客户端不再关注服务端绑定的问题。

public class DemoServiceManager {
    private static final String TAG = "DemoServiceManager";
    private static final String TARGET_PACKAGE = "com.auto.sample"; // 服务端应用的包名
    private static final String TARGET_SERVICE_CLASS = "com.auto.sample.service.DemoService";

    private static volatile DemoServiceManager instance;
    private Context context;
    private DemoService demoService;
    private boolean isBinding = false;
    private ServiceConnection connection;
    private OnServiceConnectedListener listener;

    public interface OnServiceConnectedListener {
        void onConnected();

        void onDisconnected();
    }

    private DemoServiceManager() {
    }

    public static DemoServiceManager getInstance() {
        if (instance == null) {
            synchronized (DemoServiceManager.class) {
                if (instance == null) {
                    instance = new DemoServiceManager();
                }
            }
        }
        return instance;
    }

    /**
     * 初始化并绑定服务(建议在Application或首个Activity的onCreate调用)
     */
    public void init(Context context, OnServiceConnectedListener listener,DemoServiceCallback serviceCallback) {
        this.context = context.getApplicationContext();
        this.listener = listener;
        bindService();
    }

    private void bindService() {
        Intent intent = new Intent();
        intent.setClassName(TARGET_PACKAGE, TARGET_SERVICE_CLASS);
        // 如果服务端定义了权限,可以在此添加
        // intent.setPackage(TARGET_PACKAGE); // 可选,确保只匹配该包

        connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                demoService = DemoService.Stub.asInterface(service);
                isBinding = false;
                Log.d(TAG, "Service connected");
                if (listener != null) {
                    listener.onConnected();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                demoService = null;
                isBinding = false;
                Log.d(TAG, "Service disconnected");
                if (listener != null) {
                    listener.onDisconnected();
                }
            }
        };

        boolean success = context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
        if (success) {
            isBinding = true;
        } else {
            Log.e(TAG, "bindService failed, ensure service is registered and exported");
        }
    }

    /**
     * 调用isConnect方法
     * 注意:确保已连接,否则调用会忽略(可改为抛出异常或等待)
     */
    public void sendIsConnect(boolean status) {
        if (demoService != null) {
            try {
                demoService.isConnect(status);
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException", e);
            }
        } else {
            Log.w(TAG, "Service not connected, call init() first and wait for onConnected");
            // 可选:自动重新绑定并缓存调用(较复杂,这里不实现)
        }
    }

    public DemoData getDemoData() {
        if (demoService != null) {
            try {
                return demoService.getDemoData();
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException", e);
                return null;
            }
        } else {
            Log.w(TAG, "Service not connected, call init() first and wait for onConnected");
            // 可选:自动重新绑定并缓存调用(较复杂,这里不实现)
            return null;
        }
    }

    public void registerCallback(DemoServiceCallback serviceCallback) {
        if (demoService != null) {
            try {
                demoService.registerCallback(serviceCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException", e);
            }
        } else {
            Log.w(TAG, "register fail");
            // 可选:自动重新绑定并缓存调用(较复杂,这里不实现)
        }
    }

    public void sendData() {
        if (demoService != null) {
            try {
                demoService.sendData();
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException", e);
            }
        } else {
            Log.w(TAG, "sendData fail");
            // 可选:自动重新绑定并缓存调用(较复杂,这里不实现)
        }
    }

    /**
     * 释放资源,解绑服务
     */
    public void release() {
        if (demoService != null && context != null && connection != null) {
            context.unbindService(connection);
            demoService = null;
            isBinding = false;
            connection = null;
        }
    }
}

直接调用客户端集成aar,调用getDemoData这个方法,在客户端就可以拿到服务端的数据
想必看到这个manager提供的方法,包括设置,获取,回调一整套交互方法都在这个manager当中,这里主要挑了个getDemoData为例子,具体回调相关通信方式我会提供相关demo,大家直接把demo跑起来就可以实现通信了。

客户端调用方式:
在这里插入图片描述

在这里插入图片描述

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DemoServiceManager.getInstance().init(this, new DemoServiceManager.OnServiceConnectedListener() {
            @Override
            public void onConnected() {
                Log.d("dddd", "onConnected");
                DemoServiceManager.getInstance().registerCallback(callback);
            }

            @Override
            public void onDisconnected() {
                Log.d("dddd", "onDisconnected");
            }
        });
        initListener();
    }

    private final DemoServiceCallback callback = new DemoServiceCallback.Stub() {
        @Override
        public void resultCallBack(DemoData demoData) throws RemoteException {
            Log.d("dddd", "demoData" + demoData.name);
        }
    };

    private void initListener() {
        findViewById(R.id.rootLinear).setOnClickListener(v -> {
					DemoData demoData = DemoServiceManager.getInstance().getDemoData();
            String name = demoData.name;
            Log.d("dddd", name);
        });

    }
}

关注setOnClickListener点击事件,这样就能获取到服务端的数据了,测试的时候先把service的app运行起来,然后再运行client的app。

同样是跨进程通信,广播和AIDL为什么会选择AIDL通信?

广播 (Broadcast) AIDL
发布-订阅模式(一对多)发布-订阅模式(一对多) 客户端-服务端模式(一对一或一对多)
单向:发送方 → 接收方 双向:客户端 ↔ 服务端
弱,发送后不关心结果 强,可等待服务端响应
简单数据(Bundle 支持的基本类型和 Parcelable) 复杂对象、接口回调、自定义类型
Intent 有传输大小限制(约 1MB) 理论上无严格限制,但大对象会影响性能
一次性传递,接收方被动接收 可持续交互,多次方法调用
高,涉及系统广播队列、多应用唤醒 低,直接 Binder 内核调用
较慢(毫秒级) 很快(微秒级)
重,可能唤醒多个应用进程 精细的接口级权限控制
一次性,无法维持连接状态 可保持长连接,管理会话状态
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐