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

在 Service 的 onBind() 中返回 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.aidl、IUserListener.aidl、User.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 驱动在跨进程传输时,数据会在内核态进行一次拷贝。下面通过表格对比 String 与 Parcelable 的完整流转路径:

数据类型 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 中构建出稳定、可靠的跨进程通信方案。

相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui