玩轉APK:實現Android APK瘦身99.99%,厲害了~~

2022-01-10 開發者全社區

熱文導讀 | 點擊標題閱讀

2017 Android秋招面試總結 && 面試資源推薦

Android高級進階架構系列教程視頻分享

吊炸天!74款APP完整源碼

摘要: 如何瘦身是 APK 的重要優化技術。APK 在安裝和更新時都需要經過網絡下載到設備,APK 越小,用戶體驗越好。本文作者通過對 APK 內在機制的詳細解析,給出了對 APK 各組成成分的優化方法及技術,並實現了一個基本 APK 的最小化過程。

 正文:

高爾夫運動中,分數最小者勝出。

讓我們將這一原則應用到 Android App 開發中。我們將玩轉一個稱為「ApkGolf」的 APK,目的是創建一個儘可能具有最少字節數的 App,並可安裝在運行 Oreo 的設備上。

一開始,我們用 Android Studio 生成一個預設的 App,創建密鑰庫(Keystore)

(https://developer.android.com/studio/publish/app-signing.html#generate-key) 

並對 App 籤名,然後使用命令stat -f%z $filename測定生成 APK 文件的字節數大小。

進一步,為確保該 APK 工作正常,我們將在一臺運行 Oreo 的 Nexus 5x 手機上安裝它。

看上去挺漂亮。但是現在我們的 APK 大小近乎 1.5Mb。

考慮到我們 App 的功能非常簡單,1.5Mb 的規模看上去過於臃腫了。因此,我們要深入了解一下該項目,看看是否有一些能立竿見影地削減文件大小的地方。Android Studio 生成了:

擴展AppCompatActivity而得到的MainActivity;

使用根視圖ConstraintLayout的布局文件;

Value 文件,其中包含三種顏色、一個字符串資源(Resource)和一個主題(Theme);

AppCompat和ConstraintLayout的支持庫;

一個AndroidManifest.xml文件;

PNG 格式的啟動圖標,分別是正方形、圓形和前臺的。

看上去首當其衝的目標是啟動圖標文件,因為 APK 中共包含了 15 個圖像文件,並且在mipmap-anydpi-v26下還有兩個 XML 文件。下面,讓我們使用 Android Studio 的 APK Analyser

(https://developer.android.com/studio/build/apk-analyzer.html) 

對該 APK 文件做一個定量分析。

給出的結果與我們的最初假設大相逕庭,其中顯示 Dex 文件是大頭,而上述資源僅佔 APK 大小的 20%。

文件大小佔比classes.dex74%res20%resources.arsc4%META-INF2%AndroidManifest.xml<1%

下面讓我們逐個分析每個文件的行為。

看上去罪魁禍首是classes.dex文件,它佔據了 73% 的空間,因而它成為我們的首要削減目標。該文件為 Dex 格式

 (https://source.android.com/devices/tech/dalvik/dex-format) ,

其中包含了我們的全部編譯後代碼,以及對 Android 框架和支持庫中外部方法的引用。

然而android.support軟體包中引用了超過 13000 種的方法,對於一個簡單的「Hello World」App 而言,完全沒有必要。

目錄「res」中包含了大量的布局(Layout)文件、Drawable 和動畫,它們並非在 Android Studio UI 中立刻可見。同樣,它們也是由支持庫推入其中的,約佔 APK 規模的 20%。

在resources.arsc文件中,還包含了對每個資源的引用。

目錄「META-INF」中包含有CERT.SF、MANIFEST.MF和CERT.RSA文件,這些文件都需要 v1 APK 籤名 

(https://source.android.com/security/apksigning/v2#v1-verification) 。

如果有攻擊者修改了我們 APK 中的代碼,籤名就會不匹配。這一機制保障了用戶能避免執行第三方惡意軟體的風險。

在MANIFEST.MF文件中列出了 APK 中的所有文件。其中,CERT.SF文件中包含了文件清單的摘要,以及每個文件的獨立摘要。CERT.RSA文件中包含了一個公鑰,用於驗證CERT.SF文件的完整性。

在籤名文件中,沒有目標明顯可優化。

看上去AndroidManifest文件非常類似於我們的原始輸入文件。唯一差別在於,文件中的字符串和 Drawable 等資源被整數資源 ID 所替代,這些 ID 以0x7F開頭。

我們尚未在 App 的build.gradle文件中設置允許最小化(Minification)和資源收縮(Resource Shrinking)。我們現在做此設置:

android {    buildTypes {        release {            minifyEnabled true            shrinkResources true            proguardFiles getDefaultProguardFile(              'proguard-android.txt'), 'proguard-rules.pro'        }    }}

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

將minifyEnabled屬性設置為「true」值,這將啟用 Proguard

(https://www.guardsquare.com/en/proguard) ,

該功能將從 App 中剝離出那些未使用的代碼,並對符號的名稱做模糊化處理,使得 App 難以被反向工程。

設置shrinkResources屬性,將會在 APK 中移除任何並非直接引用的資源。這時如果我們使用反射機制間接地訪問資源,就會導致問題,但是本文給出的 App 並不存在這樣的問題。

我們已經實現了 APK 規模減半,並未對我們的 APP 有任何可見的影響。

對於那些尚未在 App 中啟用AndroidManifest.xml和shrinkResources的開發人員,這是本文給出的最需要重視的並應學會的技巧。他們僅花費數小時做配置和測試,就能輕鬆地削減數兆的規模。

現在classes.dex文件已削減到佔用 APK 的 57%。在我們的 Dex 文件中,大多數方法引用屬於android.support軟體包,因此我們將要去除該支持庫。具體做法為:

public class MainActivity extends Activity

<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:gravity="center"    android:text="Hello World!" />

天哪,我們剛剛實現了近十倍的削減,即從 786Kb 削減到 108Kb。唯一可見的更改是工具條(Toolbar)的顏色,現在它使用了預設的 OS 主題。

目錄「res」現在佔用 APK 規模約 95%,原因是所有的加載圖標。如果這些 PNG 圖片是由我們自己的設計師所給出的,那麼我們可以嘗試 將它們轉換為 WebP 格式,該格式更加高效,並被 API 15 及以上所支持。

幸運的是,Google 已經優化了我們的 Drawable。即便沒有這種優化,ImageOptim 也可優化 PNG 並從中剝離不必要的元數據。

讓我們當一次壞人,將我們所有的加載圖標替換為單一的單像素黑點,並置於未驗證的res/drawable目錄中。圖片大小約 67 個字節。

我們已經移除了幾乎全部的資源,因此毫不奇怪 APK 規模已經削減了約 95%。但是resources.arsc依然引用了如下項:

讓我們從第一項著手。

Android 框架會膨脹我們的 XML 文件

 (https://developer.android.com/reference/android/view/LayoutInflater.html) ,

並自動創建一個TextView對象,用於Activity對象的contentView。

我們可以嘗試一些跳過中間的過程,具體做法是移除 XML 文件,並使用程序設置contentView。這樣會降低資源的規模,因為我們減少了一個 XML 文件。但是 Dex 文件將會增大,因為我們引用了額外的TextView方法。

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

讓我們查看一下這一權衡做法的工作情況,它削減了 5710 個字節。

 App 名稱(優化為 6034 字節,削減 4%)

下面我們將刪除strings.xml文件,並將AndroidManifest中的android:label屬性值更改為「A」。這看上去是一個小更改,但是它從resources.arsc中刪除了一項,削減了 Manifest 文件中的字符數,並從「res」目錄中移除了一個文件。略有裨益,我們削減了 228 個字節。

Android Platform 代碼庫中的resources.arsc的文檔

(https://android.googlesource.com/platform/frameworks/native/+/jb-dev/libs/utils/README) 

告訴我們,APK 中的每個資源通過resources.arsc中的一個整數 ID 引用。這些 ID 具有兩個命名空間(Namespace):

0x01: 系統資源(預裝在 framework-res.apk 中);0x7f: 應用資源(捆綁在應用的.apk 文件中)。

那麼如果在0x01命名空間中引用了一個資源,我們的 APK 發生了什麼?我們應該可以在削減文件規模的同時,得到一個更漂亮的圖標。

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

雖然文檔是這樣說的,但是在一個生產 App 中,我們應該保持「永遠不要信任系統資源」這一原則。該步驟會導致 Google Play 驗證失敗,而且考慮到我們知道某些製造商已經重定義了白色

 (https://www.reddit.com/r/androiddev/comments/71fpru/android_color_resources_not_safe/),

因此在具體操作時需要慎重。

 Manifest 文件(優化為 5252 字節,削減 1%)

目前為止,我們尚未對 Manifest 文件下手。

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

移除這些屬性將會削減 48 個字節。

看上去 Dex 文件中依然包括BuildConfig和R。

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

如果我們精煉 Proguard 規則,就會清除掉這些類。

現在對我們的Activity賦予一個混淆後的名字。對於正常類,Proguard 可自動實現混淆功能,但是考慮到Activity類名會通過Intents喚醒,因此預設情況下不要混淆Activity的名字。

MainActivity -> c.javacom.fractalwrench.apkgolf -> c.c

 META-INF(優化為 3307 字節,削減 33%)

當前在 App 籤名中,我們使用了 v1 和 v2 籤名。看上去這完全是浪費,尤其是 v2 會對整個 APK 做哈希,提供了更高級的保護能力和性能

 (https://source.android.com/security/apksigning/#apk-signing-schemes)。

在 APK Analyser 中,v2 籤名並不可見,因為它在 APK 文件本身中以二進位塊的形式存在。v1 籤名是可見的,它是以CERT.RSA 和 CERT.SF文件的形式給出。

Android Studio UI 中提供了 v1 籤名的複選框,我們需要去除該選擇,並生成一個籤名的 APK。我們也需要做相反的過程。

看上去從此以後我們使用的是 v2。

現在我們要手工編輯我們的 APK 了。我們將使用如下命令:

# 1. 創建一個未籤名的 APK。./gradlew assembleRelease# 2. 解壓縮歸檔文件。unzip app-release-unsigned.apk -d app# 對文件進行編輯。# 3. 壓縮歸檔文件zip -r app app.zip# 4. 運行 zipalign。zipalign -v -p 4 app-release-unsigned.apk app-release-aligned.apk# 5. 使用 v2 籤名運行 apksigner。apksigner sign --v1-signing-enabled false --ks $HOME/fake.jks --out signed-release.apk app-release-unsigned.apk# 6. 驗證籤名。apksigner verify signed-release.apk

此連結

adb shell am start -a android.intent.action.MAIN -n c.c/.c

下面給出新的 Manifest 文件:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="c.c">    <application>        <activity            android:name="c"            android:exported="true" />    </application></manifest>

我們還移除了加載圖標。

 削減方法引用(優化為 2179 字節,削減 12%)

package c.c;import android.app.Application;public class c extends Application {}

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="c.c">    <application android:name=".c" /></manifest>

我們可以使用 adb 驗證該 APK 是可以成功安裝的,也可以通過 Setting App 做驗證。

 Dex 優化(優化為 1961 字節,削減 10%)

在此次優化中,我花費了多個小時研究 Dex 文件格式 

 理解 Manifest 文件(優化為 1961 字節,削減 0%)

非籤名 APK 中的 Manifest 文件是二進位的 XML 格式,該格式看上去並沒有官方的文檔。我們可以使用 HexFiend編譯器去修改文件內容

(https://github.com/ridiculousfish/HexFiend) 。

 無需理解 Manifest 文件(優化為 1777 字節,削減 9%)

下圖給出了一些 Manifest 文件中的重要成分。如果沒有這些成分,APK 將會安裝失敗。

一些事情即刻是很明顯的,例如 Manifest 文件和軟體包標記。在字符串池中還可以找到軟體包名稱和 versionCode。

讓我們查看一下最終的 APK。

終歸,我們使用 v2 籤名在 APK 中留名。讓我們創建一個利用壓縮破解的新密鑰庫。

這可削減 20 個字節。

查看英文原文:https://fractalwrench.co.uk/posts/playing-apk-golf-how-low-can-an-android-app-go/

你有好的文章想和大家分享歡迎投稿,直接向我投遞文章連結即可

Java和Android架構

微信掃描或者點擊下方二維碼領取Android高級進階資源

關注後回復「百度」、「阿里」、「騰訊」、「資源」有驚喜

公眾號:JANiubility

更多學習資料點擊下面的「閱讀原文」獲取

相關焦點

  • Android Studio打包apk,aar,jar包
    一片楓葉_劉超的博客地址:http://blog.csdn.net/qq_23547831作者編寫了github項目解析、android源碼分析以及產品研發多個專題,有興趣的可以關注下學習學習~文本我們將講解android studio打包apk,aar,jar包的相關知識。
  • android apk 防反編譯技術第一篇-加殼技術
    現在將最近學習成果做一下整理總結。學習的這些成果我會做成一個系列慢慢寫出來與大家分享,共同進步。這篇主要講apk的加殼技術,廢話不多說了直接進入正題。一、加殼技術原理 所謂apk的加殼技術和pc exe的加殼原理一樣,就是在程序的外面再包裹上另外一段代碼,保護裡面的代碼不被非法修改或反編譯,在程序運行的時候優先取得程序的控制權做一些我們自己想做的工作。
  • apk瘦身;如何縮小體積呢?這篇文章來教你
    前言我們完成一個app後,都需要生成一個apk,然後上線,而apk的大小也一定程度的影響了用戶是否願意下載你的這個app,所以也就有了apk瘦身這門藝術。的結構既然要對一個apk瘦身,首先我們就得知道apk格式的文件內容。
  • Android模擬器和安裝APK文件
    好了進入正題,今天要講的是關於android模擬器和apk鏡像文件的一些事情。一.如何正確的啟動模擬器(早於Android 1.5的開發版本跳過此步) :關於在eclipse裡面如何集成android這些問題就不說了,這寫問題我想還是不用在這裡廢話的。
  • VirtualAPK:滴滴 Android 插件化的實踐之路
    VirtualAPK 對插件沒有額外的約束,原生的 apk 即可作為插件。插件工程編譯生成 apk 後,即可通過宿主 App 加載,每個插件 apk 被加載後,都會在宿主中創建一個單獨的 LoadedPlugin 對象。如下圖所示,通過這些 LoadedPlugin 對象,VirtualAPK 就可以管理插件並賦予插件新的意義,使其可以像手機中安裝過的App一樣運行。
  • 淺談安卓apk加固原理和實現
    上圖對象解析:源apk:需要加密的apk程序,源dex來自於源apk殼程序:Android工程,提供殼dex,殼dex主要作為工程入口三、apk加固實現準備:SourceProject:需要加密源程序,自定義application為:com. targetapkMyApplication,主activity為:com. targetapk
  • 將兩個 Crosswalk* Android* APK 文件提交到 Google Play Store*...
    這兩個 APK 文件的名稱採用以下格式: AppName.android.crosswalk.x86.timestamp.apk, 例如 ExampleApp.android.crosswalk.x86.20140418132640.apk AppName.android.crosswalk.arm.
  • 【Android】Apk安裝與管理工具
    APK Editor是一款應用程式,可讓您準確執行其名稱所指示的操作:編輯保存到設備中的任何APK。而且,如果您本身沒有APK,則可以從已安裝的任何應用程式中提取它。有了這個程序,您可以深入到apk結構中,也可以從中提取圖片。-APK編輯器,您無需任何編碼即可輕鬆創建自己的android應用(在安裝前稱為APK)。-APK編輯器將提取/共享/備份您在設備中製作的所有應用程式的APK,並顯示保存在SD卡中的所有APK的列表。-構建自己的較小應用程式(例如,手電筒示例apk僅約2.5萬,比市場上的其他產品要小得多)。
  • Webview.apk —— Google 官方的私有插件化方案
    這一點在 iOS 中尚未實現,(iOS OTA 的歷史也不是特別的悠久)。但是 webview.apk 不是一個普普通通的 apk,首先它沒有圖標,不算是點擊啟動的「App」。同時,更新這個 APK,會讓所有使用 webview 的應用都得到更新,哪怕是 webview 中的 UI ,比如前進後退也一樣,得到更新。這一點是如何做到的呢?
  • APK詳解一籮筐
    -I提供平臺的 android.jar 或其他 APK(如 framework-res.apk)的路徑,這在構建功能時可能很有用。如果您要在資源文件中使用帶有 android 命名空間(例如 android:id)的屬性,則必須使用此標記。--java directory指定要在其中生成 R.java 的目錄。
  • Gradle 實現 Android 多渠道定製化打包
    最近在項目中遇到需要實現 Apk 多渠道、定製化打包, Google 、百度查找了一些資料,成功實現了上述功能,在此記錄以備不時之需,溫故而知新
  • Android安全(五)--查看APK的籤名的方法
    entryandroiddebugkey, Mar 21, 2013, PrivateKeyEntry, Certificate fingerprint (MD5): E0:F4:90:EE:CD:77:17:0E:B8:C4:AC:64:B2:F6:FC:832、查看三方應用或是系統應用籤名用winrar打開待查看的apk
  • 你必須要懂的APK瘦身知識
    從我的開發經驗上來講,寧願參照自己去實現,也不願意多引入一個第三方庫。避免枚舉一個枚舉可以為您的應用程式的classes.dex文件添加大約1.0到1.4 KB的大小 。這些添加可以快速累積到複雜系統或共享庫。如果可能,請考慮使用@IntDef注釋,這種類型轉換保留了枚舉的所有類型安全優勢。
  • SpyNote5.0 Client_APK逆向分析
    開始逆向分析咱們開始對於Client_APK進行分析,通常喜歡將客戶端生成的APK程序拖入androidkiller。(如何生成客戶端請見上文的幾篇文章) 拖入程序後androidkiller會自動進行反彙編,然後有程序分析結果。
  • 如何手動注入Payload後門到安卓APK文件並維持訪問
    先捋一下思路,把PAYLOAD小馬注入到一個目標apk文件中,關鍵是要找到目標APK的入口,並把啟動payload小馬的代碼添加進去,隨目標APK一起啟動,從而實現監聽手機的目的。當然前提是apk文件可逆。這裡使用注入目標apk文件:(百度)手電筒—-小米應用市場下載的順著思路,我們先用msf生成小馬。
  • 修改Artmethod源碼來trace被dexvmp保護的apk
    apk核心的Java層代碼被轉換為Native,jadx反編譯工具,無法正常的靜態分析。我們可以跟老師學習的技能,從aosp源碼修改入手,結合frida,破解apk核心onCreate方法,被vmp保護了,變為native方法。這裡無需對vmp函數還原,可以修改aosp對函數調用進行探測,從而達到分析目的。修改aosp源碼,監控InvokeWithArgArray。
  • 手把手教學APK反編譯實現源碼閱讀
    我們編寫源程序經過編譯變成可執行文件,反編譯就是其逆過程。
  • apkpure免費下載
    你是否在尋找apkpure免費下載,18183遊戲庫為您提供最新最好的下載體驗! apkpure免費下載官方介紹: apkpure免費下載遊戲風格和音樂都不錯,屬於感官系的玩家可以一試 .輕巧但更強大的Android Appstoreulas應用程式,是一個獨立的,易於安裝的應用程式管理工具的集合,適用於Android作業系統Ice Cream Sandwich 4.0.3至6.0 Marshmallow
  • Android中apk加固完善篇之內存加載dex方案實現原理(不落地方式加載dex)
    就是關於之前的一個話題:Android中Apk加固關於這個問題,之前的一篇文章已經說過了,沒有了解的同學可以點擊這裡:Android中apk加固技術實現  請務必仔細的看完這篇文章,不然今天說的內容會感覺很蛋疼的,因為今天的文章就是為了解決當初的加固技術遺留的問題。
  • apk安裝包管理
    apk安裝包管理 系統安全 大小: 5.66M