第2章 基本数据类型传递
2.1 原理
AIDL(Android Interface Definition Language)底层依赖Binder驱动完成进程间数据搬运。基本数据类型(int、long、boolean、float、double、String、CharSequence)是Parcelable的原子单元,Binder内核会直接将这些值复制到目标进程的堆栈中,不涉及复杂对象图遍历或引用计数。传递效率最高,且无需额外序列化注解。
映射关系:AIDL中的基本类型直接对应Java/C++原生类型,底层通过Parcel.writeInt()、writeLong()等系列API写入共享内存块,接收端按顺序读取。
String和CharSequence本质是字符数组封装,但在Binder传输时被归为“基本类型”的优化路径——内核会特判字符串长度并直接拷贝字符数据,避免二次序列化。
性能要点:基本类型传递没有堆内存分配(短字符串除外),延迟通常小于0.1ms,适合高频控制信号。
2.2 实现流程
从定义接口到进程间调用的标准步骤:
- 定义 .aidl 接口文件:声明需要传递的方法及基本类型参数。
- 实现 Stub 类:在服务端继承自动生成的
Stub并重写方法。 - 暴露 Binder:在
onBind()中返回 Stub 实例。 - 客户端绑定服务:通过
bindService()获取远程接口代理。 - 发起调用:直接调用代理方法,传递基本类型数据。
关键注意:所有基本类型方法声明前无需
in/out/inout 定向修饰符——Binder 会自动按值传递。
2.3 代码实现
2.3.1 AIDL 接口定义
创建 IBaseDataType.aidl:
// IBaseDataType.aidl
package com.example.aidlbasic;
interface IBaseDataType {
int calculate(int a, long b, float c, double d, boolean flag);
String echoString(String input, CharSequence cs);
void noReturn(int value);
}
编译器生成 IBaseDataType.java 接口和 Stub 内部类。
2.3.2 服务端实现
// BasicService.java
public class BasicService extends Service {
private final IBaseDataType.Stub binder = new IBaseDataType.Stub() {
@Override
public int calculate(int a, long b, float c, double d, boolean flag) {
return flag ? (int)(a + b + c + d) : (int)(a * b);
}
@Override
public String echoString(String input, CharSequence cs) {
return "Echo: " + input + " | " + cs.toString();
}
@Override
public void noReturn(int value) {
android.util.Log.d("BasicService", "Received value: " + value);
}
};
@Override
public IBinder onBind(Intent intent) { return binder; }
}
2.3.3 客户端调用
// MainActivity.java
private IBaseDataType remoteService;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
remoteService = IBaseDataType.Stub.asInterface(service);
if (remoteService != null) {
try {
int result = remoteService.calculate(10, 100L, 3.14f, 2.718, true);
String echo = remoteService.echoString("Hello", "World");
remoteService.noReturn(42);
} catch (RemoteException e) { e.printStackTrace(); }
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
remoteService = null;
}
};
// 在合适时机调用:bindService(intent, connection, Context.BIND_AUTO_CREATE);
2.4 调试
启用 Binder 事务日志可观察基本类型传递的数据包大小:
adb shell su -- sh -c "echo 1 > /d/binder/failsafe_threshold"
adb shell dumpsys binder > /sdcard/binder_dump.txt
# 搜索 "transaction" 和 "data_size" 看基本类型调用
Logcat 中日志关键字:Binder: transaction、Parcel。通过 android.util.Log 在 Stub 方法内打印入参。
调试陷阱:基本类型传参在
RemoteException 发生时不可继续使用代理对象,必须重新绑定。
2.5 部署与稳定性
| 类型 | 跨进程安全约束 | 常见问题 |
|---|---|---|
| int/long | 无溢出检查,需业务层控制范围 | 大数导致服务端计算溢出 |
| float/double | 大小端自动转换(Android一律小端) | 精度损失(float转double) |
| boolean | 底层存储为 int(0/1) | 误传 null 或非 0/1 值 |
| String/CharSequence | 最大长度受 Binder 缓冲区限制(1MB) | 超长字符串抛出 TransactionTooLargeException |
稳定性建议:基本类型业务中避免 String 长度超过 1KB(高频调用时),否则应考虑本地缓存或分块传输。
2.6 常见错误
- TransactionTooLargeException:超长字符串或传入大量数据(基本类型本身小,但多次调用会导致堆满)。
- RemoteException: dead object:服务端进程异常退出,客户端旧代理不可用。
- ClassCastException(极少):
asInterface()传入非预期的 IBinder 对象。 - String 为 null 时未判空:Stub 实现中对 null 参数没有防御性检查,导致 NPE。
最佳实践:在 .aidl 方法声明后添加注释说明参数范围;服务端 Stub 实现内始终对 String/CharSequence 做 null 判断。
2.7 性能与优化
基本数据类型传递是 AIDL 中最轻量的操作,但仍需关注:
- 单次调用开销:约 10–30 μs(包含 Binder 上下文切换),因此高频控制(如每帧更新 UI 坐标)建议合并成一次传递一个包含多个基本类型的结构体(自定义 Parcelable)。
- 内存拷贝:每次调用会拷贝所有参数到内核缓冲区,大量参数可改用
Bundle或List<>批量传输。 - 避免不必要的 float→double 转换:接口参数类型应与实际数据类型严格匹配,否则底层会隐式转换消耗 CPU。
- 异步调用:对于不关心返回结果的 void 方法,使用
oneway关键字提升吞吐量(一对一请求不再等待)。