今天閱讀協(xié)程源碼的時(shí)候滴铅,看到 EmptyCoroutineContext 類實(shí)現(xiàn)了 readResolve() 函數(shù)戳葵,還定義了一個(gè) serialVersionUID 常量。于是學(xué)習(xí)了一下這兩者的作用汉匙,特此記錄拱烁。
Kotlin 中的單例模式
在 Kotlin 中使用單例很方便,只需要使用 object 關(guān)鍵字即可:
object Singleton
這時(shí)就可以全局使用這個(gè)單例類了噩翠。但如果這個(gè)單例類需要序列化戏自,在這個(gè)對象序列化之后,再反序列化就會產(chǎn)生一個(gè)全新的對象伤锚,導(dǎo)致單例模式失效擅笔,畢竟單例模式的特點(diǎn)就是全局只能有一個(gè)單例。
實(shí)驗(yàn)驗(yàn)證
做個(gè)實(shí)驗(yàn)驗(yàn)證一下,首先猛们,修改 Singleton 類念脯,使其實(shí)現(xiàn) Serializable 接口
object Singleton : Serializable
再對這個(gè)單例類進(jìn)行序列化和反序列化,并打印其原始內(nèi)存地址和反序列化后生成的對象地址弯淘,以作比較:
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")
}
}
運(yùn)行程序绿店,輸出如下:
~~~: Singleton: com.example.myapplication.Singleton@a3df01b
~~~: obj is Singleton, com.example.myapplication.Singleton@3ef6564
可以看出,反序列化后生成的對象地址和原始對象地址是不一致的庐橙,證實(shí)了反序列化后生成了一個(gè)新對象假勿。
解決方式 —— readResolve() 函數(shù)
想要解決這個(gè)問題,就要用到 readResolve() 函數(shù)怕午。這個(gè)函數(shù)有一個(gè)返回值废登,指的是對象進(jìn)行反序列化時(shí),讀出來的對象郁惜。
所以我們需要對單例進(jìn)行如下修改堡距,使 Singleton 的 readResolve() 函數(shù)返回 Singleton 對象本身:
object Singleton : Serializable {
private fun readResolve(): Any = Singleton
}
修改后,我們再運(yùn)行之前的測試代碼兆蕉,輸出如下:
~~~: Singleton: com.example.myapplication.Singleton@a3df01b
~~~: obj is Singleton, com.example.myapplication.Singleton@a3df01b
可以看出反序列后的對象和原始對象已經(jīng)一致了羽戒。
serialVersionUID 的作用
在一個(gè)對象被序列化后,如果這個(gè)對象的結(jié)構(gòu)被修改了虎韵,就可能導(dǎo)致反序列化時(shí)不兼容易稠。為了解決這個(gè)問題,每個(gè) class 可以定義一個(gè) serialVersionUID 靜態(tài)變量包蓝,用于標(biāo)識這個(gè)類的序列化版本驶社,當(dāng)這個(gè)對象的結(jié)構(gòu)被修改后,就修改一下這個(gè)變量测萎,這樣就能阻止不匹配的 class 版本亡电。
我測試了一下,如果把 serialVersionUID 設(shè)成 0硅瞧,序列化存到本地后份乒,再把 serialVersionUID 改成 1。進(jìn)行反序列化時(shí)腕唧,程序拋出了 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 改變后或辖,需要開發(fā)者手動處理 InvalidClassException 異常,當(dāng)這個(gè)異常出現(xiàn)時(shí)枣接,很可能是反序列化的對象結(jié)構(gòu)已經(jīng)被更改了颂暇。
如果這個(gè)類的結(jié)構(gòu)雖然改變了,但和以前的結(jié)構(gòu)是兼容的月腋,也可以不修改 serialVersionUID 的值蟀架,這樣反序列化就不會出錯(cuò)了瓣赂。