如何給你的Android 安裝文件(APK)瘦身

本文翻譯自:Putting Your APKs on Diet           原作者:Cyril Mottier

Androidapk文件越來越大了這已經是一個不爭的事實。在Android 還是最初版本的時候,一個appapk文件大小也還只有2 MB左右,到了現在,一個appapk文件大小已經升級到10MB20MB這個範圍了。apk文件大小的爆炸式增長主要是因爲用戶對app質量的期待越來越高以及開發者的開發經驗增長,具體體現在以下幾個方面:

  • Android設備 dpi 的多樣化 ([l|m|tv|h|x|xx|xxx]dpi)
  • Android平臺的進化,開發工具的改進以及開源類庫生態系統的豐富
  • 用戶對高質量UI的期待
  • 其他原因

Android開發者在設計一個app的時候應該將最終發佈一個輕量級app作爲一個最佳實踐來考慮。爲什麼?首先這就意味着你擁有了一個簡潔,易維護代碼基礎。其次,官方應用商店對超過50MBapk設置了拓展包文件下載選項,apk文件在50MB以下更容易讓用戶下載。最後,我們的應用程序環境是一個帶寬有限,存儲空間有限的環境,apk安裝文件越小,下載就會越快,安裝也會更快,良性循環,最後說不定用戶因爲這個給好評。

在大部分情況下,apk大小的增長是爲了滿足消費者的需要和期待。然而,我認爲apk大小的增速已經超過了用戶對app期待的增速。所以,很大程度上,官方應用商店裏面的那些程序可以瘦身至它們現在大小的一半甚至更多。在這篇文章裏面,我將寫下一些關於如何給apk文件瘦身的招式,希望你們能夠喜歡。


 

APK 文件格式

在說如何給apk瘦身之前,讓我們先來看看apk文件內部的結構到底是怎麼一回事。說簡單點,一個apk文件就是包含一些文件的壓縮包。作爲開發者,我們通過使用 unzip 命令解壓這個apk文件一探apk的內部結構。下面的文件結構就是我們使用 unzip <your_apk_name>.apk1這個命令所獲得的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

/assets

/lib

  /armeabi

  /armeabi-v7a

  /x86

  /mips

/META-INF

  MANIFEST.MF

  CERT.RSA

  CERT.SF

/res

AndroidManifest.xml

classes.dex

resources.arsc

我們可能對上面大部分的文件和目錄都很熟悉。它們和我們在實際開發app的時候所看到得項目結構一樣,包含了: /assets, /lib, /res,AndroidManifest.xml. 還有一些文件可能是我們第一次看到。一般說來,classes.dex, 它包含了我們所寫的Java代碼經過編譯後class文件;resources.arsc 包含了預編譯之後的資源文件(比如values文件,XML drawables 文件等。)。

由於apk文件只是一個簡單地壓縮文件,這就意味着它有兩種大小:即壓縮前的大小和壓縮後的大小。這篇文章我將主要關注壓縮後的大小。

如何減少apk文件大小

減少apk文件大小可以從幾個方面入手。由於每個app都是不同的,所以沒有什麼絕對規則來給apk文件瘦身。作爲apk文件的三個重要組成部分,我們可以考慮從它們開始入手:

  • Java 源代碼
  • 資源文件(resources/assets
  • native code

所以接下來的招式都是由減少這些組件的大小出發,進而減少整個app的大小。

掌握良好的編碼習慣

這是減少apk文件至關重要的第一步。你要對自己的代碼瞭如子掌。你要移除掉所有無用處的dependency libraries,讓你的代碼一天比一天優秀,持續地優化你的代碼。總而言之,保持一個簡潔,最新的代碼基礎是減少apk文件至關重要的一環。

當然,從零開始一個項目併爲這個項目保持一份簡潔的代碼基礎很容易。項目越老,這個工作就越困難。事實上,擁有一大段歷史背景的項目必須要去處理各種死代碼和無用代碼。還好有許多的開發工具可以幫我們來做這些事情……

使用 Proguard

Proguard 是一個很強悍的工具,它可以幫你在代碼編譯時對代碼進行混淆,優化和壓縮。它有一個專門用來減少apk文件大小的功能叫做 tree-shakingProguard 會遍歷你的所有代碼然後找出無用處的代碼。所有這些不可達(或者不需要)的代碼都會在生成最終的apk文件之前被清除掉。Proguard 也會重命名你的類屬性,類和接口,然整個代碼儘可能地保持輕量級水平。

也許現在你會認爲 Proguard 是一個相當有效地工具。但是能力越大,責任也就越大。現在許多開發這認爲Proguard有點讓人不省心,因爲它會重度依賴反射。哪些類或者屬性需要被處理或者不能處理都要開發者對 Proguard 進行配置。

廣泛使用 Lint

Proguard 只會對 Java 代碼起作用,那麼對哪些資源文件呢?比如一張圖片 my_image  res/drawable 文件夾中,沒有被使用,Proguard 只會移除掉 R 類中的引用,但是圖片依然還在文件夾中。

Lint 一個靜態的代碼分析器,你只需通過調用 ./gradlew lint這個簡單地命令它就能幫你檢查所有無用的資源文件。它在檢測完之後會提供一份詳細的資源文件清單,並將無用的資源列在“UnusedResources: Unused resources 區域之下。只要你不通過反射來反問這些無用資源,你就可以放心地移除這些文件了。

Lint 會分析資源文件(比如 /res 文件夾下面的文件) ,但是會跳過 assets 文件 ( /assets 文件夾下面的文件)。事實上assets 文件是可以通過它們的文件名直接訪問的,而不需要通過Java引用或者XML引用。因此,Lint 也不能判定某個 asset 文件在項目中是否有用。這全取決於開發者對這個文件夾的維護了。如果你沒有使用某個asset 文件,那麼你就可以直接清除這個文件。

對資源文件進行取捨

Android 支持多種設備。Android的系統設計讓它可以支持設備的多樣性:屏幕密度,屏幕形狀,屏幕大小等等。到了Android 4.4,它支持的屏幕密度包括: ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi and xxxhdpi。但是你要知道的一點是,Android 支持這麼多的屏幕密度並不意味着你需要爲每一個屏幕密度提供相應的資源文件。

如果你知道某些屏幕密度的設備只有很少部分用戶在使用,那麼你就可以直接不需要使用相應屏幕密度的資源文件。就我個人而言,我只會爲我的應用提供 hdpi, xhdpi and xxhdpi2 這幾個屏幕密度的支持。如果某些設備不是這幾個屏幕密度的,不用擔心,Android 系統會自動使用存在的資源爲設備計算然後提供資源文件。

我這麼做得原因很簡單。首先,這些設備屏幕密度就能覆蓋我 80% 的用戶。其次,xxxhdpi 這個屏幕密度只是在爲未來的設備做準備,但是未來還未到來。最後,我真的不怎麼關心低屏幕密度(比如mdpi ldpi),無論我爲這幾個屏幕密度努力,結果都是令人傷心地,還不如直接讓Android系統對 hdpi 資源文件進行適當地縮放來匹配相應地低端機型。

同樣地,在 drawable-nodpi 文件夾裏面維持一個文件也能節省空間。當然前提是你覺得對這個文件進行相應地縮放之後呈現的效果你能接受或者這個文件出現的機率很少。

資源文件最少化配置

Android 開發經常會依賴各種外部開源代碼庫,比如Android Support Library, Google Play Services, Facebook SDK 等等。但是這些庫裏面並不是所有的資源文件你都會用到。比如, Google Play Services 裏面會有一些爲其他語種提供翻譯,而你的app又不需要這個語種的翻譯,而且這個庫裏面還包含了我的app中不支持的 mdpi 資源文件

還好從Android Gradle Plugin 0.7 開始,你可以配置你的appbuild系統。這主要是通過配置 resConfig  resConfigs 以及默認的配置選項。下面的 DSL Domain Specific Language)就會阻止 aaptAndroid Asset Packaging Tool)打包app中不需要的資源文件。

 

1

2

3

4

5

6

defaultConfig {

    // ...

 

    resConfigs "en", "de", "fr", "it"

    resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"

}

 

 

 

 

壓縮圖片

AaptAndroid Asset Packaging Tool)就內置了 保真圖像壓縮算法。例如,一個只需 256 色的真彩PNG圖片會被aapt 通過一個顏色調色板轉化成一個 8-bit PNG 文件。這可以幫助你減少圖片文件的大小。當然你還可以通過Google查找相應的優化工具,比如 pngquant, ImageAlpha ImageOptim 等。你可以從中選擇一個適合你的工具。

還有一種只在Android平臺上存在的圖片文件也可以優化,它就是 9-patches。就我目前所知道,我還沒發現有這個文件的優化工具。然而你只需要求你的設計師將它的可擴展區域和內容區域儘可能地減少即可。這不但可以減少資源文件的大小,還能使得以後資源文件的維護變得更加簡單。

限制app支持的cpu 架構的數目

一般說來Android 使用Java 代碼即可以滿足大部分需求,不過還是有一小部分案例需要使用一些 native code。就像之前對資源文件那樣opinionated,你可以這些 native code opinionated 在當前的Android 生態系統中,讓你的app支持 armabi x86 架構就夠了。這裏有一篇相當不錯的關於如何瘦身native 代碼庫的文章,你可以參考參考。

儘可能地重用

重用資源可能是你在進行移動開發時需要了解的最重要的優化技巧之一。比如在一個 ListView 或者 RecyclerView,重用可以幫助你在列表滾動時保持界面流暢。重用還可以幫你減少apk文件的大小。例如,Android 提供了幾個工具爲一個asset文件重新着色,在Android L中你可以使用 android:tint  android:tintMode 來達到效果,在老版本中則可以使用 ColorFilter 

如果系統中有兩種圖片,一種圖片是另一種圖片翻轉180°得到的,那麼你就可以移除一種圖片,通過代碼實現。比如你現在有兩種圖片分別命名爲 ic_arrow_expand  ic_arrow_collapse :


 

你可以直接移除掉 ic_arrow_collapse 文件,然後在ic_arrow_expand 的基礎上創建一個 RotateDrawable 。這個方法也可以讓你減少設計人員的工作:

1

2

3

4

5

6

7

<?xml version="1.0" encoding="utf-8"?>

<rotate xmlns:android="http://schemas.android.com/apk/res/android"

    android:drawable="@drawable/ic_arrow_expand"

    android:fromDegrees="180"

    android:pivotX="50%"

    android:pivotY="50%"

    android:toDegrees="180" />

 

在合適的時候使用代碼渲染圖像

在某些情況下,直接使用Java 代碼渲染圖像也能獲得不錯的效果。比如逐幀動畫就是一個很好的例子。最近我都在嘗試一些Android Wear 的開發,瞭解了一下Android wearable support library。就像其他的Android support library 一樣,這個庫裏面也有一些工具來處理穿戴設備的。

不過讓我吃驚的是,當我簡單地構建了一個 Hello World”示例,最後得到的apk文件竟然有1.5MB。於是我快速地研究了一下 wearable-support.aar 文件,發現這個庫有兩個逐幀動畫,並分別支持了3種不同的屏幕密度:一個 success 動畫 (31 frames) 和一個 open on phone 動畫 (54 frames)


 

這個逐幀success動畫是被一個叫做 AnimationDrawable 所定義的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

<?xml version="1.0" encoding="utf-8"?>

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true">

    <item android:drawable="@drawable/generic_confirmation_00163" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00164" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00165" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00166" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00167" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00168" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00169" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00170" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00171" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00172" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00173" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00174" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00175" android:duration="333"/>

    <item android:drawable="@drawable/generic_confirmation_00185" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00186" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00187" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00188" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00189" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00190" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00191" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00192" android:duration="33"/>

    <item android:drawable="@drawable/generic_confirmation_00193" android:duration="33"/>

</animation-list>

 

這樣做得好處就是 (我當然在諷刺) 每幀顯示33ms,這使得整個動畫保持在30fps的頻率。如果每幀16ms這將會導致整個庫是之前的兩倍大。如果你去看源碼你會發現很有趣。在 generic_confirmation_00175 這一幀 (15 ) 將持續顯示 333ms generic_confirmation_00185 緊跟着它。這個優化節省了9個類似的幀 (包含了從176 幀到 184 ) 。不過最後神奇的是 wearable-support.aar 竟然神奇的包含了這個9個完全無用的幀,而且還以3中屏幕密度展示。3

在代碼中來渲染這樣的動畫明顯會很花時間。然而當你維持動畫運行在60fps這樣的頻率可以大幅度的減少apk的大小。在寫這篇博文的時候,Android還沒提供工具來渲染這樣的動畫。但是我希望Google正在開發新一代的輕量級實時渲染系統來保證material design的細節呈現。當然“Adobe After Effect to VectorDrawable 之類的設計工具也能提供很多方便。

如何更進一步?

上面所有的招式都集中在app或者library 開發者。也許我們還可以在app分發渠道方面爲apk大小做出一些改變?我想可以在app 分發服務器端做一些改進,或者在官方應用商店。例如,我們可以期待官方應用商店在用戶安裝app的時候爲設備綁定相應的native 庫而摒棄那些無用的。

同樣地,我們還可以想象只根據目標設備的配置來打包應用。不幸的是,這可能破壞Android 生態一個重要的功能特性:配置熱置換。事實上,Android一開始就是位處理各種實時的配置更改(語言,屏幕轉向)而設計的。如果我們移除掉與目標屏幕不兼容的資源文件,這可以極大的減少文件大小。不過Android需要處理實時的屏幕密度更改。即便我們假設廢除這種功能,我們仍然需要處理爲不同的屏幕密度設計的圖片以及其他配置(比如屏幕朝向,最小寬度等)。

服務器端的apk打包看起來很強大。但這樣會冒很大得風險,因爲最終傳送給用戶的apk會於開發者發給的服務器的完全不同。分發一些缺失資源文件的apk可能會導致app崩潰。

總結

設計就是在一個約束集裏面找出最好的方案。顯然apk文件的大小就是一個約束。不要害怕爲了讓多個方面變得更好而放鬆一個方面的約束。例如,當你要降低UI的渲染效果時,不要猶豫,因爲這可以讓apk的大小減小,同時使得app的運行也更加流暢。你99%的用戶是感受不到UI質量變低的,但是他們會注意到apk文件變小了,運行也更加流暢了。總之,你需要將app各方面進行整體考慮,而不是僅僅幾個方面的斟酌。

 

本文轉載自 <http://greenrobot.me/devpost/putting-your-apks-on-diet/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章