最近項(xiàng)目進(jìn)行了一次target sdk的升級28版本的改造,在處理了一些target 版本的Android9.0的兼容之后,項(xiàng)目整體運(yùn)行起來沒有什么問題,但在之后作為SDK給另一個(gè)項(xiàng)目使用之后出現(xiàn)了一個(gè)比較罕見的問題-NotSerializableException: com.google.gson.internal.StringMap數(shù)據(jù)序列化問題,看似比較簡單,也是困擾了很久,下面總結(jié)一下針對這個(gè)問題的跟蹤查詢。

一、問題描述
? ? 我在進(jìn)行功能驗(yàn)證的過程中發(fā)現(xiàn)了一個(gè)比較奇怪的問題,當(dāng)我在頁面A的時(shí)候數(shù)據(jù)沒有任何問題,也可以正常顯示,但是不管我從頁面A(Fragment)跳轉(zhuǎn)到任何頁面B、C、D(Activity,F(xiàn)ragment都行)都會(huì)崩潰,控制臺輸出問題截圖上面的的異常信息。然而我從其他頁面E(Fragment)跳轉(zhuǎn)到B、C、D的時(shí)候都沒有任何問題。
????根據(jù)控制臺的異常日志輸出信息可以看出這就是一個(gè)簡單的序列化問題,剛開始我是這么認(rèn)為的,日志信息明確說明了是PageBean的寫入數(shù)列化問題,那我們就找PageBean然后實(shí)現(xiàn)序列化就可以了。當(dāng)我找到PageBean的時(shí)候就懵了?public class PageBeanimplements Serializable {},明明都已經(jīng)實(shí)現(xiàn)了Serializable 接口了,為什么還是報(bào)錯(cuò)了,是不是子類沒有實(shí)現(xiàn)Serializable 接口呢!然后我仔細(xì)檢查了PageBean的每個(gè)子類以及子類的子類,全部都實(shí)現(xiàn)了Serializable 接口,問題開始變得復(fù)雜了。回頭又看錯(cuò)誤日志,發(fā)現(xiàn)還有一個(gè)信息,就是StringMap這個(gè)類,但是我搜索了一個(gè)引用的gson庫并沒有找個(gè)這個(gè)文件。
二、問題定位?
我把問題同步給了項(xiàng)目leader,經(jīng)過leader跟同事的連夜查找,總算是大概定位到了問題所在,而且也找到了StringMap這個(gè)類。那天我早早的可恥的溜了,事后也是感到非常的慚愧。原來StringMap是在早些的gson庫里所存在幫助json數(shù)據(jù)解析的類,而我們的項(xiàng)目的gson比較新,所以一直找不到這個(gè)類,我們的另一個(gè)項(xiàng)目是早些的gson庫,所以打包后到另一個(gè)項(xiàng)目才會(huì)出現(xiàn)此問題。出問題的地方大概在下面所存在的寫入序列化對象的代碼中
public static TemplateContainerFragment newInstance(ChannelNavBean channelNavBean, PageBean firstPageBean) {
????TemplateContainerFragment vesselFragment = new TemplateContainerFragment();
????Bundle bundle = new Bundle();
????bundle.putSerializable(AppParams.INTENT_PARAM_CHANNEL_NAV_BEAN, channelNavBean);
? ? if (firstPageBean != null) {
? ? ????bundle.putSerializable(AppParams.INTENT_PARAM_CHANNEL_PAGE_BEAN, firstPageBean);
????}
????vesselFragment.setArguments(bundle);
????return vesselFragment;
}
大概定到問題以后,leader為了鍛煉我解決問題的能力,也是拋給我2個(gè)問題,希望我能多提高自己解決問題的能力
????1.序列化是在什么時(shí)候出問題的?出問題的序列化對象在什么位置上?SrtingMap對象在當(dāng)中扮演的角色是什么?
????2.為什么頁面初始化的時(shí)候沒有問題,反而在進(jìn)入下一級頁面的時(shí)候出現(xiàn)崩潰?
三、問題分析
? ? 根據(jù)日志信息和已找到的代碼可以大概確定PageBean是在序列化的時(shí)候出現(xiàn)了問題,我找到上面的相關(guān)代碼進(jìn)行debug驗(yàn)證,尋找PageBean中未實(shí)現(xiàn)序列化的StringMap對象,還真的有所發(fā)現(xiàn)

? ? 為什么PageBean里面會(huì)有StringMap對象?帶著這個(gè)疑問我跟蹤查找了一個(gè)StringMap的產(chǎn)生,終于在gson庫里面的ObjectTypeAdapter對象中找到了StringMap的產(chǎn)生,原來是在數(shù)據(jù)解析的時(shí)候如果有JSONArray有未知的List<Object> list,Object會(huì)被轉(zhuǎn)化成為一個(gè)StringMap對象存儲,而StringMap是沒有序列化的對象,所以在傳遞數(shù)據(jù)的過程中會(huì)出現(xiàn)異常。那么第一個(gè)問題就找到了答案
public Object read(JsonReader in) throws IOException {
????JsonToken token = in.peek();
????switch(token) {
????????case BEGIN_ARRAY:
????????????????List<Object> list = new ArrayList();
????????????????in.beginArray();
????????while(in.hasNext()) {
????????????list.add(this.read(in));
????????}
????????in.endArray();
????????return list;
????????case BEGIN_OBJECT:
????????????????Map<String, Object> map = new StringMap();
????????????????in.beginObject();
????????while(in.hasNext()) {
????????????map.put(in.nextName(), this.read(in));
????????}
????????in.endObject();
????????return map;
????????case STRING:
????????????????return in.nextString();
????????case NUMBER:
????????????????return in.nextDouble();
????????case BOOLEAN:
????????????????return in.nextBoolean();
????????case NULL:
????????????????in.nextNull();
????????????????return null;
????????default:
????????????????throw new IllegalStateException();
????}
}
? ? 剩下的問題就是這個(gè)方法明明是在頁面初始化的時(shí)候調(diào)用的,為什么在頁面初始化的時(shí)候沒有問題,返回再頁面進(jìn)入下一級頁面的時(shí)候回出現(xiàn)崩潰?其實(shí)發(fā)現(xiàn)進(jìn)入下一個(gè)頁面的時(shí)候出現(xiàn)問題,也就發(fā)現(xiàn)了思路,進(jìn)入下一級頁面的時(shí)候上一個(gè)頁面要保存數(shù)據(jù),對,就是保存數(shù)據(jù)的時(shí)候可能會(huì)出現(xiàn)問題,然后我就跟蹤onSaveInstanceState(@NonNull Bundle outState)的方法,頁面離開確實(shí)調(diào)用了該方法,但是outState參數(shù)是空的,沒有傳遞任何數(shù)據(jù),為什么會(huì)出問題呢,經(jīng)過一系列的跟蹤,終于在FragmentState中發(fā)現(xiàn)了問題,原來在fragment頁面
@Override
public void writeToParcel(Parcel dest, int flags) {
????dest.writeString(mClassName);
????dest.writeInt(mIndex);
????dest.writeInt(mFromLayout ? 1 : 0);
????dest.writeInt(mFragmentId);
????dest.writeInt(mContainerId);
????dest.writeString(mTag);
????dest.writeInt(mRetainInstance ? 1 : 0);
????dest.writeInt(mDetached ? 1 : 0);
????dest.writeBundle(mArguments);
????dest.writeInt(mHidden ? 1 : 0);
????dest.writeBundle(mSavedFragmentState);
}




? ? ? ? for循環(huán)寫入數(shù)據(jù)

? ? 寫入數(shù)據(jù),原來這里要求寫入的數(shù)據(jù)對象以及子對象都必須是序列化的數(shù)據(jù),否則就會(huì)出現(xiàn)異常。為什么頁面初始化進(jìn)入寫入的數(shù)據(jù)沒有問題,反而在頁面離開保存數(shù)據(jù)的時(shí)候?qū)懭氲臄?shù)據(jù)會(huì)出現(xiàn)異常?帶著這個(gè)疑問我又看了一下bundle.putSerializable()這個(gè)方法。


? ? 看到代碼之后我釋然了,原來putSerializable這個(gè)方法值值要求傳入的對象被序列化就可以,并不要求子對象必須都實(shí)現(xiàn)數(shù)列化接口。所以才導(dǎo)致頁面初始化的時(shí)候并沒有問題,反而在頁面離開保存數(shù)據(jù)的時(shí)候?qū)懭霐?shù)據(jù)異常。
四、問題跟蹤解決
由于我們的數(shù)據(jù)PageBean的解析里面JSONArray出現(xiàn)的問題,所以就嘗試在不改變gson庫版本號的同時(shí)解決這個(gè)問題,于是根據(jù)返回的數(shù)據(jù)結(jié)構(gòu)嘗試把JSONAarray替換成List<JSONObject>形式,于是又做了一番兼容的嘗試工作,但是由于我們的數(shù)據(jù)結(jié)構(gòu)問題,并沒有成功,最后還是報(bào)出了相同的錯(cuò)誤,經(jīng)過查看還JSONObject中還是出現(xiàn)了沒有序列化的StringMap。

最后只能溝通把另一個(gè)項(xiàng)目的gson庫進(jìn)行升級解決這個(gè)問題。