1. 程式人生 > >阿里巴巴工程師縮小檔案體積

阿里巴巴工程師縮小檔案體積

各位iOS開發同學們,你們打出來的iOS的ipa包真的已經不能再小了麼?如果你也像題圖一樣,.ipa檔案比AppStore上顯示的大小相差太多,那你應該對我說的有興趣。
以下是從一個QA的角度作出的從發現問題到解決問題的整個詳細歷程,對ObjC的編譯連線方式可能理解是錯誤的,還請各位ObjC大牛指正。文章較長,我儘量講得更清楚些。為了防止大家對這種長文直接放棄,我先上一個優化前後對比結果圖:

對比結果

以阿里旅行5.2.0釋出到Appstore的版本為例,對比結果如下:
|對比 | 優化前 | 優化後 |減小了|
|:———|:———:|:——-:|:——:|
|二進位制檔案大小| 46.6M | 31.7M|14.9M|
|.ipa檔案大小(本身就是zip格式)|25.2M|22.3M|2.9M|
|手機“用量”中顯示的大小|57.2M|43.0M|14.2M|
注:前兩項大小都是Mac OS X 10.10中,通過命令列ls命令查詢得到


如果你還是沒興趣看長篇大論,直接看最後“我找到的優化方法”那段就行。

為什麼蘋果商店包與我們產出的ipa大小有差別

為了方便大家理解,先對Xcode生成的各種包進行說明:

iOS的各種結果檔案:

1) .app

iOS編譯以後生成的原始檔案,實際是一個資料夾,裡面包含各種資原始檔(圖片,第三方bundle,plist等檔案),程式的可執行檔案(二進位制格式)以及對所有檔案的簽名記錄(_CodeSignature
不能上傳AppStore

2) .dSYM

生成.app時的附屬產物。本質是一個資料夾,其中只有一個最大的檔案,作用是對iOS程式閃退後產生的log檔案進行符號化(desymbolicate);通俗的說,就是把無意義的記憶體地址變成可讀的程式中的類和方法以及程式碼行數
不能上傳AppStore

3) .ipa

實際上就是把.app放到Payload資料夾後,對Payload就行了zip操作,最後改了下副檔名。
可通過Application Loader上傳AppStore

4) .xcarchive

實際上也是一個資料夾,包含.ipa.dSYM檔案
可通過Xcode上傳AppStore

原因

由於上傳AppStore速度很慢,我們一般選擇上傳.ipa檔案到AppStore。那為什麼從蘋果商店看到的包大小和我們的.ipa大小不一致呢。下面是蘋果官方的說法(注意黑體字,後面會用到):

When your application is approved by Apple to sell on the App Store, it is encrypted for DRM purposes and re-compressed. When the encryption is added, the size of the compressed file will increase. The exact size of the increase will vary from app to app, however, the size increase can be large when the binary contains a lot of contiguous zeros.

We are unable to guarantee the size of your file after the encryption has been added.
也就是說,蘋果獲取上傳的ipa檔案後,進行解壓縮成.app,然後對其中二進位制檔案進行Apple FairPlay DRM加密,最後重新壓縮成.ipa,此時生成的.ipa作為AppStore上的顯示程式的大小。

分析

回到我們阿里旅行的包:我們v5.1.1生成的.ipa檔案是23.9M左右,而蘋果商店上顯示的是36.9M
為什麼會有這麼大差距呢,我們來看看我們的二進位制檔案AliTrip的前後大小對比:
adding: Payload/Alitrip.app/AliTrip …. (in=42806208) (out=14899495) (deflated 65%)
上面是打包機使用PackageApplication.app進行壓縮成zip(即ipa)時的日誌。可以看到我們的二進位制檔案高達42.8M,經過zip壓縮後變為14.8M左右,縮小了65%的體積。
為什麼我們的檔案壓縮比率可以這麼高呢。我們使用Sublime Text開啟此檔案就會發現,果不其然!連續的0數都數不過來。

大膽的猜想

  • 因為是重複的0,在被zip壓縮時可以直接寫作“n個0”來儲存,所以壓縮比率很高;
  • 而在進行DRM加密時,這些重複的0生成了非0欄位,再進行壓縮時,壓縮比上不去了

    驗證

    以上猜想可以通過iTunes下載ipa檔案來看:
    通過iTunes下載阿里旅行,找到下載到的ipa檔案,解壓縮後,對其中的Payload資料夾中進行zip壓縮,發現二進位制的壓縮比與之前相比,已經下降了30%:
    adding: Payload/Alitrip.app/AliTrip (deflated 35%)
    從42806064壓縮到27654347
    所以DRM操作是導致.ipa檔案變大的原因

    解決思路

    我們現在要做的就是如果減少和消除我們二進位制檔案中多出的哪些連續的0:

  • 從Xcode編譯階段著手,研究連續的0產生的原因
    • 從生成的二進位制檔案著手,刪除多餘的0
    • 刪除是可行的,但是上面提到,.app裡面會對生成的檔案進行簽名,如果我們修改了二進位制檔案,簽名就失效了。修改過得ipa檔案不能通過蘋果稽核

      我找到的優化方法

      對與iOS的編譯和連線等操作我完全是門外漢,經過幾天的搜尋整理,我找到的突破口就是 Stripstrip從字面意思上其實就是“脫光”的意思(嗯,點題了…),也就是把生成的 物件檔案 (.o檔案)中不必要的 符號(symbols)去掉的意思。所以從Xcode的Strip相關配置下手進行優化。以上是我對strip的理解,可能不完全對,望大家指正。
      在Xcode專案的Build Settings中,搜尋Strip:我們可以看到,程式預設做了一些配置,相關strip選項解釋如下(BuildSettings所有選項的官方點這裡)。
      |選項 | 意義 |
      |:—-|:——|
      | Deployment Postprocessing |strip所有選項的總開關,如果選NO,以下選項均無效 |
      |Strip Debug Symbols During Copy| 檔案拷貝編譯階段時是否進行strip,你的工程中有CopyFilesBuildPhase才有意義|
      |Strip Linked Product|這個選項才對最後生成的二進位制檔案進行strip|
      |Strip Style|allnon-globaldebuggingstrip程度依次降低:all一般用於最後生成.app的工程;non-global用於bundle和framework,debugging一般都可以。雖然all是strip最多的選項,但是選擇錯誤會導致strip失敗|
      |Dead Code Stripping|用於刪除物件檔案中不需要載入的符號,減小二進位制檔案大小|
      這是阿里旅行其中兩個工程的strip設定截圖(其中 粗體 是優化過的選項):

  • Portal工程是生成.app的工程:
    _2014_12_31_2_22_16
  • CommonUI是其中一個底層framework工程:
    _2014_12_31_2_21_09

可以看到阿里旅行的strip設定基本正確,但是最關鍵的strip總開關Deployment Postprocessing沒有開啟。然後就是Strip Style對線上包的strip程度不夠(framework工程只選擇了debugging級別,改為non-global
這個strip總開關在Xcode的Release配置中本身也是預設關閉的,需要我們手動開啟。另外其實strip除了降低app大小外,一定程度上提高了從app獲得更多資訊的難度,安全性更佳
以上strip優化只針對內測包和商店包。

結論與思考

由於iOS工程的線上配置中使用了Xcode的預設設定,導致沒有開啟strip開關,最後生成的二進位制檔案偏大。
由於我對ObjectiveC編譯、連線的實現完全不理解,我這種方式不一定是最優化的選擇;也可能除了strip外,BuildSetting還有其他的優化方式。在此拋磚引玉,歡迎大家討論。
最後,這種處理後會不會對我們crash日誌解析產生影響,還需要開發同學來確認下。優化過的app可以正常執行,我已經試驗過了。