減包與APK-Checker原理分析

為啥要優(yōu)化包體積

  • 推廣成本
  • 下載轉化率
  • 運行內存
  • 安裝時間


    體積優(yōu)化思維導圖.png

APK背景知識

對于APK瘦身,首先我們必須了解的知識點是APK的文件結構刁愿,那么上圖:


APK文件結構.png
  • Dex : 一般情況下,Android 應用在打包時通過 Android SDK 中的 dx 工具將 Java 字節(jié)碼轉換為 Dalvik 字節(jié)碼拖云。被DEX編譯后可供Dalvik/ART虛擬機所理解的文件格式
  • Res目錄
    res : 是 resource 的縮寫吴超,這個目錄存放資源文件奴烙,會自動生成對應的 ID 并映射到 .R 文件中庄呈,訪問直接使用資源 ID蜕煌。
  • Assets文件夾 :
    存放需要打包到APK中的靜態(tài)文件,assets不會自動生成對應的 ID诬留,而是通過 AssetManager 類的接口獲取斜纪。
  • Native庫 :
    通常我們的so庫都屬于這個范疇。
  • META-INF :
    存放應用程序簽名和證書的目錄文兑,簽名信息可以驗證 APK 文件的完整性盒刚。
  • resources.arsc :
    記錄著資源文件和資源 ID 之間的映射關系,用來根據(jù)資源 ID 尋找資源绿贞。

由此可見:安裝包的優(yōu)化可以籠統(tǒng)的分為:資源優(yōu)化因块、DEX文件優(yōu)化兩大部分


一、資源文件減包分析

優(yōu)化思路 優(yōu)化 -> 去重 -> 混淆

1.1 優(yōu)化

在圖片的格式選擇上

圖片使用建議

webP壓縮效果展示.png

此外 沒有透明通道的PNG可以轉換成jpg格式樟蠕,有透明通道的png可以轉成webP格式贮聂。以節(jié)省空間的占用

1.2 壓縮

在Android編譯過程中靠柑,下面代碼中的文件格式不支持壓縮:

/* these formats are already compressed, or don't compress well */
static const char* kNoCompressExt[] = {
    ".jpg", ".jpeg", ".png", ".gif",
    ".wav", ".mp2", ".mp3", ".ogg", ".aac",
    ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
    ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
    ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
    ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"
};

Question : 為什么谷歌官方不支持這些文件格式的壓縮寨辩?

  • 1.時間與空間的收益
    如果文件是沒有壓縮的,系統(tǒng)可以利用 mmap 的方式直接讀取歼冰,而不需要一次性讀到內存中

  • 2.壓縮效果不明顯
    如上方的注釋所說大部分文件壓縮效果并不明顯靡狞,比如jpg、png格式的圖片壓縮率只有3%-5%隔嫡,收益不大


1.3 去重

各工具優(yōu)缺點以及準確性分析(unUsedResource)

Lint

Lint中提供了unUsedResource和unUsedId去檢測無用甸怕、冗余的資源甘穿。

  • 弊端
    Lint作為靜態(tài)代碼檢查工具分析的是編譯前的代碼,比如Lint會忽略Proguard的代碼shrink梢杭,所以Lint不能檢查出這些無用代碼引用的無用資源温兼。

Matrix-ApkChecker

輸入apk檢查 解決了Lint只能檢查編譯前資源的缺點

  • 弊端
    類似于循環(huán)引用的資源引用方式無法被正確判定為無用資源

1.4 混淆

我們的項目中已經(jīng)有了混淆的配置,但是并沒有針對資源混淆的配置武契,資源混淆的思路就是把資源和文件的名字混淆成段路徑:

R.string.name -> R.string....               res/drawable/icon -> res/s/a

Question : 為什么資源混淆可以減少APK體積募判?

  • resource.arsc的文件格式解析
  • 解析我們的apk可以發(fā)現(xiàn)resource.arsc與META-INF文件夾下的三個文件大小很大,原因就是他們內部保存了每個資源名稱咒唆,我們在項目中有時候為了不造成沖突届垫,就把資源名起的很長,那么這樣就會導致apk的包很大全释,但是我們知道Android中的混淆是不會對資源文件進行混淆的装处,所以這時候我們就可以通過這個思路來減小包apk的大小了

shrinkResources資源壓縮功能

在gradle中的android閉包中添加 shrinkResource true minifyEnabled true
如果ProGuard將無用代碼移除,則代碼引用的資源也被標記為無用資源浸船,然后將其移除

  • 弊端
    沒有從根本上處理resource.arsc文件 較為占空間的resource.arsc仍沒有得到改善
    僅將資源文件替換為空文件
    這樣實際上文件數(shù)量并沒有得到改善妄迁,而且resource.arsc等文件的體積也沒有任何變化
image.png

在Android編譯過程中,Java Compiler會將代碼中的資源引用根據(jù)R文件直接替換為常量糟袁,而R文件中的文件資源ID默認為連續(xù)的判族,刪除某些資源會導致ID與資源無法一一對應

解決辦法:可以使用資源混淆工具 AndResGurad 對打包好的apk進行處理

處理后release包大小立減1M

QQ瀏覽器截圖20191204093020.png


二、DEX文件減包

DEX文件格式解析

Dex分包

faceBook - reDex


三形帮、 Matrix如何實現(xiàn)搜索APK中無用的資源文件

  • 首先通過讀取R.txt獲取apk中聲明的所有資源 寫入set中;
  • 通過讀取smali文件中引用資源的指令 得出class中引用的資源Set周叮;
  • 通過ApkTool解析res目錄下的xml文件辩撑、AndroidManifest.xml 以及 resource.arsc 得出資源之間的引用關系;
  • 1.遍歷DexFile仿耽,并使用Baskmali庫將其編譯成Smali文件
 private void decodeCode() throws IOException {
    for (String dexFileName : this.dexFileNameList) {
      DexBackedDexFile dexFile = DexFileFactory.loadDexFile(new File(this.inputFile, dexFileName), Opcodes.forApi(15));

      options = new BaksmaliOptions();
      List classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());

      for (ClassDef classDef : classDefs) {
        String[] lines = ApkUtil.disassembleClass(classDef, options);
        if (lines != null)
          readSmaliLines(lines);
      }
    }
    BaksmaliOptions options;
  }
  • 2.遍歷Smali文件啊易,找到字符串常量
 private void readSmaliLines(String[] lines) {
    if (lines == null) {
      return;
    }
    for (String line : lines) {
      line = line.trim();
      if (!Util.isNullOrNil(line))
        if (line.startsWith("const")) {
          String[] columns = line.split(",");
          if (columns.length == 2) {
            String resId = parseResourceId(columns[1].trim());
            if ((!Util.isNullOrNil(resId)) && (this.resourceDefMap.containsKey(resId)))
              this.resourceRefSet.add(this.resourceDefMap.get(resId));
          }
        }
        else if (line.startsWith("sget")) {
          String[] columns = line.split(" ");
          if (columns.length == 3) {
            String resourceRef = parseResourceNameFromProguard(columns[2]);
            if (Util.isNullOrNil(resourceRef))
              continue;
            if (this.styleableMap.containsKey(resourceRef))
            {
              for (String attr : (Set)this.styleableMap.get(resourceRef))
                this.resourceRefSet.add(this.resourceDefMap.get(attr));
            }
            else
              this.resourceRefSet.add(resourceRef);
          }
        }
    }
  }
  • 3.遍歷XML茴晋、resource.arsc
    private void decodeResources() throws IOException, InterruptedException, AndrolibException, XmlPullParserException {
        File manifestFile = new File(inputFile, ApkConstants.MANIFEST_FILE_NAME);
        File arscFile = new File(inputFile, ApkConstants.ARSC_FILE_NAME);
        File resDir = new File(inputFile, ApkConstants.RESOURCE_DIR_NAME);
        if (!resDir.exists()) {
            resDir = new File(inputFile, ApkConstants.RESOURCE_DIR_PROGUARD_NAME);
        }

        Map<String, Set<String>> fileResMap = new HashMap<>();
        Set<String> valuesReferences = new HashSet<>();

        ApkResourceDecoder.decodeResourcesRef(manifestFile, arscFile, resDir, fileResMap, valuesReferences);

        Map<String, String> resguardMap = config.getResguardMap();

        for (String resource : fileResMap.keySet()) {
            Set<String> result = new HashSet<>();
            for (String resName : fileResMap.get(resource)) {
               if (resguardMap.containsKey(resName)) {
                   result.add(resguardMap.get(resName));
               } else {
                   result.add(resName);
               }
            }
            if (resguardMap.containsKey(resource)) {
                nonValueReferences.put(resguardMap.get(resource), result);
            } else {
                nonValueReferences.put(resource, result);
            }
        }

        for (String resource : valuesReferences) {
            if (resguardMap.containsKey(resource)) {
                resourceRefSet.add(resguardMap.get(resource));
            } else {
                resourceRefSet.add(resource);
            }
        }

        for (String resource : resourceRefSet) {
            readChildReference(resource);
        }

        for (String resource : unusedResSet) {
            if (ignoreResource(resource)) {
                resourceRefSet.add(resource);
                ignoreChildResource(resource);
            }
        }
    }

參考文獻:

# Android App包瘦身優(yōu)化實踐-美團

# Matrix-wiki

# 支付寶 App 構建優(yōu)化解析:Android 包大小極致壓縮

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子忧便,更是在濱河造成了極大的恐慌,老刑警劉巖粒褒,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仗岸,死亡現(xiàn)場離奇詭異,居然都是意外死亡奕删,警方通過查閱死者的電腦和手機俺泣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伏钠,你說我怎么就攤上這事横漏。” “怎么了熟掂?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵缎浇,是天一觀的道長。 經(jīng)常有香客問我赴肚,道長华畏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任尊蚁,我火速辦了婚禮亡笑,結果婚禮上,老公的妹妹穿的比我還像新娘横朋。我一直安慰自己仑乌,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布琴锭。 她就那樣靜靜地躺著晰甚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪决帖。 梳的紋絲不亂的頭發(fā)上厕九,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音地回,去河邊找鬼扁远。 笑死,一個胖子當著我的面吹牛刻像,可吹牛的內容都是我干的畅买。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼细睡,長吁一口氣:“原來是場噩夢啊……” “哼谷羞!你這毒婦竟也來了?” 一聲冷哼從身側響起溜徙,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤湃缎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蠢壹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗓违,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年知残,在試婚紗的時候發(fā)現(xiàn)自己被綠了靠瞎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡求妹,死狀恐怖乏盐,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情制恍,我是刑警寧澤父能,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站净神,受9級特大地震影響何吝,放射性物質發(fā)生泄漏。R本人自食惡果不足惜鹃唯,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一爱榕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坡慌,春花似錦黔酥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至熄求,卻和暖如春渣玲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弟晚。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工忘衍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卿城。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓淑履,卻偏偏與公主長得像,于是被迫代替她去往敵國和親藻雪。 傳聞我的和親對象是個殘疾皇子秘噪,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內容