以前的項目中使用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)類的方法, 這里看不出有什么問題, 合乎情理. 不過我們還是對比下混淆前后的代碼:
左邊沒有混淆, 如果將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序列化那些事 )這個子類來對比下混淆前后
可以看到混淆后依然是繼承了
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
我感覺很好, 和沒有混淆的差不多槐壳。
趕緊跑代碼看看, 出現(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反編譯成匯編碼啊楚,省了不少事吠冤!一起來體驗下吧。
- as中雙擊apk文件或者將apk拖到as恭理,選中一個類
單擊右鍵
單擊右鍵 -
Show Bytecode
Show Bytecode -
Find Usages
Find Usages -
Generate Proguard keep rule
Generate Proguard keep rule