最近在做Android的项目,用到了JNI,现将NDK的开发流程和方法整理出来,希望能够让刚接触的小伙伴们少走一些弯路。
NDK
Native Development Kit(NDK)是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C/C++的动态库,并能自动将so和java一起打包成apk。
JNI
Java Native Interface(JNI)标准是java平台的一部分,JNI是Java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用C/C++代码,C/C++的代码也可以调用java代码。我们知道,不管是linux还是windows亦或是mac os,这些操作系统,都是依靠C/C++写出来的,还包括一些汇编语言写的底层硬件驱动 。java和C/C++不同 ,它不会直接编译成平台机器码,而是编译成虚拟机可以运行的java字节码的.class文件,所以效率就比不上C/C++代码,但是通过JNI技术可以即时编译成本地机器码。JNI调用示意图:
从上图可以得知,JNI技术通过JVM调用到各个平台的API,虽然JNI可以调用C/C++,但是JNI调用还是比C/C++编写的原生应用还是要慢一点,不过对高性能计算来说,这点算不得什么,享受它的便利,也要承担它的弊端 。
JNI与NDK的关系
NDK可以为我们生成了C/C++的动态链接库,JNI是java和C/C++沟通的接口,两者与android没有半毛钱关系,只因为安卓是java程序语言开发,然后通过JNI又能与C/C++沟通,所以我们可以使用NDK+JNI来实现“Java+C”的开发方式。
为什么要NDK开发
NDK开发具有以下优点:
1. 项目需要调用底层的一些C/C++的一些东西(java无法直接访问到操作系统底层(如系统硬件等)),或者已经在C/C++环境下实现了功能代码(大部分现存的开源库都是用C/C++代码编写的。),直接使用即可。NDK开发常用于驱动开发、无线热点共享、数学运算、实时渲染的游戏、音视频处理、文件压缩、人脸识别、图片处理等。
2. 为了效率更加高效些。将要求高性能的应用逻辑使用C/C++开发,从而提高应用程序的执行效率。但是C/C++代码虽然是高效的,在java与C/C++相互调用时却增大了开销;
3. 基于安全性的考虑。防止代码被反编译,为了安全起见,使用C/C++语言来编写重要的部分以增大系统的安全性,最后生成so库(用过第三方库的应该都不陌生)便于给人提供方便。(任何有效的代码混淆对于会smail语法反编译你apk是分分钟的事,即使你加壳也不能幸免高手的攻击)
4. 便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
NDK开发案例一 JNI开发入门
开发工具:Android Studio 2.3.3
NDK:android-ndk-r14b
1、打开Android Studio,选择“Start a new Android Studio procet”创建android工程,如下图:
2、根据项目需要修改"项目名称"、"公司域名"、"app包名"、"工程路径",勾选“Include C++ support”选项,该选项用于支持JNI开发,如下图:
3、默认勾选“Phone and Tablet”,根据项目需求选择兼容的Android最小版本。可点击“Help me choose”查看各版本功能。如下图:
4、选择项目活动类型,该案例默认选择“Basic Activity”模板,如下图:
5、设置“活动名称”、“界面布局名称"、"界面标题等",如下图:
6、设置JNI支持标准(有“Toolchain Default”和“C++11”两种标准可供选择),此项根据项目需求自行选择,该案例默认选择“Toolchain Default”,如下图:
选择“Finish”,等待工程创建完成,如下图:
可以看到编译窗口有报错:Error:A problem occurred configuring project ':app'.
先不去管错误,执行步骤7,配置NDK。
7、配置NDK。
下载安装NDk后,在Android Studio菜单项选择File->Project Structure,配置NDK路径,如下图:
打开gradle.properties,添加
android.useDeprecatedNDK=true
如下图:
重新编译工程,报错(如上图):
Error:(38, 13) Failed to resolve: com.android.support:design:28.+
......
Error:(36, 13) Failed to resolve: com.android.support:appcompat-v7:28.+
......
Error解决办法:
打开\MyApplication\app\build.gradle,在buildTypes {...}中添加代码repositories {...},
buildTypes {
repositories {
maven{
url "https://maven.google.com"
}
}
......
}
如下图:
重新编译工程,未报错。
8、配置生成so库类型。
打开\MyApplication\app\build.gradle,在cmake {...}中添加代码abiFilters "armeabi", "armeabi-v7a", "x86",如下:
defaultConfig {
......
externalNativeBuild {
cmake {
......
abiFilters "armeabi", "armeabi-v7a", "x86"//cpu的类型
}
}
}
如下图:
armeabi、armeabi-v7a和x86都表示CPU的类型。一般的手机或平板都是用arm的cpu。不同的cpu的特性不一样,armeabi就是针对普通的或旧的。arm v5 cpu,armeabi-v7a是针对有浮点运算或高级扩展功能的arm v7 cpu。
armeabi:默认选项,将创建以基于ARM* v5TE 的设备为目标的库。 具有这种目标的浮点运算使用软件浮点运算。 使用此 ABI (二进制接口)创建的二进制代码将可以在所有 ARM*设备上运行。所以armeabi通用性很强。但是速度慢。
armeabi-v7a:创建支持基于 ARM* v7 的设备的库,并将使用硬件 FPU 指令。armeabi-v7a是针对有浮点运算或高级扩展功能的arm v7 cpu。
x86:支持基于硬件的浮点运算的。
所以,如果项目只包含了 armeabi,那么在所有android设备都可以运行;如果项目只包含了 armeabi-v7a,除armeabi架构的设备外都可以运行; 如果项目只包含了 x86,那么armeabi架构和armeabi-v7a的Android设备是无法运行的;如果同时包含了 armeabi, armeabi-v7a和x86,所有设备都可以运行,程序在运行的时候去加载不同平台对应的so,这是较为完美的一种解决方案,同时也会导致包变大。
9、编写JNI接口程序。
在路径\MyApplication\app\src\main\cpp\下会看到文件native-lib.cpp,因为我们在创建android项目时勾选了C++ support,所以自动生成了JNI接口文件native-lib.cpp并附带一个JNI接口程序stringFromJNI,我们以后就在该文件里面编程JNI接口程序,此案例暂时不对其做修改,用其提供的JNI接口做演示,如下图:
//native-lib.cpp
#include
#include
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_administrator_myapplication_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
10、CMake
在Android Studio 2.2以上版本中,使用的是CMake来编译我们的C代码,如下图:
如果你在cpp文件夹中新建了cpp文件或头文件,就需要手动配置CMakeLists.txt文件。 IED自动生成的CMakeLists中默认添加的cpp文件只有native-lib.cpp。CMakeLists.txt中以#号开头都是注释,这里把它们都删了也就更清楚了,删除注释后的内容如下
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp )
第一个参数为库名字,第三个参数为cpp文件路径。 如果你新建了一个Test.cpp,就需要把这个文件配置到CMakeLists中,但这样很麻烦。有没有一种可以自动包含cpp文件夹下源文件的方法呢?当然有,请看下面。
# 查找cpp目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(src/main/cpp/ DIR_LIB_SRCS)
# 生成链接库
add_library (native-lib SHARED ${DIR_LIB_SRCS})
# 导入cpp目录下的所有头文件
include_directories(src/main/cpp/)
你可以把IED生成的那段替换成这段,这样你以后在cpp目录下新建c++文件时,就不用手动配置了,只需要点击菜单栏Build->Refresh Linked C++ Projects
,刷新后就可以在Android视图下的cpp中看到你新建的c++文件。
CMakeLists需要在\MyApplication\app\build.gradle中设置路径才能生效,如下图:
CMakeLists的路径设置代码是创建android项目时自动生成的,如果将CMakeLists移动到其它目录,只需在这里重新指定目录既可。
cmake入门:https://blog.csdn.net/weixin_40779546/article/details/84821923
11、生成so库文件。
编译工程,在目录\MyApplication\app\build\intermediates\cmake\debug\obj\下生成对应的so文件,如下图:
12、Java调用C代码,此处使用项目自动生成的例程做演示,如下图:
13、创建虚拟设备,如下图:
14、在选定的设备上运行app,显示app调用jni的打印消息"Hello from C++",如下图:
第三方so库的使用
除了调用自己生成的so库外,我们还可能需要调用第三方的so库,Android Studio中和Eclipse中使用so库略有不同。
我这里以Android Studio 2.3.3为例。
AS中默认是配置好so库的路径的,但是并没有给你生成相应的文件夹,所以首先需要新建文件夹。
切换到Project视图,依次展开app->src->main,然后在main目录下新建一个jniLibs的文件夹,注意大小写和s,建议复制粘贴。
然后点击菜单栏Build->Make Project,在切换回Android视图,就可以看到多出了一个jniLibs的目录。