引言
由于项目中需要用到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到其他文件下或者删除其他的文件夹,总之,要保证你有我也有,不能你有我无。