美文网首页
Kotlin 序列化与反序列化,readResolve() 和

Kotlin 序列化与反序列化,readResolve() 和

作者: Vic_wkx | 来源:发表于2022-04-06 17:53 被阅读0次

    今天阅读协程源码的时候,看到 EmptyCoroutineContext 类实现了 readResolve() 函数,还定义了一个 serialVersionUID 常量。于是学习了一下这两者的作用,特此记录。

    Kotlin 中的单例模式

    在 Kotlin 中使用单例很方便,只需要使用 object 关键字即可:

    object Singleton
    

    这时就可以全局使用这个单例类了。但如果这个单例类需要序列化,在这个对象序列化之后,再反序列化就会产生一个全新的对象,导致单例模式失效,毕竟单例模式的特点就是全局只能有一个单例。

    实验验证

    做个实验验证一下,首先,修改 Singleton 类,使其实现 Serializable 接口

    object Singleton : Serializable
    

    再对这个单例类进行序列化和反序列化,并打印其原始内存地址和反序列化后生成的对象地址,以作比较:

    Log.d("~~~", "Singleton: $Singleton")
    val fileSavePath = "${cacheDir}${File.separator}test.txt"
    val fileOutputStream = FileOutputStream(fileSavePath)
    ObjectOutputStream(fileOutputStream).use {
        it.writeObject(Singleton)
    }
    val fileInputStream = FileInputStream(fileSavePath)
    ObjectInputStream(fileInputStream).use {
        val obj = it.readObject()
        if (obj is Singleton) {
            Log.d("~~~", "obj is Singleton, $obj")
        } else {
            Log.d("~~~", "obj isn't Singleton, $obj")
        }
    }
    

    运行程序,输出如下:

    ~~~: Singleton: com.example.myapplication.Singleton@a3df01b
    ~~~: obj is Singleton, com.example.myapplication.Singleton@3ef6564
    

    可以看出,反序列化后生成的对象地址和原始对象地址是不一致的,证实了反序列化后生成了一个新对象。

    解决方式 —— readResolve() 函数

    想要解决这个问题,就要用到 readResolve() 函数。这个函数有一个返回值,指的是对象进行反序列化时,读出来的对象。

    所以我们需要对单例进行如下修改,使 Singleton 的 readResolve() 函数返回 Singleton 对象本身:

    object Singleton : Serializable {
        private fun readResolve(): Any = Singleton
    }
    

    修改后,我们再运行之前的测试代码,输出如下:

    ~~~: Singleton: com.example.myapplication.Singleton@a3df01b
    ~~~: obj is Singleton, com.example.myapplication.Singleton@a3df01b
    

    可以看出反序列后的对象和原始对象已经一致了。

    serialVersionUID 的作用

    在一个对象被序列化后,如果这个对象的结构被修改了,就可能导致反序列化时不兼容。为了解决这个问题,每个 class 可以定义一个 serialVersionUID 静态变量,用于标识这个类的序列化版本,当这个对象的结构被修改后,就修改一下这个变量,这样就能阻止不匹配的 class 版本。

    我测试了一下,如果把 serialVersionUID 设成 0,序列化存到本地后,再把 serialVersionUID 改成 1。进行反序列化时,程序抛出了 InvalidClassException 异常:

    com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
        Process: com.example.myapplication, PID: 8278
        java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
         Caused by: java.lang.reflect.InvocationTargetException
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
         Caused by: java.io.InvalidClassException: com.example.myapplication.Singleton; local class incompatible: stream classdesc serialVersionUID = 0, local class serialVersionUID = 1
            at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:624)
            at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1713)
            at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1594)
            at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1872)
            at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1412)
            at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
            at com.example.myapplication.MainActivity.onCreate$lambda-1(MainActivity.kt:23)
            at com.example.myapplication.MainActivity.$r8$lambda$U17Gk-Q12NTUVdhVQSbB0lbdtEQ(Unknown Source:0)
            at com.example.myapplication.MainActivity$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
            at android.view.View.performClick(View.java:7448)
            at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
            at android.view.View.performClickInternal(View.java:7425)
            at android.view.View.access$3600(View.java:810)
            at android.view.View$PerformClick.run(View.java:28305)
            at android.os.Handler.handleCallback(Handler.java:938)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:223)
            at android.app.ActivityThread.main(ActivityThread.java:7656)
            at java.lang.reflect.Method.invoke(Native Method) 
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
    

    所以在 serialVersionUID 改变后,需要开发者手动处理 InvalidClassException 异常,当这个异常出现时,很可能是反序列化的对象结构已经被更改了。

    如果这个类的结构虽然改变了,但和以前的结构是兼容的,也可以不修改 serialVersionUID 的值,这样反序列化就不会出错了。

    相关文章

      网友评论

          本文标题:Kotlin 序列化与反序列化,readResolve() 和

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