Android序列化经典解析(二)-Parcelable

提起Parcelable,我们第一反应是它是Android下进行进程间通信数据传递的最好方式,很多博客是这样写的:“它是基于内存的序列化和反序列化,而且不会像Serializable一样使用磁盘并通过反射来进行序列化和反序列化”,暂且我们认为这个结论是正确的,那我们先来了解一个Parcel和Parcelable吧。

由于Android中每个程序都是运行在独立的进程中,每个进程的内存空间和内存地址都是相互不可见的,所以我们如果直接将一个对象的引用由进程A传递给进程B,进程B根据这个引用的地址是获取不到正确的对象的。你可能还有一个疑问,如果我只是在应用内部进行跳转,为什么还需要将对象序列化呢?因为在Android中,每一个组件都设计成开放可复用的模块,我们通过一个Intent启动一个组件并不能保证启动的组件和当前组件在一个进程。

什么是Parcel

简单来说,Parcel提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象,下图是这个过程的模型。

parcel.png

Parcel在Java层和C++层都有定义,Parcel中对于不同类型的数据处理是不一样的,它有两个成员:

uint8_t* mData; //用来存储序列化流数据,可以把它理解成共享内存
size_t* mObjects;  //用来存储IBinder和FileDescriptor

为什么要区别对待呢?我们可以暂时这么理解,对于IBinder来说,它存在的意义就是要现实跨进程调用,所以我就是需要在Parcel中传递一个真实的引用,这个引用能够操作到发起进程的对象。实际上在Parcel的C++层也有一个单独的结构来描述将要写入的Binder对象:flat_binder_object。而对于文件描述符来说,本来就是kernel层的东西,所以在不同的进程中它们可以表示同一个对象,所以也无需严格的序列化。

我们这里暂不讨论讨论IBinder和FileDescriptor序列化的问题,我们只关心基本类型数据和Parcelable数据是如何在被写入的。

Parcelable示例

还是按照惯例,给一个例子:

public class SimpleActivity extends Activity {
  private static final String TAG = "SimpleActivity";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Parcel parcel = Parcel.obtain();
    Pojo pojo = new Pojo("TEST");

    //写入Parcel
    parcel.writeParcelable(pojo,0);
    //Parcel读写共用一个位置计数,这里一定要重置一下当前的位置
    parcel.setDataPosition(0);
    //读取Parcel
    Pojo pojo1 = parcel.readParcelable(Pojo.class.getClassLoader());
    Log.d(TAG,pojo1.desc);
}
}
class Pojo implements Parcelable {
protected String desc;

private Pojo(Parcel in) {
    desc = in.readString();
}

public Pojo(String desc) {
    this.desc = desc;
}

public static final Creator<Pojo> CREATOR = new Creator<Pojo>() {
    @Override
    public Pojo createFromParcel(Parcel in) {
        return new Pojo(in);
    }

    @Override
    public Pojo[] newArray(int size) {
        return new Pojo[size];
    }
};

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(desc);
}
}

可以看到,写一个实现Parcelable接口的类还是比较麻烦的,和Serailable相比,我们需要在writeToParcel中按序写入各个域到流中,同样,在createFromParcel中我们需要自己返回一个Pojo对象。

仔细看看这个设计,你会不自觉地想这个东西怎么这么眼熟...是的,你没有看错,是不是和[Java序列化的代理模式][1]很像?CREATOR的createFromParcel通过new的方式从流中读取一个对象,这比Java反序列化通过神奇魔法创建对象的黑科技强多了吧?

如何读写

下面我们看一眼Parcel读写的代码,注意,我这里只看Java层,你可以这么想象一下,比如我调用Java层的Parcel.writeInt(),会调用到C++层Parcel的writeInt(),然后在其中直接进行内存的复制操作memcpy,将数据复制到共享内存中。

Parcel.writeParcelable

public final void writeParcelable(Parcelable p, int parcelableFlags) {
    //先向流中写入p的ClassName
    writeParcelableCreator(p);
    //然后直接调用p的writeToParcel,这个方法也就是我们自己重写的
    p.writeToParcel(this, parcelableFlags);
}

这里需要注意的是,每一个Parcelable对象在写入流之前,都会在前面首先写入这个对象的ClassName,主要是方便后面读的时候,能够知道是哪个类,感觉这个地方还是做的比较粗糙,在Serializable中对应一个序列化类的信息刻画比这简单的一个类名要靠谱得多,所以官方文档上才会说,如果你想进行持久化存储,那么Parcelable不是你的菜,道理很简单,这里不会有任何版本的概念,只要你的类名不改,旧版本的数据就可以被新版本的class进行反序列化,然而class里面的域可能已经完全不一样了。

Parcel.readParcelable

首先会调用到readParcelableCreator,通过反射读取我们类中定义的CREATOR:

public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
 //首先把类名读取出来
    String name = readString();

    Parcelable.Creator<?> creator;
    //mCreators做了一下缓存,如果之前某个classloader把一个parcelable的Creator获取过
    //那么就不需要通过反射去查找了
    
    synchronized (mCreators) {
        HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
        if (map == null) {
            map = new HashMap<>();
            mCreators.put(loader, map);
        }
        creator = map.get(name);
        if (creator == null) {
            try {
                // If loader == null, explicitly emulate Class.forName(String) "caller
                // classloader" behavior.
                ClassLoader parcelableClassLoader =
                        (loader == null ? getClass().getClassLoader() : loader);
                //加载我们自己实现Parcelable接口的类
                Class<?> parcelableClass = Class.forName(name, false,
                        parcelableClassLoader);
                
                Field f = parcelableClass.getField("CREATOR");
                Class<?> creatorType = f.getType();
                creator = (Parcelable.Creator<?>) f.get(null);
            }
            catch (Exception e) {
                    //catch exception
            }
            if (creator == null) {
                throw new BadParcelableException("Parcelable protocol requires a "
                        + "non-null Parcelable.Creator object called "
                        + "CREATOR on class " + name);
            }

            map.put(name, creator);
        }
    }

    return creator;
}

然后直接调用CREATOR.createFromParcel(parcel)

public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
    Parcelable.Creator<?> creator = readParcelableCreator(loader);
    if (creator == null) {
        return null;
    }
    if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
      Parcelable.ClassLoaderCreator<?> classLoaderCreator =
          (Parcelable.ClassLoaderCreator<?>) creator;
      return (T) classLoaderCreator.createFromParcel(this, loader);
    }
    return (T) creator.createFromParcel(this);
}


作者:楚云之南
來源:简书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值