最近在研究apk瘦身時巧鸭,發(fā)現(xiàn)代碼混淆有很大作用孵运,所以就去簡單研究了一下。
我認為,代碼混淆可以保護核心功能不泄漏以及apk瘦身寇僧;但是也有個缺點:就是人為的可能會把不能混淆的代碼混淆,導致crash频祝。
經(jīng)過整理發(fā)現(xiàn)代碼混淆可以分為三部分:
- 基本指令以及一些固定不混淆的代碼沧踏;
- 某些第三方包;
- 自己書寫的一些不要混淆的代碼欣簇。
注:其中第二部分规脸,可以根據(jù)情況擴展或去掉某些未曾用到的指令坯约;第三部分需要根據(jù)自己的情況去添加;
代碼混淆
俗話說莫鸭,授人以魚不如授人以漁闹丐,但是先有魚總是好的,所以接下來就會先介紹一下代碼混淆的基本使用被因。
魚
1. 開啟混淆代碼
在app module下的gradle文件中
android {
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
默認minifyEnable是false卿拴,我們只需要改為true就開啟了混淆。這里只需要在release的時候開啟混淆即可氏身,代碼混淆會加長APK的生成時間巍棱,而且android studio2.0以后使用instance run會停用ProGuard。
這時候打包蛋欣,安裝航徙,如果引入了其他第三方的代碼的話,不出意外時會crash的陷虎,雖然proguard-android.txt文件中已經(jīng)包含了一些不混淆的指令了到踏,那些事系統(tǒng)常規(guī)的指令,對于自定義的一些指令就需要自己在app下的proguard-rules.pro文件中去定義尚猿。
2. PROGUARD模板
我看這個庫里邊的內(nèi)容也用分割線做了一下簡單的分割窝稿,包含的也是上文說到的三部分,下面簡單介紹一下具體包含的內(nèi)容凿掂。
基本指令:包含了壓縮級別伴榔,忽略警告,混淆警告等庄萎;
固定不混淆的代碼:包含繼承四大組件中的內(nèi)容踪少,support包,view相關糠涛、序列化相關援奢、R文件、枚舉忍捡、native方法等集漾;
第三方包:這一部分需要根據(jù)項目情況去處理,這里支持了我們公司常用框架里邊的第三方包砸脊;
自己書寫的不需要混淆的代碼:這里就需要根據(jù)自己的情況去書寫了具篇,而我們只需要知道哪些東西是不能混淆的就能編寫了。
下面來說一下凌埂,混淆的規(guī)則:第三方庫栽连。一般都會提供混淆規(guī)則,如果沒有提供,報錯后秒紧,我們就可以保護報錯的類,不讓其混淆挨下,實在不行就用最暴力的解決辦法熔恢,把其全部代碼都不混淆。
運行時動態(tài)改變代碼臭笆。一般例如反射叙淌,實體類。
被JNI中調(diào)用的類愁铺。
WebView中Js調(diào)用的方法鹰霍。
View相關的類和事件。
這兩步過后茵乱,基本就完成了代碼混淆茂洒,運行后發(fā)現(xiàn)問題再解決即可,下面就開始介紹一些混淆中的語法瓶竭。
漁
1督勺、常用命令
命令 | 作用 |
---|---|
-keep | 防止該類所有內(nèi)容被移除或重命名 |
-keepnames | 防止類和成員被重命名 |
-keepclassmembers | 防止成員被移除或者被重命名 |
-keepclasseswithmembers | 防止擁有該成員的類和成員被移除或者被重命名 |
-keepclasseswithmembernames | 防止擁有該成員的類和成員被重命名 |
2. 常用規(guī)則
類:需要使用完全限定名;
*:通配符斤贰,任意字符串智哀,不包含包名分隔符(.);
**:通配符荧恍,任意字符串瓷叫,包含包名分隔符(.);
extends:繼承某類的類送巡;
implement:實現(xiàn)某接口的類摹菠;
$:內(nèi)部類;
<init>:所有構(gòu)造方法授艰;
<fields>:所有成員變量辨嗽;
<methods>:所有方法;
…:任意參數(shù)淮腾;
修飾符:public private protected
3. 例子
含義 | 指令語句 |
---|---|
不混淆某個類 | -keep public class packageName.className{ *; } |
不混淆某個包的所有類 | -keep class packageName.**{ *; } |
不混淆某個類的子類 | -keep public class * extends packageName.className{ *; } |
不混淆某個接口的子類 | -keep public class * implements packageName.className{ *; } |
不混淆某個類的構(gòu)造方法 | -keepclassmembers class packageName.className{ public <init style="box-sizing: border-box;">(); }</init> |
不混淆某個類的某個方法 | -keepclassmembers class packageName.className{ public void methodName(…); } |
不混淆某個類的內(nèi)部類 | -keep class packageName.className$*{ *; } |
混淆的步驟
上文中介紹了代碼的混淆糟需,其實Android的混淆中還包括了資源壓縮,整個過程包括:壓縮谷朝、優(yōu)化洲押、混淆、以及預校驗圆凰,其中第四步在Android中可不要杈帐,默認是去掉了的,另外三個都是默認開啟的。
- 壓縮:會移除未被使用的類和成員變量挑童,會在優(yōu)化后再次被執(zhí)行累铅;
- 優(yōu)化:在字節(jié)碼級別執(zhí)行優(yōu)化,讓應用運行的更快站叼;
- 混淆:增大反編譯難度娃兽,類和類成員會被隨機命名,除非用keep保護尽楔。
下面再簡單介紹一下再開啟資源壓縮投储,和開啟代碼混淆在一起加上shrinkResources true即可。但是這時候所有未被使用的資源都會被移除阔馋,但是有的資源我們可能是動態(tài)使用的玛荞,就需要保留。
那么就可以創(chuàng)建一個包含標簽的XML文件呕寝,并使用tools:keep屬性指定保留的資源勋眯,使用tools:discard屬性指定要刪除的資源;多個就使用逗號隔開即可壁涎,還可以使用*通配符凡恍。例如:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
tools:discard="@layout/unused2" />
對于資源的保留,還有一個嚴格應用的檢查怔球,開啟后嚼酝,在使用Resources.getIdentifier()就可以根據(jù)動態(tài)生成的字符串查詢資源名字,例如:
String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());
使用方式也很簡單竟坛,如下:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
到這里闽巩,混淆相關的就基本上介紹完了,然后再總結(jié)一下需要注意的東西:
- 一定要根據(jù)混淆規(guī)則判斷自己的代碼是否需要混淆担汤;
- 測試的版本一定需要打包后的apk涎跨;
還遺留的問題是:錯誤日志的定位問題?思路是根據(jù)輸出的mapping文件去對應崭歧,沒有詳細的去落實隅很,實際遇到了再補上具體的解決方案。
本文章參考文章
還遺留的問題是:錯誤日志的定位問題率碾?思路是根據(jù)輸出的mapping文件去對應叔营,沒有詳細的去落實,實際遇到了再補上具體的解決方案所宰。