1. 程式人生 > >android .so深入理解 abiFilters的使用等

android .so深入理解 abiFilters的使用等

為什麼使用so

  • so機制讓開發者最大化利用已有的C和C++程式碼,達到重用的效果,利用軟體世界積累了幾十年的優秀程式碼;
  • so是二進位制,沒有解釋編譯的開消,用so實現的功能比純java實現的功能要快;
  • so記憶體分配不受Dalivik/ART的單個應用限制,減少OOM;
  • 相對於java程式碼,二進位制程式碼的反編譯難度更大,一些核心程式碼可以考慮放在so中。

說起.so檔案就必須提一下arm64、armeabi、armeabi-v7a、x86、x86_64等檔案夾了,這些資料夾分別代表不同的CPU架構。他們存在的意義就是來做不同CPU架構手機的相容的。

資料夾下,.so檔案存在用來做各種手機CPU架構不同的相容的。而apicloud 模組開發的雲平臺的雲編譯,只會保留armeabi這個.所以就有用到了abiFilters來指定NDK需要相容的架構。

例:

ndk {
    abiFilters "armeabi"  // 指定要ndk需要相容的架構(這樣其他依賴包裡mips,x86,armeabi-v7a,arm-v8之類的so會被過濾掉)
}

apk安裝過程中對so的選擇

在Android上安裝應用程式時,Package Manager會掃描整個apk檔案,尋找符合下面檔案路徑格式的動態連線庫:

lib/<primary-abi>/lib<name>.so

在這裡,primary-abi是上面表中的abi的值,name對應的是我們在Android.mk中定義的LOCAL_MODULE

的值,

如果在apk內並沒有找到適合當前機器primary-abi的so,Package Manager會嘗試尋找適合secondary-abi的so檔案:

lib/<secondary-abi>/lib<name>.so

即安裝應用時,系統會根據當前CPU架構選擇最優ABI適配,如果找到了合適的so檔案,包管理器會將該ABI資料夾下所有so庫全部拷貝至應用的data目錄下:data/data/<package_name>/lib/

注意:apk安裝過程對so選擇是基於整個ABI資料夾的,而非以單個so檔案為粒度,也就是說把lib/armeabi 、lib/armeabi-v7a、lib/x86等等資料夾的其中一個資料夾內所有.so複製到應用的data目錄下。

如果我們在程式碼中呼叫了某個so的功能,而最終拷貝的ABI資料夾下並沒有提供這個檔案,apk的安裝過程中並不會報錯,但是執行時會遇到java.lang.UnsatisfiedLinkError

so的載入

對於so的載入,Android在System類中提供了兩種方法:

/**

* See {@link Runtime#loadLibrary}.

*/

public static void loadLibrary(String libName) {

Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());

}

/**

* See {@link Runtime#load}.

*/

public static void load(String pathName) {

Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());

}

System.loadLibrary

這是我們最常用的一個方法,System.loadLibrary只需要傳入so在Android.mk中定義的LOCAL_MODULE的值即可,
系統會呼叫System.mapLibraryName把這個libName轉化成對應平臺的so的全稱並去嘗試尋找這個so載入。
比如我們的so檔案全名為libmath.so,載入該動態庫只需要傳入math即可:

System.loadLibrary("math");

System.load

對於System.load方法,官方是這樣介紹的:

Loads a code file with the specified filename from the local file system as a dynamic library.
The filename argument must be a complete path name.

所以它為動態載入非apk打包期間內建的so檔案提供了可能,也就是說可以使用這個方法來指定我們要載入的so檔案的路徑來動態的載入so檔案。
比如我們在打包期間並不打包so檔案,而是在應用執行時將當前裝置適用的so檔案從伺服器上下載下來,放在/data/data/<package-name>/mydir下,然後在使用so時呼叫:

System.load("/data/data/<package-name>/mydir/libmath.so");

即可成功載入這個so,開始呼叫本地方法了。

其實loadLibrary和load最終都會呼叫nativeLoad(name, loader, ldLibraryPath)方法,只是因為loadLibrary的引數傳入的僅僅是so的檔名,所以,loadLibrary需要首先找到這個檔案的路徑,然後載入這個so檔案。
而load傳入的引數是一個檔案路徑,所以它不需要去尋找這個檔案路徑,而是直接通過這個路徑來載入so檔案。

但是當我們把需要載入的so檔案放在SdCard中,會發生什麼呢?把上面so的路徑改成/mnt/sdcard/libmath.so,再嘗試載入時,會得到如下錯誤:

java.lang.UnsatisfiedLinkError: dlopen failed: couldn't map "/mnt/sdcard/libmath.so" segment 1: Permission denied

這是因為SD卡等外部儲存路徑是一種可拆卸的(mounted)不可執行(noexec)的儲存媒介,不能直接用來作為可執行檔案的執行目錄,使用前應該把可執行檔案複製到APP內部儲存下再執行。所以使用System.load載入so時要注意把so拷貝至/data/data/<package-name>/下。

通過精簡so來減小包大小

現在的apk動輒幾十M或者更大,apk包大小的精簡成為了開發過程中的重要一環。通過上面的介紹,我們知道x86、x86_64、armeabi-v7a、arm64-v8a裝置都支援armeabi架構的so,因此,通過移除不必要的so來減小包大小是一個不錯的選擇。

按照ABI分別單獨打包APK

我們可以選擇在Google Play上傳指定ABI版本的APK,生成不同ABI版本的APK可以在build.gradle中進行如下配置:

android {

// Some other configuration here...

splits {

abi {

enable true

reset()

include 'x86', 'armeabi', 'armeabi-v7a', 'mips' //select ABIs to build APKs for

universalApk false // generate an additional APK that contains all the ABIs

}

}

}

只提供armabi的so

上面的方法需要應用市場提供使用者裝置CPU型別更識別的支援,在國內並不是一個十分適用的方案。常用的處理方式是利用gradle中的abiFilters配置。
首先配置修改主工程build.gradle下的abiFilters

android {

// Some other configuration here...

defaultConfig {

ndk {

abiFilters 'armeabi'

}

}

}

abiFilters後面的ABI型別即為要打包進apk的ABI型別,除此以外都不打包進apk裡。
然後在專案的根目錄下的gradle.properties(沒有的話新建一個)中加入下面這行:

android.useDeprecatedNdk=true

通過上面方法減少的apk體積是十分可觀的,也是目前比較主流的處理方案。

進階版方案

如果進一步,會發現上面的方案並不完美。首先是效能問題:使用相容模式去執行arm架構的so,會丟失專門為當前ABI優化過的效能;其次還有相容性問題,雖然x86裝置能相容arm型別的函式庫,但是並不意味著100%的相容,某些情況下還是會發生crash,所以x86的arm相容只是一個折中方案,為了最好的利用x86自身的效能和避免相容性問題,我們最好的做法仍是專為x86提供對應的so。
針對這些問題,我們可以採用一個相對更好的方案:讓所有so都來自於網路,應用下載伺服器上的so庫後,利用System.load方法動態載入當前裝置對應的so.

需要注意的問題

不要把so放錯地方

首先要注意的是不要把另一個ABI下的so檔案放在另一個ABI資料夾下(每個ABI資料夾下的so檔名是相同的,有可能會搞錯)。

儘可能為所有ABI提供so

理想狀況下,應該儘可能為所有ABI都提供對應的so,這一點的好處我們已經在上面討論過了:在可以發揮更好效能的同時,還能減少由於相容帶來的某些crash問題。當然,這一點要結合實際情況(如SDK提供的so不全、晶片市場佔有率、apk包大小等)去考量,如果使用的so本身就很小,我們大可為儘可能多的ABI都提供so。
若是侷限於包大小等因素,可以結合通過精簡so來減小包大小一節中提供的第三個方案來調整so的使用策略。

所有ABI資料夾提供的so要保持一致

這是一個十分容易出現的錯誤。
如果我們的應用選擇了支援多個ABI,要十分注意:對於每個ABI下的so,但要麼全部支援,要麼都不支援。不應該混合著使用,而應該為每個ABI目錄提供對應的.so檔案。

先舉個例子,Bugtags的so支援所有的ABI:

libs

|

├── arm64-v8a

│   └── libBugtags.so

├── armeabi

│   └── libBugtags.so

├── armeabi-v7a

│   └── libBugtags.so

├── mips

│   └── libBugtags.so

├── mips64

│   └── libBugtags.so

├── x86

│   └── libBugtags.so

└── x86_64

└── libBugtags.so

但不是所有開發者提供的so都支援所有ABI:

lib

|

├── armeabi

│   └── libImages.so

└── armeabi-v7a

└── libImages.so

如果不做任何設定,最終打出來的apk的lib目錄會是這樣的:

lib

|

├── arm64-v8a

│   └── libBugtags.so

├── armeabi

│   ├── libBugtags.so

│   └── libImages.so

├── armeabi-v7a

│   ├── libBugtags.so

│   └── libImages.so

├── mips

│   └── libBugtags.so

├── mips64

│   └── libBugtags.so

├── x86

│   └── libBugtags.so

└── x86_64

└── libBugtags.so

參考上面apk安裝過程中對so的選擇一節,假設當前裝置是x86機器,包管理器會先去lib/x86下尋找,發現該資料夾是存在的,所以最終只有lib/x86下的so–即只有libBugtags.so會被安裝。當嘗試在執行期間載入libImages.so時,就會遇上下面常見的UnsatisfiedLinkError錯誤:

E/xxx (10674): java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/xxx-2/base.apk"],nativeLibraryDirectories=[/data/app/xxx-2/lib/x86, /vendor/lib, /system/lib]]] couldn't find "libImages.so"

E/xxx (10674): at java.lang.Runtime.loadLibrary(Runtime.java:366)

所以,我們需要遵循這樣的準則

  • 對於so開發者:支援所有的平臺,否則將會搞砸你的使用者。
  • 對於so使用者:要麼支援所有平臺,要麼都不支援。

然而,因為種種原因(遺留so、晶片市場佔有率、apk包大小等),並不是所有人都遵循這樣的原則。

一種可行的處理方案是:取你所有的so庫所支援的ABI的交集,移除其他(可以通過上面介紹的abiFilters來實現)。
如上面的例子,最終生成的apk可以是:

lib

|

├── armeabi

│   ├── libBugtags.so

│   └── libImages.so

└── armeabi-v7a

├── libBugtags.so

└── libImages.so

相關推薦

android .so深入理解 abiFilters的使用

為什麼使用so so機制讓開發者最大化利用已有的C和C++程式碼,達到重用的效果,利用軟體世界積累了幾十年的優秀程式碼; so是二進位制,沒有解釋編譯的開消,用so實現的功能比純java實現的功能要快; so記憶體分配不受Dalivik/ART的單個應用限制,減少OOM

深入理解 ext4 Linux 檔案系統

瞭解 ext4 的歷史,包括其與 ext3 和之前的其它檔案系統之間的區別。 目前的大部分 Linux 檔案系統都預設採用 ext4 檔案系統,正如以前的 Linux 發行版預設使用 ext3、ext2 以及更久前的 ext。 對於不熟悉 Linux 或檔案系統的朋

深入理解性及Restful風格API的冪性問題詳解

什麼是冪等性 HTTP/1.1中對冪等性的定義是:一次和多次請求某一個資源對於資源本身應該具有同樣的結果(網路超時等問題除外)。也就是說,其任意多次執行對資源本身所產生的影響均與一次執行的影響相同。 這裡需要關注幾個重點: 冪等不僅僅只是一次(或多次)請求對資源沒有副作用(比如查詢資料庫操作,沒有增刪改,因此

OpenGLES Android篇零基礎系列(二):OpenGL各座標系及模型矩陣(ModelViewMatrix),投影矩陣(ProjectionMatrix)深入理解

上一篇我們粗略的介紹了下GLES20 中 GLSurfaceView以及Render介面的使用。 對於三角形頂點座標的定義並沒有做出註釋,其實在官方的ApiDemo中,它也是赤裸裸的,一個註釋都沒有,且程式碼寫得一點都不敢恭維,不知道那位同行現在是不是還在go

Android 異步消息處理機制前篇(二):深入理解Message消息池

連接 guid ply 指針 cau ann 區別 就會 消息處理機制 版權聲明:本文出自汪磊的博客,轉載請務必註明出處。 上一篇中共同探討了ThreadLocal,這篇我們一起看下常提到的Message消息池到底是怎麽回事,廢話少說吧,進入正題。 對於稍有經驗的開發人員來

Android開發之深入理解泛型extends和super的區別

我想 lis dataset 文檔 cnblogs extend 擦除 選擇 提前 摘要: 什麽是泛型?什麽是擦除邊界?什麽是上界限定或下界限定(子類型限定或超類型限定)?什麽是類型安全?泛型extends關和super關鍵字結合通配符?使用的區別,兩種泛型在實際Andro

Android so文件兼容之abiFilters的使用

今天 總結 遍歷 解決 default 不兼容 遇到 框架 fault 最近項目中遇到了要使用opencv的情況,涉及到了abi兼容的選擇。因為如果全部都適配的話,包很大,這樣兼容那些用戶數極少的cpu就很不劃算,所以我只適配了armeabi-v7a這一個。但是今天在x64

深入理解 Android Https

前言 大家都知道https相比http增加的是安全性。 怎麼增加安全性呢? 就是加密和解密步驟。 下面來詳細談談對https的理解和在Android中的使用. 兩種加密 加密方式分兩種,對稱加密和非對稱加密。這兩種方式都有自己的優劣勢, https中這兩種方式都採用了。 我們約定S是服務端,C是客戶端,

Android深入理解:Handler + Looper + Message

宣告:本文是一篇對Handler相關內容的整理(經過相當一段時間,幾次內容增減),有相當部分內容來源網路,其中融入部分作者本身的理解,並加以整理。如有涉及到哪位老師的原作,在此深表感謝! 目錄   Handler + Looper + Message:生產者 + 消費者 + 倉

深入理解Android Gradle

深入理解Android Gradle 標籤(空格分隔): 未分類 原作者真的寫的很棒附上鍊接 新的android開發工具引用了Gradle構建工具,方便了開發者進行構建不同的應用版本以完成不同的需求。(從此多版本不再痛苦) 1. gradle基本語法 新建專案

Android MVP模式的化簡深入理解

           網上關於MVP的冗長教程已經很多了,自己結合所做的MVP專案儘量簡潔,簡單,大白話的方式記錄心得,為了日後遺忘的差不多了能迅速把記憶抓起來。一圖勝千言,先上圖。圖1是mvp的框架流程圖:   &n

Android 小米推送(MiPush)的化簡深入理解

      小米推送(MiPush)是小米公司向開發者提供的訊息推送服務,總的流程如官方文件所示:      由圖可知,推送是雙向的:      1.推送是可以由app的後臺端發起,應用伺服器的後

ANdroid O MeidiaPlayer 深入理解一()

前言 android對於java層的音訊播放器提供了很多api,主要的有 AudioTrack、SoundPool、MediaPlayer(其實AudioPlayer和MediaPlayerAdapter也都是加了AudioFcus後對於MediaPlayer的二次

深入理解Android 卷III》第八章深入理解Android桌布(節選)

                      第8章 深入理解Android桌布(節選) 本章主要內容: ·  討論動態桌布的實現。 ·  在動態桌布的基礎上討論靜態桌布的實現。 ·  討論WMS對桌布視窗所做的特殊處理。 本章涉及的原始碼檔名及位置: ·  Wal

深入理解Android Bitmap的各種操作

文章目錄 一、Bitmap 1.1 Bitmap的建立 1.1.1 根據已有的Bitmap來建立新Bitmap 1.1.2 通過畫素點陣列建立空的Bitmap 1.1.3 建立縮放的Bit

深入理解與熟練使用 Android 動畫

文章目錄 一、逐幀動畫(Frame Animation) 1.1 概念 1.2 使用 二、 補間動畫(Tween Animation) 2.1 概念 2.2 View 動畫的種類

深入理解Android的startservice和bindservice

一、首先,讓我們確認下什麼是service?          service就是android系統中的服務,它有這麼幾個特點:它無法與使用者直接進行互動、它必須由使用者或者其他程式顯式的啟動、它的優先順序比較高,它比處於前臺的應用優先順序低,但是比後臺的其他應用優先順序高,

深入理解Android之View的繪製流程

本篇文章會從原始碼(基於Android 6.0)角度分析Android中View的繪製流程,側重於對整體流程的分析,對一些難以理解的點加以重點闡述,目的是把View繪製的整個流程把握好,而對於特定實現細節則可以日後再對相應原始碼進行研讀。 在進行實際的分析之前,我們先來看下面

Android 深入理解Android中的自定義屬性

1、引言 對於自定義屬性,大家肯定都不陌生,遵循以下幾步,就可以實現: 自定義一個CustomView(extends View )類 編寫values/attrs.xml,在其中編寫styleable和item等標籤元素 在佈局檔案中CustomView使用自定義的屬性(

深入理解Android之Xposed詳解

一、背景Xposed,大名鼎鼎得Xposed,是Android平臺上最負盛名的一個框架。在這個框架下,我們可以載入很多外掛App,這些外掛App可以直接或間接操縱系統層面的東西,比如操縱一些本來只對系統廠商才open的功能(實際上是因為Android系統很多API是不公開的,