引言
由于項目中需要用到JNI,以前雖然在Eclipse上使用過JNI和SO 文件,移植到Android Studio上的時候是花費好些力氣的,也處理過不少常見的錯誤,而且網上很多文章都是只寫了大致的步驟,忽略了很多細節,為了讓新手們少走彎路,同時也是加強自己的理解,把自己一步一步的操作記錄下來。
一、Android studio引入jar
不同于eclipse的配置build path,Android Studio可以通過圖形界面Project Structure來配置dependencies還可以通過gradle.build腳本來配置。
1、先把對應jar包copy到libs或者jniLibs下再"Add As Library"(個人推薦)
- 將jar文件復制、粘貼到app的libs或者jniLibs目錄中
- 右鍵點擊jar文件,并點擊彈出菜單中的“Add As Library”,將jar文件作為類庫添加到項目中
- 選擇指定的類庫。(高能提醒:如果不執行后兩步,jar文件將不起作用,當然不能使用import語句引用。)
2、先copy再通過gradle.build腳本配置
將jar文件復制、粘貼到app的libs或者jniLibs目錄中
在app下的build.gradle腳本里配置dependencies 節點(與android節點同級)
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')//**主要是這兩句
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.0.0'
compile files('libs/konke-android-lib.jar')//**編譯konke-android-lib.jar
}
3、通過Android Studio的圖形界面
Open module setting——>Project Structure——>選中對應的module——>Dependencies——>"+"——>選擇對應的jar包執行完畢之后會被添加到libs文件夾下(即Project模式下的libs)
二、Android Studio依賴module
如圖module app 依賴于zklibs:
1、通過Android Studio的圖形界面
Open module setting——>Project Structure——>選中對應的module——>Dependencies——>"+"——>選擇對應的jar包
2、通過gradle.build腳本配置
//app的gradle
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.0.0'
compile 'com.google.code.gson:gson:2.7'
compile project(path: ':zklibs')//主要是這一句
}
三、Android Studio使用SO文件
前面一篇Android NDK——配置NDK及使用Android studio開發Hello JNI并簡單打包SO介紹了so文件,它是unix的動態連接庫,是二進制文件,其本質就是本地語言(c/c++)程序文件,作用相當于windows下的.dll文件。而在Android中調用動態庫文件(.so)都是通過jni的方式*。
1、引入so文件到項目中
我們都知道Android Studio的項目結構與在Eclipse里的區別巨大,切換為Project模式和Android模式,顯示的結構都有所不同,這也導致很多初學者有點迷了,當然也包括我,走過不少彎路,Google、StackOverFlow走了很多遍,折騰了一番,最后終于成功了,只需兩步驟。
把Android Studio 里的項目且為Project類型的結構,在xxx/src/main的目錄下下新建名為 ”jniLibs“ 文件夾(注意大小寫,與java文件夾同級)
再將so文件復制、粘貼到“jniLibs”目錄內。(其實jniLibs文件里不僅僅可以放置so文件、也可以放置jar包類型的庫)不需要再額外去配置Gradle了
//當然還有另一種引入so,就是放到libs下,我不喜歡用這種方式。。。
/**如果使用jniLibs文件夾導入so文件,不需要在gradle中配置了;如果將so文件添加在module的libs文件夾下,則需要在module的gradle配置中添加一下配置*/
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
2、定義自己的本地jni接口類
2.1、獲取so里定義的本地方法簽名
借助是是Linux的一個命令:nm -D xxxx.so還可以設置-D以外的其他參數,不過-D已經足夠
nm -D libelia.so
下圖顯示的就是聯發科SmartLink方案的so庫定義的方法簽名還有其他信息,就不貼了
2.2、實現自己的本地jni接口類
把所要使用的so文件復制粘貼到”jniLibs“文件夾之后,一般來說其他第三方的開放平臺的so文件都是已經把對應的本地Java接口類一起封裝到so或者其他庫文件里了,我們不需要自己去定義自己的本地接口類,假如說第三方只是提供了so文件,那么就需要我們去定義jni接口類(這個類并不能是隨意的,必須是和so文件里定義的方法名的一一對應,即包名和類名必須一致,否則會發生編譯通過加載的時候就出錯)
假如so里是這樣定義本地方法,那么對應的我們這個本地接口類,必須滿足四個條件:
包名是crazymo.train.jnitraining
類名是MainActivity
定義的方法名為 helloJni
返回值類型為String
那么定義這個本地接口方法類的一般步驟是:
- 在項目里首先創建一個對應的包
- 再這個包里創建對應的公開類
- 最后在這個類里定義對應的本地接口方法(常規修飾符 native static 返回值類型 helloJni**當然static并不是必須的)
3、加載so文件
加載so文件很簡單,如果你這個APP必須依賴于這個so才能運行的話,建議可以在自己的Application去實現
System.loadLibrary("helloJni");//加載so文件,不要帶上前綴lib和后綴.so
package crazymo.train.jni;
/**
* @auther: Crazy.Mo
* Date: 2016/10/13
* Time:15:22
* Des:
*/
public class HelloJNI {
static {
System.loadLibrary("helloJni");//引入你的so庫文件,不要把前面的lib添加進來
}
public native String helloJni();
}
4、利用本地jni接口類調用對應的接口方法
這個更簡單了,就和我們普通java類的調用語法一樣,如果是靜態的就用類去調用,如果非靜態則用對應的實例去調用,至于怎么調用到本地代碼的,那部分工作由系統會根據你本地接口的包名、方法名去找到對應的C/C++代碼,所以本地接口類往往是我們使用so時發生錯誤的罪魁禍首之一
package crazymo.train.jni;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((TextView)findViewById(R.id.txt_usejni)).setText( new HelloJNI().helloJni());//使用jni方法
}
}
5、簡單使用so庫項目的結構圖
四、NDK調試
默認情況下是不支持NDK調試的,但我們只要做些簡單配置即可實現支持。
1、打開JNI調試 openModuleSettings——>選中module——>Build Types——>Jni Debuggable為true——Apply
2、配置Android Native - Debugger run——>Edit configurations——>選中對應的module——>Debugger——>Debugger Type 選native——Apply
3、下載安裝LLDB,Done。
五、Eclipse項目導入到Android Studio
1、普通Eclipse導入Android Studio
普通的導入流程很簡單,有兩個入口:直接在打開Android studio的窗口中選擇"import project(Eclipse ADT ,gradle,etc)"然后按步驟導入即可(進到這個入口也很簡單,把Android studio其他的Project 窗口都關閉了,只留下一個Project然后“Close Project”即可)
或者在已經打開的Project窗口中,切換到Project視圖——>在Project跟目錄上右鍵——>Module——>import Eclipse ADT project
2、JNI Eclipse 項目導入到Android Studio
導入JNI Eclipse項目時,前面的步驟都一樣,導入完成之后,還得通過選中Module——>右鍵“Link C++ Project with Gradle”配置C++ Link——>可以選ndk-build——>找到Android.mk——>點擊Ok (或者CMake——>選中CMakeList.txtk——>點擊Ok),否則會本地代碼會報錯。
3、Eclipse項目導入到Android Studio的常見錯誤
3.1、編碼錯誤
比如說Eclise項目下的編碼為UTF-8,而Android Studio下的默認為UTF-8 無BOM 格式,此時只需要把Eclipse下的編碼改為UTF-8 無BOM即可解決以下錯誤
3.2、未配置Link C++ Project with Gradle
六、使用so時常見錯誤
1、java.lang.UnsatisfiedLinkError: Couldn't load library xxxx from loader dalvik.system.PathClassLoader
導致這個異常的根本原因就是系統在本地方法與我們本地方法接口類無法對應上,官方一點就是JVM找不到native method的native
- 還未加載對應的so導致的Crash!xxxcouldn’t find “xxx.so”,因為apk打包安裝時,系統會把apk中libs目錄下armeabi的so拷貝到應用的私有目錄下
Crash!java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/xxx],nativeLibraryDirectories=[/vendor/lib, /systemb]]] couldn’t find “xxx.so”
加載的so與所運行的設備的abi架構不一致,只要在在對應的文件夾里添加上相應的so文件即可
java.lang.UnsatisfiedLinkError:No implementation found for XXX
這種錯誤一般來就是我們本地方法接口類沒有和c/c++里的方法對應上
2、java.lang.UnsatisfiedLinkError: com.android.tools.fd.runtime.IncrementalClassLoader$DelegateClassLoader
原因是引用了多方的so,很常見的情況是libaxx.so在各個架構對應的文件夾中都存在,而另一個libcxx.so只存在于32位對應的armaebi文件下,其他架構的都沒有,那么此時程序運行在非armaebi架構的設備時則會直接報錯強退。錯誤的日志如下:
10-28 15:42:28.122 5307-5307/com.xiaoi.app.zkSmartHome E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.xiaoi.app.zkSmartHome, PID: 5307
java.lang.UnsatisfiedLinkError: com.android.tools.fd.runtime.IncrementalClassLoader$DelegateClassLoader[DexPathList[[dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-zxing_c557fb7a8d7e6e337af354ce06614692a32b946a-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-support-annotations-24.0.0_abdd7eb84ec5507286f957f2abccaca254128b0c-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_9-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_8-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_7-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_6-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_5-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_4-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_3-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_2-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_1-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-slice_0-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-rxjava-1.1.8_75fd2ee9fdad54b1b788e8d01c74e78698f28eae-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-retrofit-2.0.0-beta4_3efd0604843b4a6440028ce43f72e5845c1c3325-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-picasso-2.5.2_badcc59626c8bf60fbd570ba883ac0f8d5c9be7a-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-okio-1.6.0_c6c36c9266a53bff725e5087f6a3090b1d0ab593-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-okhttp-3.0.1_a35a122a63f63f6d2b3ba59d028c055fab521b52-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-io.reactivex-rxandroid-1.2.1_6e88671f81f408ad9e58406d59bc0cda6a6af625-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-internal_impl-24.0.0_1ca3cb52067dc09725d551b03ece99cd965979ac-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-in.srain.cube-ultra-ptr-1.0.11_b0a09794d2bb3bfed3ce82634bdccabed79fc5d0-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-gson-2.4_1cef8cfc76ca82a728656c88394ab94c85c46ee1-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-glide-3.6.1_f81c2f329f31a6fbb9641a61098e423c033cd42e-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-converter-gson-2.0.0-beta4_75a1a6273cb28d11375dfff6cd0aa45f11079258-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-com.orhanobut-logger-1.3_89736aa22bffa06d17995d9ad26acdfaf3572df7-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-com.android.support-support-vector-drawable-24.0.0_8d5d9e2412dc464146da0fdb00638a8cb0b0130d-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-com.android.support-support-v4-24.0.0_225ce4463e0d8c3e77ccfd8c1e749bd698e46fcc-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-com.android.support-recyclerview-v7-24.0.0_39a4b7cd3d134a80b92025fdd19f175953aa0dcc-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-com.android.support-cardview-v7-24.0.0_22b22b962be76ccc27cc64fad5c53d30515f6535-classes.dex", dex file "/data/data/com.xiaoi.app.zkSmartHome/files/instant-run/dex/slice-com.android.support-appcompat-v7-24.0.0_4ce805b4f9e08926ae1
解決方法:
最佳的方案肯定是添加上對應的so到對應的文件夾下,不過由于某些原因,不能找到對應的so庫,也可以采用投機取巧的方式,把armaebi下的copy到其他文件下或者刪除其他的文件夾,總之,要保證你有我也有,不能你有我無。