第3章 自定义Parcelable对象:实现Parcelable接口、编写CREATOR、在AIDL中引入
3.1 原理:Parcelable 在跨进程通信中的角色
Android 的 Binder 机制只能直接传递基本数据类型(int、long、String等)和 Parcelable 对象。自定义的复杂数据对象(如用户、订单、消息体)在跨进程传递前,必须序列化为扁平化的字节流(Parcel),接收方再反序列化还原对象。
Parcelable 是 Android 原生序列化协议,专门为高性能 IPC 设计,避免 Java 原生序列化的反射开销。与 Serializable 对比:
| 特性 | Parcelable | Serializable |
|---|---|---|
| 序列化机制 | 手动写入/读取,无反射 | 反射遍历字段,性能差 |
| 临时文件 | 不需要 | 生成大量临时对象 |
| 跨进程性能 | 高(推荐 AIDL 使用) | 低(不推荐跨进程) |
| 接口方法数 | 3个(writeToParcel + CREATOR) | 0个(只加标记) |
| 版本兼容性 | 需手动控制 serialVersionUID | 可实现默认序列化 |
核心思想: 所有通过 AIDL 跨进程传递的非基本数据类型,都必须实现 android.os.Parcelable 接口,并在 AIDL 文件中显式声明。
3.2 实现流程总览
定义一个可跨进程传递的 User 对象,完整流程包含三步:
- 实体类实现 Parcelable: 实现 writeToParcel()、describeContents(),以及提供 private 构造方法从 Parcel 读取字段。
- 编写内部 CREATOR: 创建 public static final Parcelable.Creator<User> CREATOR 对象,实现 createFromParcel() 和 newArray()。
- 在 AIDL 中引入: 在同包名下创建同名 .aidl 文件声明 parcelable 类型,并 import 使用。
3.3 实现步骤与代码
3.3.1 定义实体类 User 并实现 Parcelable
package com.example.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
private int userId;
private String userName;
private String email;
// 公开构造器用于创建对象
public User(int userId, String userName, String email) {
this.userId = userId;
this.userName = userName;
this.email = email;
}
// private 构造器:从 Parcel 中反序列化,必须与 writeToParcel 顺序一致
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
email = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeString(email);
}
@Override
public int describeContents() {
// 通常返回 0;如果包含文件描述符返回 CONTENTS_FILE_DESCRIPTOR
return 0;
}
// 3.3.2 内部 CREATOR
public static final Parcelable.Creator<User> CREATOR =
new Parcelable.Creator<User>() {
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
// Getter 方法与业务逻辑略
}
顺序一致性: writeToParcel 中先写 int userId,再写 String userName,最后写 String email。private 构造器中读取顺序必须完全一致,否则数据错乱或抛出异常。
3.3.2 编写 CREATOR 的细节
- CREATOR 必须为 public static final: Android 框架通过反射找到该字段,用于反序列化。
- createFromParcel(Parcel source): 调用 private 构造器,从 source 中读取字段。
- newArray(int size): 返回该类型的空数组,用于 Binder 传输数组时分配空间。
- 泛型匹配: 使用
Parcelable.Creator<User>而非裸 Creator。
3.3.3 在 AIDL 文件中引入自定义 Parcelable
创建与 User.java 同包、同名的 .aidl 声明文件:
// User.aidl (com/example/aidl/User.aidl)
package com.example.aidl;
// 标记为 parcelable 类型,不需要定义接口
parcelable User;
在业务 AIDL 接口文件中显式 import:
// IUserService.aidl
package com.example.aidl;
import com.example.aidl.User;
interface IUserService {
// 输入参数:User 对象跨进程传递
boolean addUser(in User user);
// 输出参数:返回 User 列表
List<User> getAllUsers();
}
路径必须匹配: AIDL 文件中 import 的包名必须与 Java 文件一致。如果放在不同包,需在 .aidl 中使用完整包路径声明
parcelable com.example.model.User;。
3.4 调试与验证
在 Service 端 onBind 返回的 Binder 实现中,通过日志确认序列化是否正常:
// Service 端 Binder 实现
@Override
public boolean addUser(User user) {
Log.d("UserService", "收到用户: id=" + user.getUserId()
+ ", name=" + user.getUserName());
// 验证对象不为 null,字段正确
return true;
}
调试常见检查点:
- 客户端调用 addUser 后,CheckLogcat 中 Service 端是否打印正确日志。
- 尝试传递空对象或空字符串,观察是否会触发 Parcel.readException 或空指针。
- 修改字段后反序列化,验证写/读顺序是否引发字段错位。
3.5 常见错误与解决方案
| 错误现象 | 根因 | 解决方案 |
|---|---|---|
| ClassNotFoundException | 客户端/服务端没有 User 类 | 将 User.class 放入公共 library module |
| BadParcelableException | CREATOR 字段未定义或未 public static final | 添加正确 CREATOR 字段 |
| 字段读取错位 | write/read 顺序不一致 | 按写入顺序逐一读取,保持类型一致 |
| 集合类型传递失败 | List 未指定泛型或者不兼容 | 使用 ArrayList / 显式写入 ClassLoader |
| 客户端抛出 SecurityException | AIDL 未声明引入 | 在 .aidl 中添加 import 或直写 parcelable 声明 |
3.6 性能与稳定性优化
- 避免在 writeToParcel 中创建大对象: 序列化方法在主线程或 Binder 线程执行,不应分配大数组或执行 IO。
- 对象池复用: 对于高频传递的轻量对象(如坐标),可缓存并重用 Parcel 数据减少分配。
- Parcel 的最大 Payload: Binder 传输数据上线约 1MB(包括所有对象)。超大数据应拆分或改用文件描述符。
- Parcel 的 recycle(): 在 Service 端处理完 in 参数后,无需手动回收;Binder 框架自动管理。如果手动创建 Parcel,请调用 recycle()。
- 使用 @Nullable 注解: 在 writeToParcel 中为可空字段写入 writeValue(null),读取时用 readValue() 并转型。
// 处理可空字段的写入/读取示例
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeValue(email); // 支持 null
}
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
email = (String) in.readValue(String.class.getClassLoader());
}
推荐做法: 使用 Android Studio 的 Parcelable 插件(Generate → Parcelable)自动生成模板,可避免手动排序错误。