第3章 自定义Parcelable对象:实现Parcelable接口、编写CREATOR、在AIDL中引入

3.1 原理:Parcelable 在跨进程通信中的角色

Android 的 Binder 机制只能直接传递基本数据类型(int、long、String等)和 Parcelable 对象。自定义的复杂数据对象(如用户、订单、消息体)在跨进程传递前,必须序列化为扁平化的字节流(Parcel),接收方再反序列化还原对象。

Parcelable 是 Android 原生序列化协议,专门为高性能 IPC 设计,避免 Java 原生序列化的反射开销。与 Serializable 对比:

特性ParcelableSerializable
序列化机制手动写入/读取,无反射反射遍历字段,性能差
临时文件不需要生成大量临时对象
跨进程性能高(推荐 AIDL 使用)低(不推荐跨进程)
接口方法数3个(writeToParcel + CREATOR)0个(只加标记)
版本兼容性需手动控制 serialVersionUID可实现默认序列化
核心思想: 所有通过 AIDL 跨进程传递的非基本数据类型,都必须实现 android.os.Parcelable 接口,并在 AIDL 文件中显式声明。

3.2 实现流程总览

定义一个可跨进程传递的 User 对象,完整流程包含三步:

  1. 实体类实现 Parcelable: 实现 writeToParcel()、describeContents(),以及提供 private 构造方法从 Parcel 读取字段。
  2. 编写内部 CREATOR: 创建 public static final Parcelable.Creator<User> CREATOR 对象,实现 createFromParcel() 和 newArray()。
  3. 在 AIDL 中引入: 在同包名下创建同名 .aidl 文件声明 parcelable 类型,并 import 使用。
Parcelable 对象跨进程序列化流程 User 对象 (Java) writeToParcel() Parcel 字节流 Binder 驱动 CREATOR.createFromParcel() User 对象 (还原) 关键约束: • 写与读的顺序、类型必须完全一致 • 集合泛型需写入 ClassLoader • 对象图不能有循环引用

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
BadParcelableExceptionCREATOR 字段未定义或未 public static final添加正确 CREATOR 字段
字段读取错位write/read 顺序不一致按写入顺序逐一读取,保持类型一致
集合类型传递失败List 未指定泛型或者不兼容使用 ArrayList / 显式写入 ClassLoader
客户端抛出 SecurityExceptionAIDL 未声明引入在 .aidl 中添加 import 或直写 parcelable 声明

3.6 性能与稳定性优化

  1. 避免在 writeToParcel 中创建大对象: 序列化方法在主线程或 Binder 线程执行,不应分配大数组或执行 IO。
  2. 对象池复用: 对于高频传递的轻量对象(如坐标),可缓存并重用 Parcel 数据减少分配。
  3. Parcel 的最大 Payload: Binder 传输数据上线约 1MB(包括所有对象)。超大数据应拆分或改用文件描述符。
  4. Parcel 的 recycle(): 在 Service 端处理完 in 参数后,无需手动回收;Binder 框架自动管理。如果手动创建 Parcel,请调用 recycle()。
  5. 使用 @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)自动生成模板,可避免手动排序错误。