與 .so 有關的一個長年大坑
對集成過第三方 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 的掃描順序,所以同樣的文件放置會帶來兩種不同的安裝結果:
這邊還有個小插曲,這個 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 文件夾
推薦閱讀:
※知乎有沒有開發相應的APP應用,比如Android和iOS版本的知乎?
※Android應用怎麼繞過Fiddler等抓包工具?
※如何通過自學成為一名 Android 應用開發工程師?
※適合小團隊的知識庫軟體有哪些?
※初使用 Android Studio ,體驗是不是很不好?
TAG:Android开发 | AndroidNDK | Android |