前言

上一篇我们研究了一下Serializable,虽然它使用方便,但是效率和兼容性上确实还存在一些问题。为此Android提供了特有的Parcelable机制,按照官方说法,速度是Serializable的十倍左右。

正文

public class TestBean implements Parcelable {    private int x;    private int y;    public int getX() {        return x;    }    public void setX(int x) {        this.x = x;    }    public int getY() {        return y;    }    public void setY(int y) {        this.y = y;    }    public static final Parcelable.Creator CREATOR = new Creator() {        @Override        public TestBean[] newArray(int size) {            return new TestBean[size];        }        @Override        public TestBean createFromParcel(Parcel in) {            TestBean bean = new TestBean();            bean.setX(in.readInt());            bean.setY(in.readInt());            return bean;        }    };    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(x);        dest.writeInt(y);    }}

上面是一个Parcelable的示例,对比Serializable,可以看出有以下优劣:

  • Serializable用法简单,Parcelable实现要相对复杂
  • Serializable只能序列化所有属性,Parcelable可以在write和read方法选择性的序列化
  • Serializable具有可继承性,Parcelable虽然也具有,但是仍然需要完善实现,因为CREATOR是静态的。

通过用法我们已经对两者的区别有了一些认识,接下来我们看看Parcelable的源码工作原理。 提到跨进程通信,AIDL是常见的解决方案之一。

如果你对AIDL的使用还不够了解,可以先阅读我之前学过的博客:
AIDL使用学习(一):基础使用学习
AIDL使用学习(二):跨进程回调以及RemoteCallbackList
AIDL使用学习(三):源码深入分析

我们就看生成的文件ITestInterface:

public interface ITestInterface extends android.os.IInterface {        public static abstract class Stub extends android.os.Binder implements com.lzp.aidlstudy.ITestInterface {        private static final java.lang.String DESCRIPTOR = "com.lzp.aidlstudy.ITestInterface";        ...               @Override        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {            java.lang.String descriptor = DESCRIPTOR;            switch (code) {                case INTERFACE_TRANSACTION: {                    reply.writeString(descriptor);                    return true;                }                case TRANSACTION_getCalculateResult: {                    data.enforceInterface(descriptor);                    com.lzp.aidlstudy.bean.TestBean _arg0;                    if ((0 != data.readInt())) {                        _arg0 = com.lzp.aidlstudy.bean.TestBean.CREATOR.createFromParcel(data);                    } else {                        _arg0 = null;                    }                    int _result = this.getCalculateResult(_arg0);                    reply.writeNoException();                    reply.writeInt(_result);                    return true;                }                default: {                    return super.onTransact(code, data, reply, flags);                }            }        }        private static class Proxy implements com.lzp.aidlstudy.ITestInterface {            ...            @Override            public int getCalculateResult(com.lzp.aidlstudy.bean.TestBean bean) throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                int _result;                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    if ((bean != null)) {                        _data.writeInt(1);                        bean.writeToParcel(_data, 0);                    } else {                        _data.writeInt(0);                    }                    mRemote.transact(Stub.TRANSACTION_getCalculateResult, _data, _reply, 0);                    _reply.readException();                    _result = _reply.readInt();                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }        }}

我们简化了部分与分析无关的代码,从上面的代码我们已经看到了Parcelable熟悉的身影,首先我们得到的代理对象Proxy,通过Proxy对象调用getCalculateResult()方法:

// 如果参数TestBean不等于nullif ((bean != null)) {         _data.writeInt(1);         // 调用writeToParcel把要序列化的数据写入到某处        bean.writeToParcel(_data, 0);} else {// 参数等于null,就写个0       _data.writeInt(0);}

我们先不管序列化的数据到底写到哪去了,反正是保存起来了,接着调用:

 mRemote.transact(Stub.TRANSACTION_getCalculateResult, _data, _reply, 0);

mRemote就是Stub类的实例,通过Binder的源码,在transact调用了onTransact:

 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {        ......        // 如果参数不是空的,通过反序列化得到参数        if ((0 != data.readInt())) {                _arg0 = com.lzp.aidlstudy.bean.TestBean.CREATOR.createFromParcel(data);        } else {                _arg0 = null;        }        // 本地计算结果,再次返回        int _result = this.getCalculateResult(_arg0);        reply.writeNoException();        reply.writeInt(_result);        return true;        }......     }}

过程就是这么简单,现在我们唯一的困惑是:

这些序列化的数据到底写到哪去了呢

Parcelable只是一个接口,具体的序列化原理是借助Parcel,我们刚才也看到这样的代码:

 android.os.Parcel _data = android.os.Parcel.obtain(); bean.writeToParcel(_data, 0);

Parcel的write和read方法全是native方法:

// 截取的部分native方法    @FastNative    private static native void nativeWriteInt(long nativePtr, int val);    @FastNative    private static native void nativeWriteLong(long nativePtr, long val);    @FastNative    private static native void nativeWriteFloat(long nativePtr, float val);    @FastNative    private static native void nativeWriteDouble(long nativePtr, double val);    static native void nativeWriteString(long nativePtr, String val);    private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);    private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);

一般来说使用JNI的速度肯定是比使用Java方法要快,因为他绕过了Java层的api,不过这个并不是速度的保证,真正的优势是避免的大量的反射操作,减少了临时变量的创建,提高了序列化的效率。

根据Parcel的注释,我们了解了数据的去向:

有一个专门负责IBinder传输数据的容器。
(一个可以进程共享的内存区)
Parcel可以把数据压入,另一端的Parcel可以把数据取走
(通过实现我们可以推断出保存数据的是一个先进先出的堆栈)
Parcel仅仅是为了实现高性能的IPC通信,在其他的持久化方案在并不推荐。

到这里Parcelable的序列化机制就已经分析结束了,如果我们非要把Parcelable保存到本地怎么办呢?

我这里给出一个简单的示例:

private fun writeTestData() {        Thread(Runnable {            val student = Student("zhangsan", 18)            val parcel = Parcel.obtain()            // 因为Parcel内部有缓存复用            // 设置数据的位置指针为头部            parcel.setDataPosition(0)            student.writeToParcel(parcel, 0)            val fos = FileOutputStream(path)            fos.write(parcel.marshall())            fos.flush()            fos.close()            parcel.recycle()        }).start()    }    private fun readTestData() {        Thread(Runnable {            val fis = FileInputStream(path)            val data = fis.readBytes()            val parcel = Parcel.obtain()            // 因为Parcel内部有缓存复用            // 设置数据的位置指针为头部            parcel.unmarshall(data, 0, data.size)            parcel.setDataPosition(0)            val student = Student(parcel)parcel.recycle()            runOnUiThread {                findViewById(R.id.text).text = student.toString()            }        }).start()    }

刚才提到了Parcel内部有缓存,推荐使用Parcel.obtain()来获取一个可用的Parcel对象,类似的还有Message.obtain():

/**     * Retrieve a new Parcel object from the pool.     */    public static Parcel obtain() {        final Parcel[] pool = sOwnedPool;        synchronized (pool) {            Parcel p;            for (int i=0; i

总结

最后对Parcelable的序列化做一个总结:

  • Parcelable的序列化需要借助Parcel。
  • Parcel通过JNI把序列化数据写入到进程的共享内存中,或从进程共享内存中读数据。
  • Parcel推荐使用Parcel.obtain()方法获取可用实例。
  • 与Serializable相比,Parcelable避免了大量反射操作,在效率上有很大提升。
  • Parcelable仅仅是IPC的高效实现方案,其他场景慎用。

ok,这一篇就结束了,有什么问题欢迎大家留言指正。

更多相关文章

  1. 一句话锁定MySQL数据占用元凶
  2. Android序列化学习
  3. Android(安卓)AOP之AspectJ入门
  4. audio_route 分析
  5. Android(安卓)DataBinding使用详解(一)
  6. Android(安卓)MediaPlayer指定文件位置播放
  7. android 【点击输入框调出输入法前的】输入框获取焦点和输入法的
  8. Android(安卓)2.3 Midia Framework
  9. Android(安卓)数据传递-通过剪切板传递数据

随机推荐

  1. Android开源项目之二---工具库篇
  2. android 判断字符串是否为空的最优方法
  3. Android(安卓)Volley 完全解析(三),定制自
  4. Android(安卓)ART invoke 代码生成
  5. Android---3---布局之LinearLayout
  6. MPAndroidChart开源图表库(一)之饼状图
  7. java读取文本文件内容2
  8. Android解析HTML+android爬虫框架jsoup
  9. Android各种屏幕的分布率以及自适应各种
  10. Android(安卓)Binder-涉及到Linux kernel