關於so庫 減少體積的事情

作者:Caspar
鏈接:https://zhuanlan.zhihu.com/p/21359984
來源:知乎
Android 應用開發中不可避免的會引入第三方的代碼。如果是開源項目風險相對可控,如果引入商用的 SDK 那就要謹慎了,難免會有這樣或那樣的問題。比如我們今天要說的這一個。
對集成過第三方 SDK 的同學,上圖中的目錄結構應該不陌生。正常情況下我們只需要將不同版本的 .so 文件分別放置。但如果我們要集成的這個第三方 SDK 偏偏沒有 arm-v7a 的版本呢?是刪除 armeabi-v7a 目錄只保留 armeabi ?還是說兩個目錄下 .so 文件數不同也沒有關係?系統會加載哪個 .so 呢?

如果只對結論感興趣可以直接跳到最後

爲了方便說明我們先引入 FAT Binary 的概念。我們知道不同的 CPU 支持的指令集也不一樣,那麼如果我們需要讓 App 儘可能不同的 CPU 上都可以正常運行該怎麼做呢?簡單,只需要將不同版本的 Binary 放在一個文件裏,運行時按需取用就可以了。這就是 FAT Binary 的典型實現。Android 實現 FAT 的方式有些不同,就是上邊提到的將 .so 文件放置在相應文件夾中。在 Android 系統中 ndk 默認會生成如下 7 種 .so。

在 apk 文件中帶這麼多版本的 .so 是一種很不經濟的做法:

mips / mips64: 極少用於手機可以忽略
x86 / x86_64: x86 架構的手機都會包含由 Intel 提供的稱爲 Houdini 的指令集動態轉碼工具,實現 對 arm .so 的兼容,再考慮 x86 1% 以下的市場佔有率,x86 相關的兩個 .so 也是可以忽略的
armeabi: ARM v5 這重點內容是相當老舊的一個版本,缺少對浮點數計算的硬件支持,在需要大量計算時有性能瓶頸
armeabi-v7a: ARM v7 目前主流版本
arm64-v8a: 64位支持
這樣我們就可以明確 mips, mips64, x86, x86_64 這 4 個 .so 我們是不需要的。

我們回到開頭提到的問題:

假定我們現在的情況是這樣的(b.so 就是那個只有 armeabi 版本的第三方 .so):

如果這樣放置的話,在 ARM / ARM v7 兩種設備上運行 apk 時會分別執行哪個 .so 呢?

答案是:不確定……

這麼坑爹的答案是怎麼來的呢?

由於 Android 上 FAT binrary 的設計如此陽春,在 apk 安裝時就需要根據 CPU 情況執行對應版本 .so 的拷貝。對上邊的情況最合理的一種做法應該是使用 armeabi-v7a/a.so 和 armeabi/b.so 這兩個文件。Google 最初也是這麼想的,然後就引入了 Bug…

Native library copy issue when install apk with different abi native libraries on device

上圖是到 Android 4.4 還在使用的 .so 文件拷貝邏輯,看起來沒有問題?

坑爹是 Android 在安裝 apk 文件時沒有保證 zip entry 的掃描順序,所以同樣的文件放置會帶來兩種不同的安裝結果:

看的有點頭暈?簡而言之,如果按我們上面的放置方式,安裝後系統可能只拷貝了 armeabi-v7a/a.so。如果執行到 b.so 的邏輯,程序顯然會 crash。

這邊還有個小插曲,這個 bug 的發現者在提交時其實已經給出了完善的解決方案,但在經歷了快有小一年的 code review 後 Android 官方表示:我們自己另起爐竈修好了=_=。

這個問題確實在 Android 5.0 已經 “修復” 了。“修復” 方式簡單粗暴,不再以文件爲粒度匹配 abi,直接拷貝整個文件夾=_=。所以如果按我們之前的放置方法,在 Android 5.0+ 如果執行到 b.so 也是一定會 crash 的。

上面提到,只保留 armeabi 文件夾從性能角度是不明智的。正確的做法是將 armeabi/b.so 複製一份到 armeabi-v7a/b.so. 這是由於 ARM v7 是前向兼容 ARM v5 的。

撇開上面曲折離奇的故事,放置 .so 文件的正確姿勢其實就兩句話:

爲了減小 apk 體積,只保留 armeabi 和 armeabi-v7a 兩個文件夾,並保證這兩個文件夾中 .so 數量一致
對只提供 armeabi 版本的第三方 .so,原樣複製一份到 armeabi-v7a 文件夾

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