想必很多開發者和我們一樣,遇到過許多UnsatisfiedLinkError的困難,着實令人頭疼,現在總結一下,希望能幫助更多的人。
常見錯誤
- lib庫不同目錄下的SO文件參差不齊。
- lib庫目錄下的SO不符合相應的CPU架構。
- 64-bit下使用System.load加載SO:"lib_xyz.so" is 32-bit instead of 64-bit
- java代碼混淆導致。
- 注冊方式不對,或已經被其他類注冊。
- empty/missing DT_HASH in "libxxxx.so" (built with --hash-style=gnu?)
出錯現象及解決方案
-
lib庫不同目錄下的SO文件參差不齊。
發現很多APK包打出來,lib目錄下同時帶着armeabi、armeabi-v7a,但是armeabi目錄下可能有3個SO,而armeabi-v7a下只有2個SO,更有甚者還有armeabi、armeabi-v7a、x86、x86_64、arm64-v8a全部都有,但是不同目錄下的SO個數都不一樣。
這樣打出來的APK包,在安裝的時候會讓Android系統“很為難”,它搞不清到底該選擇哪個SO來安裝。有時可能會造成某個SO的漏安裝,那么在APP運行的時候加載SO時就會出現異常了。
解決方案:
1、只保留lib下的一個目錄足夠(armeabi或armeabi-v7a保留一個),其他目錄全部不用配置。
2、如果想繼續多配置幾個CPU架構的lib目錄,那就全部配置齊全。實際上有時候很難做到,特別是當需要使用三方庫的SO的時候,往往並不那么容易找的齊全。由於全部打齊全會對APK的體積有增加,所以還是推薦第一種方案。
-
lib庫目錄下的SO不符合相應的CPU架構。
同上面的問題差不多,有些APK包打出來,同時配置了armeabi和arm64-v8,但是卻在arm64-v8放置了某個或多個armeabi版本的SO,那么在APP運行的時候就會報類似的錯誤:"lib_xyz.so" is 32-bit instead of 64-bit
-
64-bit下使用System.load加載SO:"lib_xyz.so" is 32-bit instead of 64-bit
Found an explanation: 64-bit Android can use 32-bit native libraries as a fallback,
only if System.loadlLibrary() can't find anything better in the default search path.
You get an UnsatisfiedLinkError if you force the system to load the 32-bit library using System.load() with the full library path.
So the first workaround is using System.loadLibrary() instead of System.load().
64-bit處理器可以向下兼容32-bit指令集,即可以運行32-bit動態庫,所以APK包仍然可以只保留lib下的一個目錄足夠(armeabi或armeabi-v7a保留一個),其他目錄全部不用配置。
有一種組合錯誤,就是APK的lib庫打的參差不齊,又在64-bit下使用System.load加載SO。
有一個APP在MX5(android5.0.1)下出現了以下異常:
Caused by: java.lang.UnsatisfiedLinkError: dlopen failed: "/data/data/com.xxx.pris/app_lib/libPDEEngine.so" is 32-bit instead of 64-bit
首先可以大致知道這是一個64位的機器,查看雲捕展示的機器信息,確實是arm64-v8a。首先就是看APK的lib目錄打的對不對,果然
armeabi下有12個SO,而armeabi-v7a下卻只有11個SO。但是他們使用了雲捕代碼來嘗試安全加載SO來降低UnsatisfiedLinkError的異常。
public static void loadLibrary(final Context context, final String library) {
if (context == null) {
throw new IllegalArgumentException("Given context is null");
}
if (TextUtils.isEmpty(library)) {
throw new IllegalArgumentException("Given library is either null or empty");
}
try {
System.loadLibrary(library);
return;
} catch (final UnsatisfiedLinkError ignored) {
// :-(
CrashHandler.leaveBreadcrumb("ReLinker: System.loadLibrary failed");
}
final File workaroundFile = getWorkaroundLibFile(context, library);
if (!workaroundFile.exists()) {
unpackLibrary(context, library);
}
System.load(workaroundFile.getAbsolutePath());
}
可以看出,如果因為SO打的參差不齊導致了APK在安裝的時候SO就已經有遺漏的沒有被安裝進lib的加載目錄。那么System.loadLibrary的時候便會有異常,然后代碼嘗試解壓並釋放所需要的SO文件,但是這個時候只能通過System.load來加載了,又由於當前是arm64-v8a的機器,所以就出現了Use 32-bit jni libraries on 64-bit android - Stack Overflow的問題。
解決辦法:
- APK包打的時候把SO打的齊全了,並建議只保留一個目錄足夠(armeabi或armeabi-v7a保留一個)。
- 雲捕SDK在發現上述問題之后,嘗試解壓釋放SO的時候,把解壓目錄設置到lib的加載路徑順序里去,並繼續使用System.loadLibrary來加載(而不是System.load)。並在第一次System.loadLibrary出現異常時,面包屑告訴足夠多的信息,例如是否是SO不存在。
- java代碼混淆導致。
由於Native層需要注冊到java層函數,如果java層對應的類名和函數名在打包的時候被混淆了,肯定是會出現異常的。此類問題比較定位解決,但是也比較容易忘記。解決辦法就是在proguard混淆時keep掉對應的類和函數。
-
注冊方式不對,或已經被其他類注冊。
早期的崩潰捕獲功能是在加殼里用的,后來把崩潰捕獲的代碼單獨抽出為雲捕SDK,為了保證復用,加殼和雲捕SDK共同使用一個libbugrpt.so。外殼如果注冊了,則雲捕不再注冊。如果外殼已經注冊過了,雲捕仍然要繼續注冊使用,就會出現上面的錯誤。解決辦法是:當外殼已經注冊啟用了崩潰捕獲,則雲捕不再啟動。 -
empty/missing DT_HASH in "libxxxx.so" (built with --hash-style=gnu?)
java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH in "libxxxx.so" (built with --hash-style=gnu?) at java.lang.Runtime.loadLibrary(Runtime.java:371) at java.lang.System.loadLibrary(System.java:989)
c++ - Android NDK UnsatisfiedLinkError: "dlopen failed: empty/missing DT_HASH" - Stack Overflow
總結
如果問題出現時可以嘗試通過以上的幾種方法來排查,如果有其他沒有羅列的情形可以發給我,我將會持續收集整理並更新,以期幫助更多的開發者解決問題。
如果想要規避以上的問題,最好的辦法就是打好打全相應CPU架構的SO文件。
另外你也可以直接使用雲捕SDKRelinker.loadLibrary功能,來幫助你安全加載SO以降低此類UnsatisfiedLinkError的異常。