ProGuard常见问题及解决套路
ProGuard是一個(gè)壓縮、優(yōu)化和混淆Java字節(jié)碼的工具,非常好用。本篇文章總結(jié)一下許多人在使用ProGuard時(shí)經(jīng)常遇到的問題。
我把在使用ProGuard時(shí)經(jīng)常遇到的問題分為兩類,分別是導(dǎo)致構(gòu)建失敗的編譯時(shí)問題,以及構(gòu)建通過但運(yùn)行時(shí)崩潰或結(jié)果不正確的運(yùn)行時(shí)問題。大多數(shù)人所遇到的大多數(shù)問題,都可以在下面的內(nèi)容中找到對(duì)應(yīng)的解決套路。
在開始講這兩類問題前,先明確一點(diǎn):我們所說的添加混淆規(guī)則,不是指加入了才會(huì)混淆相關(guān)的類,相反,事實(shí)上,當(dāng)你啟用混淆之后,添加的一些諸如-keep xxxx的規(guī)則才是起著不混淆的作用。
下面開始講這兩類問題。
編譯時(shí)問題
問題
首先講編譯時(shí)的問題。導(dǎo)致編譯不通過,最常見的情況是這樣的:
在漫長的編譯之后,我們等到的控制臺(tái)上的這樣一個(gè)輸出結(jié)果:
原因及解決方法
有些小伙伴會(huì)自動(dòng)忽略英文日志,即便它給出了明確的答案,這是個(gè)嚴(yán)重的不良習(xí)慣。如上,其實(shí)在這段日志中,已經(jīng)表明了原因及解決方案了。注意Warning開頭的警告內(nèi)容,最后一個(gè)警告是讓你先解決第一個(gè)警告的內(nèi)容,所以先忽略。我們看它前面的警告:
there were 11 unresolved references to classes or interfaces.You may need to add missing library jars or update their versions.If your code works fine without the missing classes, you can suppressthe warnings with '-dontwarn' options.這是什么意思呢?
第一句話是告訴你原因:有11個(gè)未解析的類或引用。
后面兩句是解決方案。方案一:你可能需要添加丟失的庫或更新它們的版本。方案二:如果你現(xiàn)在代碼運(yùn)行得好好的,也就是沒有它們也沒關(guān)系,那你可以使用-dontwarn來禁止這樣的警告。
那么如何知道有哪些未解析的類或引用呢?
遇到這樣的問題,很簡單,我們往上翻,最終肯定會(huì)找到Warning開頭的日志內(nèi)容:
它的句型是這樣的:Warning: xxxx.xxx.ABC: can't find referenced class xxx.xxx.XYZ
意思就是第三方庫里的ABC引用了XYZ這個(gè)類,但是我在類路徑中找不到XYZ這個(gè)類。
這種情況很常見,比如一個(gè)Java庫使用了另一個(gè)庫的一些類來實(shí)現(xiàn)某個(gè)特性。如果你在使用這個(gè)庫的時(shí)候需要這個(gè)特性,那么你就要把另一個(gè)庫也加進(jìn)來,也就是前面給出的方案一。而如果你不需要用到,如上面的例子,項(xiàng)目中實(shí)際上用不到org.conscrypt包里的內(nèi)容,那么我們?cè)诨煜?guī)則的文件中添加上-dontwarn規(guī)則就可以了。
如何添加?
規(guī)則很簡單。
-dontwarn 類名在上面的日志中,有引用的類,也有被引用的類,這兩者都可以。也就是對(duì)于前面的句型,你既可以使用-dontwarn xxxx.xxx.ABC,也可以使用-dontwarn xxx.xxx.XYZ。
比如上面的例子,我們用
和用
-dontwarn org.conscrypt.OpenSSLProvider -dontwarn org.conscrypt.Conscrypt都可以解決問題。但從這里也可以看出疑惑來了。如果有多個(gè)類呢?是不是每一個(gè)類都要寫上去呢?
當(dāng)然不是。這里的類名是支持通配符的,比如上面,我們可以寫為:
表示禁止org.conscrypt包下的類的警告。但是這里的*是不包含包分隔符的,也就是說它的子包里面的類是不會(huì)被禁止的。如果需要連它下面的包的類也一并禁止,可以使用包含包分隔符的**,也就是如下:
-dontwarn org.conscrypt.**關(guān)于過濾器的更多用法,可以參閱文檔:https://www.guardsquare.com/en/products/proguard/manual/usage#filters
總結(jié)
這是最常見的一類問題,我們來總結(jié)一下這個(gè)套路:
運(yùn)行時(shí)問題
我們?cè)賮砜戳硪活悊栴}。
當(dāng)有人在討論群里求助混淆相關(guān)的問題時(shí),往往會(huì)有人說加-keep。那么-keep是作什么用呢?在什么情況下需要它呢?
在文章開頭一句話介紹過,Proguard 是一個(gè)壓縮、優(yōu)化和混淆Java字節(jié)碼文件的工具。也就是,它所做的不僅僅包括我們所知道的混淆,它會(huì)分析所有的類,找出沒有用的類、字段、方法等把它們刪除掉,從而達(dá)到對(duì)字節(jié)碼的壓縮及優(yōu)化。而如果我們使用了反射去調(diào)用一些類或方法的話,它是不知道的,這樣就會(huì)導(dǎo)致“誤刪”的情況。
所以當(dāng)開啟混淆之后,出現(xiàn)類找不到,方法找不到,屬性找不到時(shí),我們就要使用keep相關(guān)規(guī)則來把它們給留住啦。比如使用-keep class xxxx {*;}保留指定的類名及其成員。
keep規(guī)則有三種(Android新增加的@keep注解不談,這里只講規(guī)則文件的內(nèi)容):
- -keep 保留指定的類名及其成員
- -keepclassmembers 只保留住成員,不能保留住類名
- -keepclasseswithmembers 根據(jù)成員找到滿足條件的所有類,保留它們的類名和成員名
比如我們使用了事件總線,為使方法能保留,我們可以添加如下的規(guī)則:
-keepclassmembers class ** {@org.greenrobot.eventbus.Subscribe <methods>; }表示保留所有類中的有@org.greenrobot.eventbus.Subscribe注解的方法。<methods>是指方法,如果要保留字段,則使用<fields>。
反正就是找不到類或成員,你就keep住。這里的keep規(guī)則也是很靈活的,比如除了上面指定的類,你也可以指定為接口:
-keepclassmembernames,allowobfuscation interface * {@retrofit2.http.* <methods>; }可以指定繼承自某個(gè)類的所有類,如:
-keep public class * extends android.app.Service可以指定實(shí)現(xiàn)某個(gè)接口的類,如:
-keep class * implements com.google.gson.TypeAdapterFactory可以指定某個(gè)包下的所有類(包括子包)及所有成員,如:
-keep class com.tencent.stat.** {* ;}除此之外,我們還可能會(huì)遇到其他問題,這里把其他常用的規(guī)則也快速過一下:
注解解析不到?
-keepattributes *Annotation*泛型轉(zhuǎn)換失敗?
-keepattributes Signature枚舉?
-keepclassmembers enum * { *; }掌握以上幾條規(guī)則,通常可以解決絕大多數(shù)混淆以后運(yùn)行出錯(cuò)的問題了。
本篇講到這里,希望對(duì)被以上常見問題所困擾的同學(xué)有所幫助。這里需要注意一下,對(duì)于第一類問題,在編譯的時(shí)候就能暴露出來,而第二類問題,只有在運(yùn)行到相關(guān)代碼時(shí)才會(huì)出現(xiàn)。所以如果一個(gè)項(xiàng)目以前是不使用混淆的話,在啟用混淆之后一定要做好回歸測(cè)試。
總結(jié)
以上是生活随笔為你收集整理的ProGuard常见问题及解决套路的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我们为什么要使用 Markdown
- 下一篇: 运行命令大全