美文网首页Android开发经验谈Android开发Android技术知识
序列化原理(二):从源码理解Parcelable

序列化原理(二):从源码理解Parcelable

作者: 珠穆朗玛小王子 | 来源:发表于2019-05-28 15:22 被阅读6次

    前言

    上一篇我们研究了一下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<TestBean> CREATOR = new Creator<TestBean>() {
            @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不等于null
    if ((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<TextView>(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<POOL_SIZE; i++) {
                    p = pool[i];
                    if (p != null) {
                        pool[i] = null;
                        if (DEBUG_RECYCLE) {
                            p.mStack = new RuntimeException();
                        }
                        p.mReadWriteHelper = ReadWriteHelper.DEFAULT;
                        return p;
                    }
                }
            }
            return new Parcel(0);
        }
    

    总结

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

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

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

    相关文章

      网友评论

        本文标题:序列化原理(二):从源码理解Parcelable

        本文链接:https://www.haomeiwen.com/subject/dzjdtctx.html