(原創: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 服務端系統。