Android 5.0 到 Android 6.0 + 的深坑之一 之 .so 動態庫的適配


(原創:http://www.cnblogs.com/linguanh)

 

目錄:

  前序

  一,問題描述

  二,為何會如此"無情"?

  三,目前存在該問題的知名SDK

  四,解決方案,1 對 N

 

前序:

  嫌無聊的請跳過。上次發博文是同年8月,時光荏苒,空閑時間少,現在都接近年關了,其實這4個月學了很多,接觸了IM(非第三方),學習了 golang 並采用它成功完成仿朋友圈頁面的服務端api,等等..等等..,由於這個問題的確是業界超級"毒瘤",趁午休時間,盡我程序員的"干貨"思維,少廢話,盡通俗。

    碼字發文。

 

一,問題描述

  先簡單闡述下幾個概念,這些不是重點。

  1,Android 編譯時候的 api 版本,指的是你要生成的這個 apk 所依賴的 sdk 版本,例如 api 23 即是 Android 6.0 ;

 

  2,.so 動態鏈接庫,為 Linux 下的庫文件,Windows 是 .dll,Android 是基於 Linux 內核的,所以使用的是 .so,在安卓上面,一般由 C/C++ 語言進行 Jni 編程后,采用 NDK 工具編譯后所生成的,可以參考下我之前的一篇教程博文:http://www.cnblogs.com/linguanh/p/4624768.html

 

  3,.so的作用,主要是提供系統底層函數,供應用層使用。不用它行不?可以,在Android已經提供了的情況下,你不需要再自己添加,例如一個 View 的繪制,里面都有很多 Native 關鍵詞的函數,這個就是底層函數,Android api 對應的是它已經提供了。那么如果,你老板要去實現,語音,圖像,視頻處理等系統沒有的功能,你就只能自己寫 .so 來供調用了。

  問題來了:

    發生的環境:此類問題一般發生在 Android 6.0 及其以上的系統,具體也存在於其他的 api 版本,主要集中在 api >=23;

    具體表現是:同一個 APP 在 api <=22 的 sdk 情況下編譯,可以運行正常,不存在閃退或者 .so 庫加載失敗的情況,當你采用 api >=23 的sdk 編譯的時候,安裝到 Android 6.0 及其以上的手機的時候,大范圍出現崩潰 或者 .so 庫加載失敗,而在 6.0 以下的手機卻正常;

    Catch的信息:dlopen failed: cannot locate symbol "XXXX" xxxx.so, XX 是泛配,此類崩潰信息,你完全可以對號入我"座"。

 

二,為何如此無情?

   如果只為解決問題,可以不看這部分。

  現在我用一句話說白它,就是:不同鏈接方式時,dlopen會打開指定的系統中(手機中)提供的動態庫,並使用 dlsym 獲取符號地址,也就是說,如果,在此時的手機中如果找不到,那么就會出問題,一般和 API 有關系。

  人為因素就是,編譯這個 .so 庫的人,他在編譯的時候沒考慮到下面這些情況,導致提供給別人用的時候,或者自己用的時候在高 API 版本手機出現問題。

  感興趣的就接着看下面詳解吧!上面問題描述的第二點提到 .so 是運行在 Linux 環境下的,而且在 Android 里面一般由 NDK 編譯,編譯的時候,我們可以指明一種文件叫做 Application.mk,里面有一行 APP_STL := XXX 指明庫的鏈接方式,默認是靜態,STL的取值:

    1)system,默認的值,最危險方式,直接和手機系統版本掛鈎,采用手機最小版本的.so庫鏈接

    2)gabi++_static

    3)gabi++_shared

    4)stlport_static

    5)stlport_shared

    6)gnustl_static

    7)gnustl_shared

  如果不特別定義的話,“system”運行時庫是默認的值。除此之外,凡是后面帶“_static”的,表示其是一個靜態鏈接的運行時庫(運行時庫的代碼包含在編譯后的程序中);而凡是后面帶“_shared”的,表示其是一個動態鏈接的運行時庫(運行時庫在程序運行時被動態加載進來)。如果去除動態或靜態鏈接的因素,則除了默認的“system”運行時庫之外,還有所謂的“gabi++”運行時庫、“stlport”運行時庫和“gunstl”運行時庫。如果想支持C++異常的話,必須要使用gunstl運行時庫。

  主要是兩種,靜態鏈接動態鏈接

    動態鏈接,是指在生成可執行文件時不將所有程序用到的函數鏈接到一個文件,因為有許多函數在操作系統帶的dll文件中,當程序運行時直接從操作系統中找。

    靜態鏈接,是把所有用到的函數全部鏈接到 .so 文件中;

  重點來了,上面說到了,靜態鏈接是會把所需要的都搞到exe中,其實不然,這個說法是早期的了,對於現在的 Android 發展來說,為了使程序方便擴展,具備通用性,已經采用插件形式來鏈接動態庫,編譯時的靜態和動態鏈接僅僅是程度問題。插件加載形式有:

    1)dlopen

    2)dlsym

    3)dlclose

  dlopen打開指定的系統中(手機中)動態庫。並使用 dlsym 獲取符號地址,也就是說,如果,在此時的手機中如果找不到,那么就會出問題,一般和 API 有關系。

 

三,目前存在該問題的知名SDK

  根據我所了解到的,存在這類問題的 SDK 有,百度地圖、環信、高德地圖、語音庫 speex, 不知道修復沒有,這些 SDK 一但在你的 APK 編譯版中中設置 API >=23 就會出現各種問題,閃退或者拋出異常。

  

四,解決方案,1 對 N

  主要有兩種:

    1- 委曲求全,指標不治本,把你的 APK target API 先降低到 23以下,若不行再把 編譯時 API 降低到 23 以下,還出問題就繼續降低,這意味着,你很多 Android Sdk 的新控件用不了;

    2- 在 Application.mk 中修改 APP_STL,重新編譯 .so ,如果,我說如果你沒有源碼,那么悲劇了,要么等他們解決,要么采用第一種,建議嘗試,APP_STL := gnustl_shared,

  這種方式,對於所需要的外部動態鏈接函數、符號,在 NDK 13b 中都會獨立生成一份,全部引用就解決此類問題,例如

1 private void load() {
2       try {
3           System.loadLibrary("gnustl_shared"); // 也可以不寫這一句,但是要保證 gnustl_shared.so 放置在 libs 里面
4           System.loadLibrary("speex");
5       } catch (Throwable e) {
6           Log.d("zzzzz","加載語音庫異常 :"+e.toString());
7       }
8 }

    3- 如果需要 libgnustl_shared.so 的,留郵箱,我發一份你。

 

  至此,基本講完了,下期 開源二次開發的IM 服務端系統。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM