減少Android APK的大小99.99%

duanhao發表於2021-09-09

我們將這一原則應用於Android。建立可以安裝在執行Oreo的裝置上的最小的應用程式。

測量基線

我們將開始使用Android Studio生成的預設應用。我們,簽署應用程式,並使用位元組來測量檔案大小stat -f%z $filename

我們還將在執行Oreo的Nexus 5x上安裝APK,以確保一切正常。

美麗。我們的APK重量約為1.5Mb。

APK分析儀

1.5Mb似乎很多考慮到我們的應用程式,所以讓我們探索這個專案,並尋找任何快速的勝利。Android Studio已經生成:

  • MainActivity,延伸AppCompatActivity

  • 具有ConstraintLayout根檢視的佈局檔案。

  • 包含三種顏色的值檔案,一個字串資源和一個主題。

  • AppCompatConstraintLayout支援庫。

  • 一 AndroidManifest.xml

  • 方形,圓形和前景啟動器圖示PNG。

圖示看起來像最簡單的目標,因為總共有15張圖片,下面有2個XML檔案mipmap-anydpi-v26。我們來量化使用Android Studio的。

與我們的初始假設相反,似乎我們的Dex檔案是最大的,資源僅佔APK大小的20%。

檔案 尺寸
classes.dex 74%
res 20%
resources.arsc 4%
META-INF 2%
AndroidManifest.xml

我們來分析一下每個檔案的作用。

Dex檔案

classes.dex是73%的最大罪魁禍首,因此是我們的第一個目標。此檔案包含所有編譯程式碼,並且還引用了Android框架和支援庫中的外部方法。

android.support軟體包引用了超過13,000種方法,對於Hello World應用程式來說,這似乎過多了。

資源

我們的res目錄有大量的佈局檔案,可繪製和動畫,在Android Studio的UI中不會立即顯示。再次,這些已經從支援庫中拉入,並且佔據了APK大小的大約20%。

resources.arsc檔案還包含對這些資源中的每一個的引用。

簽名

META-INF資料夾包含CERT.SF,,MANIFEST.MFCERT.RSA檔案,這是所必需的。如果攻擊者修改了我們的APK中的程式碼,則簽名將不匹配,這意味著使用者將被儲存以執行第三方惡意軟體。

MANIFEST.MF列出APK中的檔案,而CERT.SF包含清單的摘要,以及每個檔案的單個摘要。CERT.RSA包含用於驗證其完整性的公鑰CERT.SF

這裡沒有明顯的目標。

AndroidManifest

AndroidManifest看起來與我們原始的輸入檔案非常相似。唯一的例外是字串和可繪製的資源已經被替換為整數資源ID,從頭開始0x7F

啟用分類

我們還沒有嘗試在我們的應用程式build.gradle檔案中實現小型化和資源縮減。讓我們一起去吧

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile(
              'proguard-android.txt'), 'proguard-rules.pro'
        }
    }}
-keep class com.fractalwrench.** { *; }

設定minifyEnabled為true可以使從我們的應用程式中未使用的程式碼。它也模糊符號名稱,使得更難以逆向工程的應用程式。

shrinkResources將從我們的APK中刪除任何不直接引用的資源。如果您使用反射來間接訪問資源,這可能會導致問題,但這不適用於我們的應用程式。

786 Kb(減少50%)

我們將APK大小減少一半,對我們的應用程式沒有明顯的影響。如果您還沒有啟用minifyEnabledshrinkResources在您的應用程式中,這是您應該從這篇文章中刪除的最重要的一件事。您可以輕鬆地節省幾兆位元組的空間,只需幾個小時的配置和測試。

AppCompat,我們幾乎不知道你們

classes.dex現在佔用了APK的57%。我們的Dex檔案中的大多數方法引用屬於該android.support包,因此我們將刪除該支援庫。為此,我們將:

  • 從我們build.gradle完全刪除依賴關係塊

dependencies {
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'}
  • 更新MainActivity擴充套件 android.app.Activity

public class MainActivity extends Activity
  • 更新我們的佈局使用一個TextView

  • 從元素中刪除styles.xml並刪除該android:theme屬性,這是API 15及更高版本支援的更有效的檔案格式。

    幸運的是,Google已經最佳化了我們的繪圖,儘管如果不是這樣,也可以最佳化並從PNG中刪除不必要的後設資料。

    讓我們成為一個不好的公民,並用一個1畫素的黑點替換所有的發射圖示,放在不合格的res/drawable資料夾中。該影像重67頁。

    6808位元組(減少94%)

    我們已經擺脫了我們幾乎所有的資源,所以我們看到我們的APK大小減少了95%,這並不奇怪。以下專案仍然參考resources.arsc

    • 1個佈局檔案

    • 1字串資源

    • 1啟動器圖示

    我們從頂部開始吧。

    佈局檔案(6262位元組,減少9%)

    Android框架將,並自動建立一個TextView物件設定為contentViewActivity

    我們可以嘗試透過刪除XML檔案來跳過中間人,並以程式設計方式設定contentView。我們的資源大小會減少,因為XML檔案少一些,但是我們的Dex檔案會增加,因為我們將引用其他TextView方法。

    TextView textView = new TextView(this);textView.setText("Hello World!");setContentView(textView);

    看起來我們的權衡已經有效了,我們下降到5710位元組。

    應用名稱(6034位元組,減少4%)

    我們刪除strings.xml,並android:label在AndroidManifest 替換為“A”。這可能看起來像是一個小小的變化,但刪除一個條目resources.arsc,減少清單中的字元數,並從res目錄中刪除一個檔案。每一點幫助 - 我們剛剛儲存了228個位元組。

    啟動器圖示(5300位元組,減少13%)

    該在Android平臺庫告訴我們,在APK每個資源被引用的resources.arsc一個整數ID。這些ID有兩個名稱空間:

    0x01:系統資源(預安裝在framework-res.apk中)

    0x7f:應用程式資源(捆綁在應用程式中.apk)

    那麼如果我們引用0x01名稱空間中的資源,我們的APK會發生什麼?我們應該能夠獲得更好的圖示,同時減少我們的檔案大小。

    android:icon="@android:drawable/btn_star"

    不用說,但是您不應該在生產應用程式中相信系統資源。這一步將失敗Google Play驗證,並且考慮到某些製造商已經知道重新定義,請謹慎行事。

    清單(5252位元組,減少1%)

    我們還沒有觸及清單。

    android:allowBackup="true"android:supportsRtl="true"

    刪除這些屬性可以節省48個位元組。

    保衛駭客(4984位元組,減少5%)

    它看起來像BuildConfigR仍然包括在塞米松檔案。

    -keep class com.fractalwrench.MainActivity { *; }

    最佳化我們的Proguard規則將剝離這些類。

    混淆(4936位元組,減少1%)

    讓我們的Activity成為一個混淆的名字。Proguard自動為常規類執行此操作,但是由於可以透過Intents呼叫Activity類名稱,因此預設情況下不會進行混淆。

    MainActivity - > c.java

    com.fractalwrench.apkgolf - > cc

    META-INF(3307位元組,減少33%)

    目前,我們正在使用v1和v2簽名簽署我們的應用程式。這似乎是浪費的,特別是透過雜湊整個APK,v2提供。

    我們的v2簽名在APK分析器中不可見,因為它在APK檔案本身中被包含為二進位制塊。我們的v1簽名是可見的,CERT.RSA以及CERT.SF檔案的形式。

    讓我們取消選中Android Studio介面中的v1簽名核取方塊,並生成一個已簽名的APK。我們也會嘗試相反的做法。

    簽名 尺寸
    V1 3511
    V2 3307

    看來我們現在將使用v2。

    我們要去哪裡,我們不需要IDE

    現在是手動編輯我們的APK了。我們將使用以下命令:

    # 1. Create an unsigned apk./gradlew assembleRelease# 2. Unzip archiveunzip app-release-unsigned.apk -d app# Do any edits# 3. Zip archivezip -r app app.zip# 4. Run zipalignzipalign -v -p 4 app-release-unsigned.apk app-release-aligned.apk# 5. Run apksigner with v2 signatureapksigner sign --v1-signing-enabled false --ks $HOME/fake.jks --out signed-release.apk app-release-unsigned.apk# 6. Verify signatureapksigner verify signed-release.apk

    可以在找到APK簽名的詳細概述。總而言之,gradle生成一個無符號歸檔,zipalign更改未壓縮資源的位元組對齊方式,以便在APK載入時提高RAM使用率,最後,APK被加密地簽名。

    我們的未簽名和未對齊的APK重量在1902位元組,這表明這個過程增加了大約1Kb。

    檔案大小差異(2608位元組,減少21%)

    奇怪的!解壓縮未對齊的APK並手動刪除META-INF/MANIFEST.MF,儲存我們543位元組。如果有人知道為什麼會這樣,請聯絡!

    我們現在已經在我們簽名的APK中下載了3個檔案。但是,我們也可以擺脫resources.arsc,因為我們沒有定義任何資源!

    這使我們有清單和classes.dex檔案,每個檔案大小相同。

    壓縮駭客(2599位元組,減少0.5%)

    我們將所有剩餘的字串更改為“c”,將我們的版本更新為26,然後生成一個已簽名的APK。

    compileSdkVersion 26
        buildToolsVersion "26.0.1"
        defaultConfig {
            applicationId "c.c"
            minSdkVersion 26
            targetSdkVersion 26
            versionCode 26
            versionName "26"
        }
    
        進行此最佳化,因為諸如校驗和和偏移之類的各種機制使人工編輯變得更加困難。
    

    然而,為了縮短長篇小說,事實證明,APK安裝的唯一要求是classes.dex檔案必須存在。因此,我們只需刪除原始檔案,touch classes.dex在終端中執行,並使用空檔案減少10%。

    有時最愚蠢的解決方案是最好的。

    瞭解清單(1961位元組,減少0%)

    我們的未簽名APK的清單是二進位制XML格式,似乎沒有正式記錄。我們可以使用編輯器來操作檔案內容。

    我們可以猜到檔案頭中的幾個有趣的專案 - 前四個位元組的編碼38,這是與Dex檔案相同的版本號。接下來的兩個位元組編碼660,這是檔案大小的方便。

    我們嘗試透過將targetSdkVersion設定為1,並將檔案大小標題更新為一個位元組659。不幸的是,Android系統拒絕將其視為無效的APK,所以看起來這裡有一些額外的複雜性。

    不瞭解清單(1777位元組,減少9%)

    我們在整個檔案中輸入啞字元,然後嘗試安裝APK,而不更改檔案大小。這將確定是否存在校驗和,或者如果我們的更改使檔案頭中的偏移值無效。

    令人驚訝的是,以下清單被解釋為執行Oreo的Nexus 5X上的有效APK:我想我可以聽到Android框架工程師負責保持BinaryXMLParser.java尖叫聲大聲地進入枕頭。

    為了最大化我們的收益,我們將用空位元組替換這些虛擬字元。這將使您可以更輕鬆地檢視HexFiend中檔案的重要部分,並從先前的壓縮駭客中獲取位元組數。

    UTF-8清單

    這些是清單的基本元件,沒有這些元件,APK無法安裝。一些事情很明顯 - 比如清單和包標籤。versionCode和包名也可以在字串池中找到。

    十六進位制顯示

    以十六進位制檢視檔案顯示檔案頭中描述字串池和其他值(如檔案大小)的值0x9402。字串也有一個有趣的編碼 - 如果它們超過8個位元組,它們的總長度在前面的2個位元組中指定。

    然而,這看起來並不像在這裡有更多的收穫。

    完成了嗎?(1757位元組,減少1%)

    我們來檢查一下最終的APK。畢竟這一次,我透過v2簽名將我的名字留在了APK中。讓我們建立一個利用壓縮駭客的新金鑰庫。

    這節省了我們20個位元組。

    階段5:驗收

    1757 位元組很小,據我所知,這是存在的最小的APK。

    不過,我有信心在Android社群的某個人進行進一步的最佳化,這將會打敗我的分數。

    原文連結:http://www.apkbus.com/blog-873055-75894.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1343/viewspace-2813428/,如需轉載,請註明出處,否則將追究法律責任。

相關文章