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 通信,需要掌握以下几个核心要点:
- 接口定义 :使用
.aidl文件声明跨进程方法,自定义对象必须实现Parcelable。 - 双向通信 :服务端通过
RemoteCallbackList管理客户端回调,解决跨进程对象地址不一致的问题。 - 死亡监听 :利用
IBinder.DeathRecipient监听服务端进程死亡,及时重连或降级。 - 生命周期 :在
onDestroy中注销回调并解绑服务,防止内存泄漏和异常崩溃。
掌握这些机制,就能在 Android 中构建出稳定、可靠的跨进程通信方案。