之前对NDK开发一直是个小白,最近花了几天时间研究得到的一些理解在此做个记录分享。结论不足之处拒绝反驳,所有观点仅单方面宣布,后果自负。*.*,本文出处:http://www.jianshu.com/p/201046751a7c
一、什么是NDK?
NDK全称是Native Development Kit(原生开发工具包),NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。也就是说它是一个“开发工具包”,就像SDK一样,区别就在于SDK是面向java开发者的工具集合,而NDK面向的则是C/C++开发者的工具集合(包括对c/c++源码的打包编译工具ndk,一些h头文件等)。附上官方NDK工具包的下载路径:官网ndk下载,需要翻墙。
二、为什么需要使用NDK?
1.代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。
2.可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。
3.提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
4.便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
三、JNI、SO介绍
JNI 全称Java Native Interface,这套技术的机制是用于java访问c/c++代码而产生的,说白了NDK开发的核心就是JNI开发,利用java代码来调用遵循JNI规范的c/c++的方法实现某个功能。
so全称Shared Object,本地原生库,先暂时理解为java中的jar包,所有的c/c++的代码在android(Linux)平台中最终都会编译成so库,然后才能被调用。所以ndk开发所编写出的c/c++代码最终的目的都是为了获得这个so库,与java方法形成jni的映射关系从而实现调用。
四、开始撸码
本文使用Android Studio2.0进行演示HelloJni
大概步骤:
1. java文件中声明native方法,和java方法声明一样,在此基础上加了natvie修饰。
2. 利用javah命令生成与该类对应的头文件(包含方法信息)
3. 根据头文件的信息编写c源代码文件
4. 在app\build.gradle文件中配置ndk的编译信息
5. 配置NDK工具包路径,编译运行
创建项目:HelloJni
1. 定义一个java类SayHello,并在里面声明一个静态无参native方法speak,并且创建jni文件夹
2. 利用javah命令生成与该类对应的jni头文件,生成的头文件的目的主要是用来编写c/c++源文件
3.根据.h头文件的信息编写c源代码文件 : 创建SayHello.c文件,把头文件里的方法copy到该文件中,并修改成实体方法,下面则是返回一段字符串。如果熟悉了jni方法名称命名规范,完全可自己手写,生成头文件的步骤也可跳过。亲测发现如果包名带有数字的命名规则不好把握,所以建议用javah生成。
4.在build.gradle中配置ndk的编译信息,配置完成保存同步之后可能出现错误,添加 android.useDeprecatedNdk=true 到gradle.properties 文件中即可解决。
5.配置下载好的NDK工具包:File->Project Structure->SDK Location(文件路径\android-ndk-r14b目录配置到系统环境变量中,以备后面使用)
然后回到在java文件中,加载buil.gradle中配置的moduleName的类库名称,这里配置为:SayHello
最后在MainActivity中测试该方法。
运行。
至此,体验了一把基本的ndk开发过程。不过洗脑还没有结束:
在运行完成之后,我们并没有发现工程目录中有so库文件,其实这个so库文件是在运行之后直接打包到了apk文件中的lib目录下了
由于我们在build.gradle配置了abiFilters打包时只打包x86的文件夹中的so库。所以我们在apk中只看到x86的文件夹,里面存放的就是so库,如果不配置abiFilters,那么将会出现android支持的7种abi,可参见该文章理解ABI。
因为我们可以调用so这个库,显然这个so库是根据我们在jni文件夹下编写的源文件编译生成的,如果我们没有配置,gradle默认就会去编译jni的文件夹下的c/c++的代码生成so库,这个路劲就是src/main/jni,如果这个文件夹没有文件即使配置了ndk{...}信息也不会生成so库,当然gradle还提供自定义配置,下面就看看如何配置:
sourceSets{ main{ jin.srcDirs=["src/mian/jni"] //默认路径,jin.srcDirs指的是需要加入编译的jni的路径,可以自己修改路径的 } }
这个apk安装到x86 abi手机上之后,so库会安装在data\app\包名-数值\ib目录下(可通过Device Monitor工具查看),所以由此可判断System.loadLibrary()加载的库默认是这个路径下的库,也可以调用System.load(data\app\包名-数值\ib\abi\libxx.so)加载绝对路径的so库。
所以java代码能不能正确的执行so库里的内容取决于so库能否被正确的安装。如果未能正确安装,当虚拟机去System.loadLibrary时就会报错java.lang.UnsatisfiedLinkError。
上面的做法只有在打包时才能得到so库,下面就介绍通过ndk开发工具包里的ndk-build单独来编译出so库,这种方式就无需在build.gradle中配置ndk{...}了。
1. 在jni文件家中新建android.mk编译配置文件,参见Android.mk详细配置
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := SayHello
LOCAL_SRC_FILES := SayHello.c
include $(BUILD_SHARED_LIBRARY)
2.在jni文件家中新建application.mk配置需要生成支持的abi so库,参见Application.mk详细配置
APP_CFLAGS += -Wno-error=format-security
APP_ABI := all
这里配置支持所有的abi。
3.在terminal中调用ndk-build工具生成so库
我们可以看到在main文件夹下生成了libs目录,并且生成了支持所有abi的so库,到此生成so完毕;现在任务就是要让这些so库打包到apk文件中的libs目录下,在build.gradle中配置sourceSets的另一个属性jniLibs.srcDirs,配置的路径下的so库文件都会打包到apk文件中,其默认值为app/libs,所以也可以把这些so文件拷到app/libs中而不配置这个属性用其默认值。
上图中不配置jni.srcDirs的路径的作用是为了打包时不让编译系统再去编译得到so库(因为我们已经单独生成),虽然上面ndk没有被配置,但是只要的配置这个路径下有c文件就会生成so库,并且名字为libapp.so,这样一来就造成了相同的包存在两个增加app的体积。
最后build apk看看apk里有没有so库:
运行,大功告成。
下一篇文章介绍:第三方so库的调用