Gson混淆后報AbstractMethodError

以前的項目中使用Gson沒有直接用到JsonAdapter(指TypeAdapter,TypeAdapterFactory, JsonSerializer, JsonDeserializer子類或?qū)崿F(xiàn)類), 但Gson內(nèi)置有多種類型的TypeAdapter在解析時生效吮蛹。當(dāng)使用JsonAdapter并開啟混淆運(yùn)行后拋出莫名其妙的異常:

     Caused by: java.lang.AbstractMethodError: abstract method "java.lang.Object c.c.b.J.a(c.c.b.d.b)"
        at c.c.b.I.a(SourceFile:5)

以前項目也有開啟混淆代碼跑起來都很正常,可能和kotlin的使用有關(guān)(下文進(jìn)行驗證,讀者也可以嘗試進(jìn)行驗證)拌屏。定位到mapping.txt中:

com.google.gson.stream.JsonReader -> c.c.b.d.b:
com.google.gson.TypeAdapter -> c.c.b.J:
    java.lang.Object read(com.google.gson.stream.JsonReader) -> a
com.google.gson.TypeAdapter$1 -> c.c.b.I:
    5:5:java.lang.Object read(com.google.gson.stream.JsonReader):199:199 -> a

結(jié)合異常信息說明調(diào)用的TypeAdapter.read(JsonReader):Object是一個抽象方法潮针。

又見TypeAdapter

為什么說"又"
使用了TypeAdapter, 先看下這個類的幾個方法:

public abstract class TypeAdapter<T> {
  public abstract void write(JsonWriter out, T value) throws IOException;
  public abstract T read(JsonReader in) throws IOException;

  public final TypeAdapter<T> nullSafe() {
    return new TypeAdapter<T>() {
      @Override public void write(JsonWriter out, T value) throws IOException {
        if (value == null) {
          out.nullValue();
        } else {
          TypeAdapter.this.write(out, value);
        }
      }
      @Override public T read(JsonReader reader) throws IOException {
        if (reader.peek() == JsonToken.NULL) {
          reader.nextNull();
          return null;
        }
        return TypeAdapter.this.read(reader);
      }
    };
  }
}

TypeAdapter源碼中唯一一個內(nèi)部類就在nullSafe()中產(chǎn)生, 這是一個局部內(nèi)部類, read最后調(diào)用return TypeAdapter.this.read(reader); , 聯(lián)系到j(luò)ava的泛型具有類型擦除的性質(zhì)(參考筆者另一篇文章 關(guān)于Gson的TypeToken ), 局部類TypeAdapter$1的類型是擦除的. 假如有TypeAdapter的實現(xiàn)類, 類型擦除后調(diào)用原始版本的Object read(JsonReader)是可以說得通的. 反編譯這個內(nèi)部類的read方法這一行的調(diào)用:

invoke-virtual {v0, p1}, Lc/c/b/J;->a(Lc/c/b/d/b;)Ljava/lang/Object;

果然調(diào)用的是Object read(JsonReader)的方法. 同時該方法也是virtual的, 而java支持多態(tài), 因此最終調(diào)用的還是實現(xiàn)類的方法, 這里看不出有什么問題, 合乎情理. 不過我們還是對比下混淆前后的代碼:

混淆前后nullSafe()對比

左邊沒有混淆, 如果將mapping對應(yīng)上幾乎和右邊沒什么兩樣了, 最關(guān)鍵的一處是混淆后的泛型信息<TT;>被刪除了!

這里混淆后泛型信息被刪除,使用的是原始類型,因此子類的實現(xiàn)方法變成沒有被任何代碼引用的無用代碼, 混淆時直接將這些沒用代碼刪除以精簡apk.

沒用混淆時泛型信息保留著可以引用到這些實現(xiàn)的方法, 而且這些方法也沒有被刪除掉, 可以正常運(yùn)行.

定制混淆規(guī)則(proguard rule)

既然問題在nullSafe()中, 不混淆這個方法可以確保泛型信息保留:

-keepclassmembers class com.google.gson.TypeAdapter{
    nullSafe();
}

泛型保住了, 而TypeAdapter子類的實現(xiàn)方法依然被無情的刪除了, 拿BooleanAdapter(參看 Gson序列化那些事 )這個子類來對比下混淆前后

BooleanAdapter混淆前

BooleanAdapter混淆后

可以看到混淆后依然是繼承了TypeAdapter(混淆后是super Lc/c/b/J;), 因此有兩個抽象方法, 但是我們自己寫的兩個實現(xiàn)方法卻被刪除了(上面的截圖就是該類的所有匯編代碼, 沒有截斷), 所以子類中只剩下類的簽名信息了. 根據(jù)java的多態(tài)性, 保留我們實現(xiàn)的方法應(yīng)該就可以正常運(yùn)行了, 于是在混淆文件中加入以下代碼:

-keepclassmembers class * extends com.google.gson.TypeAdapter {*;}

TypeAdapter子類的成員都保留下來(類名允許混淆了). 這是再看下打包后的BooleanAdapter

BooleanAdapter混淆后

我感覺很好, 和沒有混淆的差不多槐壳。

趕緊跑代碼看看, 出現(xiàn)一個類似的異常, 這次換成是JsonDeserializer類(項目中也有用到, 類似的方法再保留成員), 可以正常運(yùn)行了.

gson.pro

綜上所述, 對于gson如果使用了JsonAdapter, 應(yīng)該添加混淆選項:

-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer

或者

-keepclassmembers class * extends com.google.gson.TypeAdapter {*;}
-keepclassmembers class * implements com.google.gson.TypeAdapterFactory {*;}
-keepclassmembers class * implements com.google.gson.JsonSerializer {*;}
-keepclassmembers class * implements com.google.gson.JsonDeserializer {*;}

后者混淆的程度更高.

nullSafe()方式

為了驗證混淆后實現(xiàn)方法被刪除與kotlin和java這兩種語言是否有關(guān)然低,這里編寫了BooleanAdapter的java版本BooleanAdapterJ, 看下混淆的結(jié)果:

com.common.entity.BooleanAdapter -> c.e.b.a.b:
com.common.entity.BooleanAdapterJ -> c.e.b.a.c:
com.google.gson.TypeAdapter -> c.c.b.J:
    119:119:void <init>() -> <init>
    java.lang.Object read(com.google.gson.stream.JsonReader) -> a
    void write(com.google.gson.stream.JsonWriter,java.lang.Object) -> a
    186:186:com.google.gson.TypeAdapter nullSafe() -> a
    233:237:com.google.gson.JsonElement toJsonTree(java.lang.Object) -> a
com.google.gson.internal.bind.TypeAdapters$1 -> c.c.b.b.a.H:
    69:69:void <init>() -> <init>
    69:69:java.lang.Object read(com.google.gson.stream.JsonReader) -> a
    69:69:void write(com.google.gson.stream.JsonWriter,java.lang.Object) -> a
    72:73:void write(com.google.gson.stream.JsonWriter,java.lang.Class) -> a
    77:77:java.lang.Class read(com.google.gson.stream.JsonReader) -> a

上面貼出來的是mapping.txt中所有關(guān)于這兩個類的混淆結(jié)果。

作為參照把TypeAdapter和gson中一個已寫好的實現(xiàn)類的混淆結(jié)果也貼了出來务唐。TypeAdapters中內(nèi)置了很多常用類型的適配器雳攘,TypeAdapters$1是第一個內(nèi)部類(gson-2.8.5)。

public final class TypeAdapters {
 @SuppressWarnings("rawtypes")
 public static final TypeAdapter<Class> CLASS = new TypeAdapter<Class>() {
   @Override
   public void write(JsonWriter out, Class value) throws IOException {
     throw UnsupportedOperationException
   }
   @Override
   public Class read(JsonReader in) throws IOException {
     throw UnsupportedOperationException
  }
}.nullSafe();

可以看出筆者寫的兩個類均沒能將實現(xiàn)的方法保留下來, 而自帶的TypeAdapters$1保留下來了枫笛。依樣畫葫蘆, 我們也使用nullSafe()的方式來注冊一個看效果.

GsonBuilder()
            ...
            .registerTypeAdapter(Boolean::class.java, BooleanAdapter().nullSafe())
            .create()

再看下混淆結(jié)果:

com.common.entity.BooleanAdapter -> c.e.b.a.b:
    19:19:void <init>() -> <init>
    19:19:java.lang.Object read(com.google.gson.stream.JsonReader) -> a
    19:19:void write(com.google.gson.stream.JsonWriter,java.lang.Object) -> a
    21:25:void write(com.google.gson.stream.JsonWriter,java.lang.Boolean) -> a
    28:32:java.lang.Boolean read(com.google.gson.stream.JsonReader) -> a

驚不驚喜,意不意外! 其實只要代碼中有通過nullSafe()的調(diào)用使得實現(xiàn)的方法被引用到就可以保留下來吨灭。所以使用nullSafe()的方式來避免混淆問題也是行得通的。是選擇寫混淆規(guī)則還是nullSafe()方式刑巧,這就要看你是否接受nullSafe()中的默認(rèn)處理方式喧兄。

TypeAdapters.nullSafe()中的泛型信息順便也保留下來了。

參考

Gson序列化那些事
關(guān)于Gson的TypeToken
比對合并工具meld

彩蛋

Android Studio這個功能強(qiáng)大的IDE居然可以直接將apk反編譯成匯編碼啊楚,省了不少事吠冤!一起來體驗下吧。

  1. as中雙擊apk文件或者將apk拖到as恭理,選中一個類單擊右鍵
    單擊右鍵
  2. Show Bytecode


    Show Bytecode
  3. Find Usages


    Find Usages
  4. Generate Proguard keep rule


    Generate Proguard keep rule
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拯辙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子颜价,更是在濱河造成了極大的恐慌涯保,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件周伦,死亡現(xiàn)場離奇詭異夕春,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)专挪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門及志,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人寨腔,你說我怎么就攤上這事困肩。” “怎么了脆侮?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵锌畸,是天一觀的道長。 經(jīng)常有香客問我靖避,道長潭枣,這世上最難降的妖魔是什么比默? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮盆犁,結(jié)果婚禮上命咐,老公的妹妹穿的比我還像新娘。我一直安慰自己谐岁,他們只是感情好醋奠,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著伊佃,像睡著了一般窜司。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上航揉,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天塞祈,我揣著相機(jī)與錄音,去河邊找鬼帅涂。 笑死议薪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的媳友。 我是一名探鬼主播斯议,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼醇锚!你這毒婦竟也來了捅位?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤搂抒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后尿扯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粱玲,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡懂算,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碾阁。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赊豌,靈堂內(nèi)的尸體忽然破棺而出等恐,到底是詐尸還是另有隱情,我是刑警寧澤泊脐,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布空幻,位于F島的核電站,受9級特大地震影響容客,放射性物質(zhì)發(fā)生泄漏秕铛。R本人自食惡果不足惜约郁,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望但两。 院中可真熱鬧鬓梅,春花似錦、人聲如沸谨湘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坊罢。三九已至,卻和暖如春寓辱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诱鞠。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留这敬,地道東北人航夺。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓崔涂,卻偏偏與公主長得像阳掐,于是被迫代替她去往敵國和親冷蚂。 傳聞我的和親對象是個殘疾皇子缭保,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容