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

 

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

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

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

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

    reducing_apk_file_size

     

    APK 文件格式

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

     

    /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-shaking。Proguard 會遍歷你的所有代碼然後找出無用處的代碼。所有這些不可達(或者不需要)的代碼都會在生成最終的apk文件之前被清除掉。Proguard 也會重命名你的類屬性,類和接口,然整個代碼盡可能地保持輕量級水平。

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

      廣泛使用 Lint

      Proguard 隻會對 Java 代碼起作用,那麼對哪些資源文件呢?比如一張圖片 my_imageres/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 開始,你可以配置你的app的build系統。這主要是通過配置resConfigresConfigs 以及默認的配置選項。下面的 DSL (Domain Specific Language)就會阻止 aapt(Android Asset Packaging Tool)打包app中不需要的資源文件。

       

      defaultConfig{
          // ...
          resConfigsen,de,fr,it
          resConfigsnodpi,hdpi,xhdpi,xxhdpi,xxxhdpi
      }

       

       

      壓縮圖片

      Aapt(Android 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:tintandroid:tintMode 來達到效果,在老版本中則可以使用 ColorFilter

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

      expand_collapse

       

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

       

      
      

       

       

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

      在某些情況下,直接使用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)。

      wearable_support

       

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

       

      
      
          
          
          
          
          
          
          
          
          
          
          
          
          
          
          
          
          
          
          
          
          
          
      

       

       

      這樣做得好處就是 (我當然在諷刺) 每幀顯示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各方面進行整體考慮,而不是僅僅幾個方面的斟酌。

       

      本文翻譯自:Putting Your APKs on Diet

      原作者:Cyril Mottier

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *