AIDL 实现 IPC 进程间通信

AIDL(Android Interface Definition Language)是 Android 提供的进程间通信(IPC)方案。本文从服务端搭建、客户端绑定,到双向回调与进程死亡监听,完整梳理 AIDL 的核心用法与底层数据流转。


一、服务端实现步骤

1. 定义业务接口 AIDL

在服务端工程中创建 IUser.aidl,声明客户端可调用的方法:获取/设置名称、获取用户对象,以及注册/注销回调监听器。

arduino 复制代码
// IUser.aidl
package com.qhsoft.foodsafety;
​
import com.qhsoft.foodsafety.User;
​
interface IUser {
    String getName();
    void setName(String name);
    User getUser();
    void registerListener(IUserListener listener);
    void unregisterListener(IUserListener listener);
}

2. 定义回调接口 AIDL

创建 IUserListener.aidl,用于服务端向客户端主动推送数据(回调机制)。

java 复制代码
// IUserListener.aidl
package com.qhsoft.foodsafety;
​
import com.qhsoft.foodsafety.User;
​
interface IUserListener {
    void onUser(in User user);
}

3. 自定义 Parcelable 数据类

AIDL 默认不支持自定义对象传输。若要在进程间传递 User 对象,必须实现 Parcelable 接口,并提供对应的 User.aidl 声明文件。

User.java

typescript 复制代码
package com.qhsoft.foodsafety;
​
import android.os.Parcel;
import android.os.Parcelable;
​
public class User implements Parcelable {
    private String name;
    private String age;
​
    public User(String name, String age) {
        this.name = name;
        this.age = age;
    }
​
    protected User(Parcel in) {
        name = in.readString();
        age = in.readString();
    }
​
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }
​
        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
​
    @Override
    public int describeContents() {
        return 0;
    }
​
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeString(age);
    }
​
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getAge() { return age; }
    public void setAge(String age) { this.age = age; }
}

User.aidl

ini 复制代码
package com.qhsoft.foodsafety;
​
parcelable User;

4. 实现 AIDL 接口(Stub)

创建一个类继承 IUser.Stub,完成接口方法的具体逻辑。

java 复制代码
private IBinder mBinder = new IMyImpl();
​
class IMyImpl extends IUser.Stub {
    @Override
    public String getName() throws RemoteException {
        return "Server Name";
    }
​
    @Override
    public void setName(String name) throws RemoteException {
        // 处理名称设置逻辑
    }
​
    @Override
    public User getUser() throws RemoteException {
        return new User("张三", "25");
    }
​
    @Override
    public void registerListener(IUserListener listener) throws RemoteException {
        mListenerList.register(listener);
    }
​
    @Override
    public void unregisterListener(IUserListener listener) throws RemoteException {
        mListenerList.unregister(listener);
    }
}

5. 创建远程 Service

ServiceonBind() 中返回 Binder 实例。这里通过子线程定时向所有已注册的客户端发送回调。

typescript 复制代码
public class MyService extends Service {
    private RemoteCallbackList<IUserListener> mListenerList = new RemoteCallbackList<>();
    private boolean running = true;
​
    @Override
    public void onCreate() {
        super.onCreate();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (running) {
                    SystemClock.sleep(2000);
                    int count = mListenerList.beginBroadcast();
                    for (int i = 0; i < count; i++) {
                        try {
                            mListenerList.getBroadcastItem(i).onUser(new User("Server", "20"));
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    mListenerList.finishBroadcast();
                }
            }
        }).start();
    }
​
    @Override
    public void onDestroy() {
        super.onDestroy();
        running = false;
    }
​
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

6. 在 AndroidManifest.xml 中注册

ini 复制代码
<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true" />

exported="true" 表示允许外部应用绑定该服务。


二、客户端实现步骤

1. 复制 AIDL 文件到客户端

先把服务端的三个 AIDL 文件(IUser.aidlIUserListener.aidlUser.aidl)以及 User.java 原样复制到客户端工程中。注意:包名必须与服务端保持一致。

2. 绑定远程服务

通过 ServiceConnection 建立与服务端的连接。

java 复制代码
private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mUser = IUser.Stub.asInterface(service);
        try {
            mUser.registerListener(mUserListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
​
    @Override
    public void onServiceDisconnected(ComponentName name) {
        // 服务异常断开(不会主动回调 unbind)
    }
};
​
// 执行绑定
Intent intent = new Intent();
intent.setComponent(new ComponentName(
    "com.qhsoft.foodsafety",
    "com.qhsoft.foodsafety.MyService"
));
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

3. 实现回调接口接收服务端推送

java 复制代码
private IUserListener.Stub mUserListener = new IUserListener.Stub() {
    @Override
    public void onUser(User user) throws RemoteException {
        Log.d("Client", "收到服务端回调: " + user.getName());
    }
};

4. 解绑与注销

在页面销毁时注销监听器并解绑服务,防止内存泄漏。

scss 复制代码
@Override
protected void onDestroy() {
    super.onDestroy();
    if (mUser != null) {
        try {
            mUser.unregisterListener(mUserListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    unbindService(mConnection);
}

三、RemoteCallbackList 的作用

在跨进程场景中,客户端传入的 IBinder 对象到了服务端会变成代理对象。如果直接用普通的 ArrayList 存回调,由于对象地址不同,unregister 时会找不到同一个实例,导致客户端无法正确注销。

RemoteCallbackList 就是专门解决这个问题的容器。 它内部使用 ArrayMap<IBinder, Callback> 存储回调,以 IBinder 作为唯一标识。更关键的是,它自带进程死亡监听:当某个客户端进程崩溃或被杀死时,RemoteCallbackList 会自动清理对应的回调,避免服务端继续向一个已死亡的进程发消息。

方法 说明
register(E callback) 注册一个回调
unregister(E callback) 注销一个回调
beginBroadcast() 开始遍历,返回当前回调总数
getBroadcastItem(int index) 获取指定位置的回调
finishBroadcast() 结束遍历,必须与 beginBroadcast 配对使用

四、监听服务端进程死亡(DeathRecipient)

服务端进程可能因崩溃或内存回收而被杀死。客户端可以通过 linkToDeath 注册死亡监听,在 binderDied() 中执行重连逻辑。

ini 复制代码
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (mUser == null) return;
        mUser.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mUser = null;
        // 重新发起绑定
        bindService();
    }
};
​
// 在 onServiceConnected 中建立死亡监听
mUser.asBinder().linkToDeath(mDeathRecipient, 0);

底层触发链路: 当服务端进程死亡时,Binder 驱动会通知客户端的 libbinder 层,最终回调到应用层的 DeathRecipient.binderDied()。这样客户端就能及时感知并自动恢复连接。


五、Binder 数据流转对比

Binder 驱动在跨进程传输时,数据会在内核态进行一次拷贝。下面通过表格对比 StringParcelable 的完整流转路径:

数据类型 Client Process Kernel Server Process
Java String Java String → Native Parcel 拷贝一次 Native Parcel → Java String
Parcel Java Parcel → Native Parcel 拷贝一次 Native Parcel → Java Parcel

可以看出,无论传输的是基本类型还是自定义 Parcelable 对象,数据都需要经过 Native Parcel → Kernel → Native Parcel 的过程。对于 String,Native 层负责与 Java String 互转;而对于自定义对象,则需要开发者手动实现 Parcelable 的序列化与反序列化逻辑。


六、总结

通过 AIDL 实现 IPC 通信,需要掌握以下几个核心要点:

  1. 接口定义 :使用 .aidl 文件声明跨进程方法,自定义对象必须实现 Parcelable
  2. 双向通信 :服务端通过 RemoteCallbackList 管理客户端回调,解决跨进程对象地址不一致的问题。
  3. 死亡监听 :利用 IBinder.DeathRecipient 监听服务端进程死亡,及时重连或降级。
  4. 生命周期 :在 onDestroy 中注销回调并解绑服务,防止内存泄漏和异常崩溃。

掌握这些机制,就能在 Android 中构建出稳定、可靠的跨进程通信方案。

相关推荐
AFinalStone2 小时前
Android 7系统无障碍服务(五)AccessibilityNodeInfo 的构建与查询
android·无障碍服务
今天要早睡_3 小时前
C++ 核心语法速过:命名空间、引用、函数重载与 nullptr 深度解析
android·java·c++
啦啦啦~~~2224 小时前
PC端+安卓端阅读器推荐!开源本地小说阅读器软件,
android·论文阅读·windows·开源软件·福昕阅读器
Crazy_MT4 小时前
Android Studio 运行 Flutter iOS 真机白屏,但 Xcode 和命令行正常的排查记录
flutter·android studio
我命由我123454 小时前
Android Jetpack Compose 开发问题:Unresolved reference ‘Icons‘.
android·java-ee·kotlin·android studio·android jetpack·android-studio·android runtime
爱笑鱼4 小时前
Android 系统启动机制(六):Zygote 创建 App 为什么走 Socket?哪些是源码事实,哪些是架构解释?
android·linux
AI2中文网5 小时前
安卓模拟器启动卡在进度条?两步解决硬件加速冲突
android
IT毕设实战小研5 小时前
基于大数据的DAX40成分股金融新闻情感趋势可视化分析
android·大数据·python·考研·金融·课程设计
Android-Flutter5 小时前
flutter async/await 详解
android·flutter