Android IPC | 序列化详解

1,119 阅读14分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

前言

这里是IPC的系列,为什么突然要说序列化呢?首先我们需要知道什么是序列化,以及各种序列化的区别。

序列化就是把对象转成易于传输和保存的过程,注意这里的关键字是传输和保存,而对于传输我们又分为俩种:

  • 一种是网络传输,比如在后台把一个Book对象经过序列化,经过互联网传输到手机,手机进行展示时,这时就需要反序列化才可以使用和展示。

这种情况下,我们一般采用序列化的方式是把对象转成Json格式,这种序列化的优点是序列化后的内容不包含序列化之前类的信息,而且数据量较小

  • 另一种是对于Android开发来说,我们页面跳转需要使用Intent,可以用Intent传递数据,比如从A Activity跳转到B Activity需要传递一个Book对象,这时可以通过Serializable或者Parcelable进行序列化,来进行传输

还有一点就是保存,对于保存来说,我们既可以序列化成Json格式,也可以通过Serializable序列化为字节格式,这个不是本章的重点。

本章的重点我们聚焦于使用Parcelable和Serializable实现的序列化方式,而对于使用fastJson或者Gson等库的其他序列化方式不是本章说明点。

正文

我们平时在APP中启动Activity时,经常有如下代码:

findViewById<Button>(R.id.jump1).setOnClickListener {
    startActivity(Intent(this, SecondActivity::class.java).apply {
        //put一个Parcelable对象
        putExtra("key",aTest)
        //put一个Serializable对象
        putExtra("key1",BTest())
    })
}

在这里我们可以使用Intent传递一个对象,这个对象必须是序列化过的,要不是通过Java的Serializable接口,要不是Android独有的Parcelable接口,我们就来自己探究一下这俩种序列化方式的实现和区别。

Serializable接口

Serializable是Java提供的一个序列化接口,它是一个空接口,对对象提供标准的序列化和反序列化操作。使用起来非常方便,只需要实现该接口,同时在类中声明一个如下的标识符:

class BTest : Serializable {

    companion object{
        private const val serialVersionUID = 1L
    }

    var test: String? = null
}

其中serialVersionUID是可以省略的。

如何序列化

还是回到文章最开始的地方,序列化的作用是为了方便传输和保存,而Serializable提供的序列化就是推荐用来持久化保存,这里就需要用到ObjectOutputStream和ObjectInputStream,常见的使用代码如下:

val bTest = BTest().apply {
    test = "okk"
}
val file = File(Environment.getExternalStorageDirectory().path, "cache1.txt")
if (!file.exists()){
    file.createNewFile()
}
//序列化
val out = ObjectOutputStream(FileOutputStream(file))
out.writeObject(bTest)
out.close()
//反序列化
val inStream = ObjectInputStream(FileInputStream(file))
val b = inStream.readObject() as BTest

上述代码就是使用Serializable接口进行序列化持久化保存和反序列的过程,我们简单来看一下它的writeObject方法:

public final void writeObject(Object obj) throws IOException {
    ...
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        ...
        throw ex;
    }
}
private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
           ...
           //使用反射
            Object orig = obj;
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;

            ...
            //按类型进行读写
            if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
            // END Android-changed: Make Class and ObjectStreamClass replaceable.
            } else if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                //会判断是否是Serializable类型
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }
private void writeOrdinaryObject(Object obj,
                                 ObjectStreamClass desc,
                                 boolean unshared)
    throws IOException
{
    ...
    try {
        desc.checkSerialize();
        //进行IO操作
        bout.writeByte(TC_OBJECT);
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        if (desc.isExternalizable() && !desc.isProxy()) {
            writeExternalData((Externalizable) obj);
        } else {
            writeSerialData(obj, desc);
        }
    } finally {
        if (extendedDebugInfo) {
            debugInfoStack.pop();
        }
    }
}

通过上面代码我们可以知道如下实现细节:

  1. 会使用反射,在writeObject方法中会通过反射来拿到类的成员信息。众所周知,反射是一个比较耗时的操作。
  2. 会固定判断几种类型,比如String,Enum等,其中最主要的就是会判断Serializable类型,这也就说明了要使用ObjectOutputStream进行序列化对象时,该对象必须是Serializable类型
  3. 在真正序列化时,把元素保存为字节时,需要用到IO操作,而IO操作也是一种比较费时的操作。

从上面3点分析,我们也看出了使用Serializable的弊端,即效率较差。

serialVersionUID用处

在前面序列化类时,使用了serialVersionUID这个字段,它是一长串数字,一般情况下,我们都会选择忽略,但是这个字段是有意义的,我们要正确理解其含义,可以防止一些问题出现。

serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才可以正常被反序列化

它的工作机制是这样的:序列化时,系统会把当前类的serialVersionUID写入序列化文件中,当反序列化时,会检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致则说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功序列化;否则就说明当前类和序列化类相比发生了变换,比如成员变量的数量、类型可能发生改变,这时就无法反序列化。

一般来说,我们应该手动去指定serialVersionUID的值,比如1L这种,或者根据当前类的结构自动去生成一个hash值。这样可以防止一种崩溃,就是序列化的类当增加或者减少了成员变量,如果不指定serialVersionUID的值,Serializable会在序列化和反序列化时通过hash对类计算出值,这时就无法正常反序列化,如果指定了serialVersionUID值,还是可以正常反序列化的。当然如果类的结构发生了毁灭性变化,比如成员变量的类型发生了变化,这样即使指定了serialVersionUID也无法还原。

这里再注意俩点:

  1. 静态成员变量属于类不属于对象,所以不会参与序列化过程
  2. 用transient关键字标记的成员变量不会参与序列化过程

会保存类的信息

这个是啥意思呢? 不论是Serializable还是Parcelable进行序列化的结果都会保存原始类的信息,这个特性也就导致了这俩种序列化方式不适合用在网络传输中,因为谁也无法保证在服务器和客户端定义的类是一样的。

比如下面2个类:

class BTest : Serializable {
    var test: String? = null
}
class BTest1 : Serializable {
    var test: String? = null
}

他们的成员变量名以及类型是一样的,但是类名不一样,当我们把BTest对象进行序列化后,用BTest1类型进行反序列化时会报错,如果我们用Gson给序列化为Json字符串就不会出现这种情况,所以不同的序列化方式要灵活选择使用。

Parcelable

Parcelable作为Android独有的序列化接口,不知道你是否有没有想过,为什么Android要单独设计出这种序列化方式,毕竟已经有了Java的Serializable接口,而且还可以把类序列化为json字符串来保存和传递也非常方便,这些问题我们接下来一一分析。

使用

还是先来看看如何使用Parcelable,还是实现Parcelable接口,但是实现的内容比较多,代码如下:

public class Book implements Parcelable {
    public int BookId;
    public String BookName;

    public Book(int bookId,String bookName){
        this.BookId = bookId;
        this.BookName = bookName;
    }

    protected Book(Parcel in) {
        BookId = in.readInt();
        BookName = in.readString();
    }

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

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

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

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

和使用Serializable的简洁不同,实现了Parcelable接口多了很多代码,不过这些代码都是可以自动生成,无需手动写。

序列化不外乎就是序列化和反序列化,所以上述代码通过writeToParcel实现了序列化,而通过一个静态内部类Creator实现了反序列化,从实现细节来看序列化和反序列化就是往Parcel中写数据和读数据的过程。

下面是几个方法梳理:

方法功能
writeToParcel将当前对象写入序列化结构中,即把对象写入Parcel中
createFromParcel从序列化的对象中创建原始对象,即从Parcel中读取数据
newArray创建指定长度的原始对象数组,可以不用在意
Book(Parcel in)从parcel对象中创建原始对象
describeContents返回当前对象的内容描述,默认为0

好了,到现在我们会发现Parcelable的核心就是Parcel了,搞明白这个Parcel就能整明白为什么Android要单独搞一个序列化接口了。

Parcel原理

其实Parcel的翻译为"包裹"、"包"的意思,所以Parcel的设计初衷我觉得并不是序列化,它只是数据的包裹类,而该包裹类可以通过IBinder传输

同时Parcel并不是一个通用的序列化机制,是不推荐使用Parcelable来进行数据持久化保存的

上面是Parcel的官方注释,其中有个非常重要的点是Parcel包裹的数据可以在IBinder之间传输,关于IBinder接口可以做个简单介绍,它是通过Binder进行IPC必须的接口,所以Parcel是为了高性能的IPC通信而设计的。

既然这里说了Parcel比较高效,我们就来看看其如何实现,下面是源码部分:

//Book类中
@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(BookId);
    dest.writeString(BookName);
}
//写入一个int值到parcel中
public final void writeInt(int val) {
    int err = nativeWriteInt(mNativePtr, val);
    if (err != OK) {
        nativeSignalExceptionForError(err);
    }
}
//会调用Native层方法
private static native int nativeWriteInt(long nativePtr, int val);

这里会调用底层C++代码,在android_os_Parcel.cpp中实现如下:

{"nativeWriteInt",            "(JI)V", (void*)android_os_Parcel_writeInt},
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        const status_t err = parcel->writeInt32(val);
       if (err != NO_ERROR) {
            signalExceptionForError(env, clazz, err);
        }
    }
}

会发现它会调用C++的Parcel对象的方法:

status_t Parcel::writeInt32(int32_t val)
{
    return writeAligned(val);
}
//这个就是C++层核心代码,写入数据val
status_t Parcel::writeAligned(T val) {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
    //其中mData就是parcel申请的共享内存
    //mDataPos就是这块共享内存的地址
    //这里先计算mDataPos加val大小是否超过容量,如果超过容量会去扩容,再重新赋值
    if ((mDataPos+sizeof(val)) <= mDataCapacity) {
    //计算mData加mDataPos得到物理地址,转成T类型的指针,然后将val赋值给指针指向的内容
restart_write:
        *reinterpret_cast<T*>(mData+mDataPos) = val;
       return finishWrite(sizeof(val));
    }

    status_t err = growData(sizeof(val));
    if (err == NO_ERROR) goto restart_write;
    return err;
}

由上面代码分析可知在C++层,会将数据写入到一块共享的内存中,所以同理我们从Parcel中读取数据时,也是从这块共享内存中读取。

这时不了解Linux进程的同学,会有点疑惑这个共享的内存是啥玩意。其实这个在后面我们说内存映射时,会好好介绍,这里可以先做个简单了解。

我们一个APP的进程所运行的内存空间是分为用户空间和内核空间的,其中不同进程的用户空间不共享,但是内核空间是共享的;用户空间和内核空间交互时需要通过系统调用,这时为了防止用户空间内的代码发生问题导致系统一块挂掉。

而Parcel把数据保存的地方就是共享空间的内存,所以它和前面所说的Serializable区别不同,Serializable是通过IO操作来保存数据,而Parcel则是直接对内存操作,所以速度比Serializable快很多。

还有一点注意就是,因为Parcel保存是按内存地址加偏移量来保存的,所以读数据时,成员变量的顺序不能乱,一定要和保存时保持一致

其他补充

上面我们对俩种序列化方式做了原理性的探究,透过现象看本质后,我们发现这俩种区别还是非常大的。

大致总结如下:

  1. 因为Serializable需要频繁的IO操作,且涉及到反射调用,而Parcelable只是对共享内存的操作,所以Parcelable比Serializable要快很多
  2. Parcelable实现比较复杂,且需要读写顺序一致性,Serializable则只需要继承接口即可,比较方便。
  3. 俩者的序列化结果都会保留原类信息,和Json的序列化格式有本质区别
  4. Parcelable的设计初衷就是高效的IPC通信,所以在Android中使用内存序列化时(比如Intent使用)使用Parcelable,而做数据持久化使用Serializable

本来这篇文章到此就结束了,但是对于Intent可以传递Parcelable和Serializable数据我有点疑惑,因为Parcelable接口的实现是有代码的,但是实现Serializable接口则是一个空实现,那是什么时候去调用前面所说的ObjectOutputStream和ObjectInputStream呢 我们就来简单看一下流程。

在MainActivity中,我们跳转其他Activity,并且携带一个Serializable对象:

startActivity(Intent(this, SecondActivity::class.java).apply {
    //put一个Serializable对象
    putExtra("key",aTest)
})
//Intent中方法
public @NonNull Intent putExtra(String name, @Nullable Serializable value) {
    if (mExtras == null) {
        mExtras = new Bundle();
    }
    //可以发现这里其实是mExtras才是真正携带数据的
    mExtras.putSerializable(name, value);
    return this;
}
//mExtras是Bundle类型
private Bundle mExtras;
//put一个Serializable数据就是往mMap中塞数据
void putSerializable(@Nullable String key, @Nullable Serializable value) {
    unparcel();
    mMap.put(key, value);
}
//map是一个ArrayMap
ArrayMap<String, Object> mMap = null;

通过上面代码我们可以知道真实携带数据就是Bundle,我们看一下Bundle类定义:

public final class Bundle extends BaseBundle implements Cloneable, Parcelable

可以发现这个类居然也是实现了Parcelable接口,接下来我们分析当页面跳转时,参数是如何传递的,这部分涉及Activity的启动,前面的流程就不说了,我们直接从ActivityManagerNative调用Intent的writeToParcel说起:

public void writeToParcel(Parcel out, int flags) {
    //调用Parcel的writeBundle方法
    out.writeBundle(mExtras);
}
public final void writeBundle(@Nullable Bundle val) {
    if (val == null) {
        writeInt(-1);
        return;
    }
    //调用Bundle的writeToParcel方法
    val.writeToParcel(this, 0);
}
//这里就是把Bundle的内容写入一个Parcel中,为了可以在Binder中通信
public void writeToParcel(Parcel parcel, int flags) {
    final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
    try {
        //调用父类的方法
        super.writeToParcelInner(parcel, flags);
    } finally {
        parcel.restoreAllowFds(oldAllowFds);
    }
}
void writeToParcelInner(Parcel parcel, int flags) {
    ...
    int lengthPos = parcel.dataPosition();
    //这里先写入一些开始标志符号
    //这里的writeXXX函数在前面我们分析过了
    parcel.writeInt(-1); // placeholder, will hold length
    parcel.writeInt(BUNDLE_MAGIC);
    int startPos = parcel.dataPosition();
    //真正开始写入Bundle数据
    parcel.writeArrayMapInternal(map);
    int endPos = parcel.dataPosition();
    //写入数据长度,以及设置结束位置
    parcel.setDataPosition(lengthPos);
    int length = endPos - startPos;
    parcel.writeInt(length);
    parcel.setDataPosition(endPos);
}

所以代码回到Parcel中:

 void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) {
    ...
    //先写入数据长度
    final int N = val.size();
    writeInt(N);
    ...
    int startPos;
    //遍历Bundle
    for (int i=0; i<N; i++) {
        if (DEBUG_ARRAY_MAP) startPos = dataPosition();
        //写入key
        writeString(val.keyAt(i));
        //写入值
        writeValue(val.valueAt(i));
        if (DEBUG_ARRAY_MAP) Log.d(TAG, "  Write #" + i + " "
                + (dataPosition()-startPos) + " bytes: key=0x"
                + Integer.toHexString(val.keyAt(i) != null ? val.keyAt(i).hashCode() : 0)
                + " " + val.keyAt(i));
    }
}

到这里我们逐渐清晰,还记得我们最开始传递的Serializable对象吗,它将会在writeValue中写入:

//判断数据类型,针对不同的数据类型,采用不同的写入方法,即把不同的object都展开为数据
//写入到parcel中
public final void writeValue(@Nullable Object v) {
    if (v == null) {
        writeInt(VAL_NULL);
    } else if (v instanceof String) {
        writeInt(VAL_STRING);
        writeString((String) v);
    } else if (v instanceof Integer) {
        writeInt(VAL_INTEGER);
        writeInt((Integer) v);
    } else if (v instanceof Map) {
        writeInt(VAL_MAP);
        writeMap((Map) v);
    } else if (v instanceof Bundle) {
        // Must be before Parcelable
        writeInt(VAL_BUNDLE);
        writeBundle((Bundle) v);
    } else if (v instanceof PersistableBundle) {
        writeInt(VAL_PERSISTABLEBUNDLE);
        writePersistableBundle((PersistableBundle) v);
    } else if (v instanceof Parcelable) {
        // IMPOTANT: cases for classes that implement Parcelable must
        // come before the Parcelable case, so that their specific VAL_*
        // types will be written.
        writeInt(VAL_PARCELABLE);
        writeParcelable((Parcelable) v, 0);
    } else if (v instanceof Short) {
        writeInt(VAL_SHORT);
        writeInt(((Short) v).intValue());
    } else if (v instanceof Long) {
        writeInt(VAL_LONG);
        writeLong((Long) v);
    } else if (v instanceof Float) {
        writeInt(VAL_FLOAT);
        writeFloat((Float) v);
    } else if (v instanceof Double) {
        writeInt(VAL_DOUBLE);
        writeDouble((Double) v);
    } else if (v instanceof Boolean) {
        writeInt(VAL_BOOLEAN);
        writeInt((Boolean) v ? 1 : 0);
    } else if (v instanceof CharSequence) {
        // Must be after String
        writeInt(VAL_CHARSEQUENCE);
        writeCharSequence((CharSequence) v);
    } else if (v instanceof List) {
        writeInt(VAL_LIST);
        writeList((List) v);
    } else if (v instanceof SparseArray) {
        writeInt(VAL_SPARSEARRAY);
        writeSparseArray((SparseArray) v);
    } else if (v instanceof boolean[]) {
        writeInt(VAL_BOOLEANARRAY);
        writeBooleanArray((boolean[]) v);
    } else if (v instanceof byte[]) {
        writeInt(VAL_BYTEARRAY);
        writeByteArray((byte[]) v);
    } else if (v instanceof String[]) {
        writeInt(VAL_STRINGARRAY);
        writeStringArray((String[]) v);
    } else if (v instanceof CharSequence[]) {
        // Must be after String[] and before Object[]
        writeInt(VAL_CHARSEQUENCEARRAY);
        writeCharSequenceArray((CharSequence[]) v);
    } else if (v instanceof IBinder) {
        writeInt(VAL_IBINDER);
        writeStrongBinder((IBinder) v);
    } else if (v instanceof Parcelable[]) {
        writeInt(VAL_PARCELABLEARRAY);
        writeParcelableArray((Parcelable[]) v, 0);
    } else if (v instanceof int[]) {
        writeInt(VAL_INTARRAY);
        writeIntArray((int[]) v);
    } else if (v instanceof long[]) {
        writeInt(VAL_LONGARRAY);
        writeLongArray((long[]) v);
    } else if (v instanceof Byte) {
        writeInt(VAL_BYTE);
        writeInt((Byte) v);
    } else if (v instanceof Size) {
        writeInt(VAL_SIZE);
        writeSize((Size) v);
    } else if (v instanceof SizeF) {
        writeInt(VAL_SIZEF);
        writeSizeF((SizeF) v);
    } else if (v instanceof double[]) {
        writeInt(VAL_DOUBLEARRAY);
        writeDoubleArray((double[]) v);
    } else {
        Class<?> clazz = v.getClass();
        if (clazz.isArray() && clazz.getComponentType() == Object.class) {
            // Only pure Object[] are written here, Other arrays of non-primitive types are
            // handled by serialization as this does not record the component type.
            writeInt(VAL_OBJECTARRAY);
            writeArray((Object[]) v);
        } else if (v instanceof Serializable) {
            // Must be last
            //必须要放到最后
            writeInt(VAL_SERIALIZABLE);
            writeSerializable((Serializable) v);
        } else {
            throw new RuntimeException("Parcel: unable to marshal value " + v);
        }
    }
}

上面有非常多的类型,里面细节就不说了,其实就对应着Intent可以传递哪些类型的数据类型,其中在最后的地方,终于看到了我们想看到的类型:Serializable,接着看该函数实现:

public final void writeSerializable(@Nullable Serializable s) {
    if (s == null) {
        writeString(null);
        return;
    }
    String name = s.getClass().getName();
    writeString(name);
    //字节数组
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        //还是通过ObjectOutputStream
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(s);
        oos.close();
        //写入字节数组
        writeByteArray(baos.toByteArray());
    } catch (IOException ioe) {
        throw new RuntimeException("Parcelable encountered " +
            "IOException writing serializable object (name = " + name +
            ")", ioe);
    }
}

好了,到这我们终于发现了即使使用Intent传入了Serializable类型的数据,最后还是通过把Serializable类型数据转成字节数组,通过Parcel写入共享内存,来完成通信。

这里提供一个小技巧,就是这种找代码调用关系比较复杂时,可以手动创建crash,通过Log日志就可以发现,比如上面调用流程当传入的类型不对时,会报下面crash:

//错误栈
java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = com.zyh.ipc.ATest)
        //报错部分
        at android.os.Parcel.writeSerializable(Parcel.java:1527)
        at android.os.Parcel.writeValue(Parcel.java:1475)
        at android.os.Parcel.writeArrayMapInternal(Parcel.java:724)
        at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1408)
        at android.os.Bundle.writeToParcel(Bundle.java:1157)
        at android.os.Parcel.writeBundle(Parcel.java:764)
        at android.content.Intent.writeToParcel(Intent.java:8700)
        at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3082)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1518)
        at android.app.Activity.startActivityForResult(Activity.java:4244)
        at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:597)
        at android.app.Activity.startActivityForResult(Activity.java:4202)
        at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:583)
        at android.app.Activity.startActivity(Activity.java:4541)
        at android.app.Activity.startActivity(Activity.java:4509)
        at com.zyh.ipc.MainActivity.onCreate$lambda-2(MainActivity.kt:26)
        //APP代码部分
        at com.zyh.ipc.MainActivity.$r8$lambda$zUKr2W1DarwwQ9g_Poh-FBVnM4g(MainActivity.kt)
        at com.zyh.ipc.MainActivity$$ExternalSyntheticLambda2.onClick(Unknown Source)
        at android.view.View.performClick(View.java:5637)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1131)
        at android.view.View$PerformClick.run(View.java:22429)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6145)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:892)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:782)

通过上面的错误调用栈,我们可以清晰地看出调用关系。

总结

关于序列化的知识还是非常多的,首先就是Java提供的Serializable接口,通过查看源码我们发现其内部涉及IO操作和反射调用,所以性能一般;其次就是Android特有的Parcelable接口,其原理就是Parcel包裹类实现,序列化和反序列化过程其实的通过C++代码操作共享内存,所以性能非常好,非常适合IPC通信。

然后对Serializable和Parcelable做了比较,以及通过Intent传递对象数据我们得知数据其实保存在Bundle中,Serializable类型数据也会转成字节数组写入共享内存。

后续文章继续探究IPC其他知识点。

笔者水平有限,如有问题,欢迎大家评论指正。