JNI简介
JNI(Java Native Interface),是方便Java调用C/C++等Native代码封装的一层接口。
NDK简介
NDK(Native Development Kit),是Android提供的一套工具集合,通过NDK可以在Android中更加方便的通过JNI开访问本地代码。NDK提供了交叉编译,开发人员只需要简单的修改mk文件就可以生成特定CPU平台的动态库。使用NDK有如下好处:
1、提高代码安全性。由于apk的java层代码很容易被反编译,而so库反编译比较困难
2、可以很方便的使用目前已有的C/C++开源库
3、便于平台间的移植。通过C/C++实现的动态库可以很方便的在其他平台上使用
4、提高程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能
NDK使用步骤
- 第1步:配置Android NDK环境
- 第2步:创建Android项目,并与NDK进行关联
- 第3步:在Android项目中声明所需要调用的Native方法
- 第4步:使用Android需要交互的本地代码实现在Android中声明的Native方法
- 第5步:通过ndk-build命令编译产生.so库文件
- 第6步:编译Android Studio工程,从而实现Android调动本地代码
1、配置NDK环境
2、关联Android Studio 项目与NDK
在local.properties文件中添加配置
ndk.dir=D\:\\Android\\sdk\\ndk-bundle
3、在Android项目中声明Native方法
public class JNIUtils {
//加载生成的so库文件
//注意要跟.so库文件名相同
static {
System.loadLibrary("native-lib");
}
//java调C中的方法都需要用native声明且方法名必须和c的方法名一样
public native static String stringFromJNI();
}
4、实现在Android中声明的Native方法
重新Make Project一下工程,完成后会在工程目录app/build/intermediates/classes/debug/com/hsdi/jnidemo下看到编译后的classes文件JNIUtils.class文件
a、使用javah工具生成头文件
我们的NDK模块源代码由C/C++的头/源文件和make文件组成,这些文件必须放在jni目录下
理论上,jni目录可以放在任何地方,例如我们放在xx/jni/下,在xx/jni/下执行ndk-build之后会在xx/下(即jni同级目录下)产生编译结果(即libs和obj文件)在Android Studio项目里,我们一般把jni目录放在项目根目录下(即src同级目录),这样我们编译出来的libs和obj将会在项目根目录下。
打开终端,输入以下命令:
javah -d ./app/jni -classpath ./app/build/intermediates/classes/debug com.hsdi.jnidemo.JNIUtils
b、创建本地代码文件
在jni目录下创建com_hsdi_jnidemo_JNIUtils.cpp文件,内容如下:
#include "com_hsdi_jnidemo_JNIUtils.h"
JNIEXPORT jstring JNICALL Java_com_hsdi_jnidemo_JNIUtils_stringFromJNI(JNIEnv *env, jobject){
// 1. JNIEnv:代表了VM里面的环境,本地的代码可以通过该参数与Java代码进行操作
// 2. jobject:定义JNI方法的类的一个本地引用(this)
return env->NewStringUTF("Hello from C++");
}
注:
- JNIEXPORT jstring JNICALL中的 JNIEXPORT和JNICALL不能省略,其中jstring表示该方法返回字符串
- 方法名的格式:Java_包名_类名_Java需要调用的方法名,对于包名,包名中的 . 要改成 _ ,_ 要改成 _1
c、在Module的build.gradle添加ndk节点
android{
...
defaultConfig{
...
ndk{
//模块名称,即编译的.so文件名
moduleName "native-lib"
//"log"表示加入Android的调试日志,只要再导入#include <android/log.h>
//就可以使用__android_log_print方法打印日志到logcat中
ldLibs "log"
}
...
}
...
}
5、创建Android.mk文件和Application.mk文件
Android.mk文件是一个负责向NDK构建系统描述NDK项目的GNU Makefile片段,是每一个NDK项目的必备组件,根据GNU Make的命名规则,变量名要大写
# Android.mk必须以LOCAL_PATH开头,注释#除外
# 设置工作目录,而my-dir则会返回Android.mk文件所在的目录
LOCAL_PATH := $(call my-dir)
# 借助CLEAR_VARS变量清除除LOCAL_PATH外的所有LOCAL_<name>变量
include $(CLEAR_VARS)
# 设置模块的名称,即编译出来.so文件名
# 注,要和上述步骤中build.gradle中NDK节点设置的名字相同
LOCAL_MODULE := native-lib
# 指定参与模块编译的C/C++源文件列表,多文件用"\"隔开
LOCAL_SRC_FILES := com_hsdi_jnidemo_JNIUtils.cpp
# 必须在文件结尾定义编译类型,指定生成的静态库或者共享库在运行时依赖的共享库模块列表。
# BUILD_SHARED_LIBRARY 共享库,供java或者其他共享库调用
# BUILD_STATIC_LIBRARY 静态库,供共享库调用,不能直接被java调用
include $(BUILD_SHARED_LIBRARY)
Application.mk文件配置编译平台相关的内容
# 最常用的APP_ABI字段:指定需要基于哪些CPU平台的.so文件
# 常见的平台有armeabi x86 mips,其中移动设备主要是armeabi平台
# 默认情况下,Android平台会生成所有平台的.so文件,即同APP_ABI := armeabi x86 mips
# 指定CPU平台类型后,就只会生成该平台的.so文件,即上述语句只会生成armeabi平台的.so文件
# APP_ABI := armeabi armeabi-v7a mips x86
APP_ABI := all
APP_PLATFORM := android-23
5、编译上述文件,生成.so库文件
经过上述步骤,在app/jni文件夹中已经有3个文件(即test.cpp、Android.mk、Application.mk)
打开终端,输入以下命令
$ cd ./app/jni
$ ndk-build
可能会编译失败,提示如下:
D:/Android/sdk/ndk-bundle/build//../build/core/add-application.mk:178: *** Android NDK: APP_STL gnustl_static is no longer supported. Please switch to either c++_static or c++_shared. See https://developer.android.com/ndk/guides/cpp-support.html for more information. . Stop.
出现的原因可能是由于ndk版本过高导致的,使用低版本的ndk再编译一次:
$ cd ./app/jni
$ D:\Android\android-ndk-r14b-windows-x86_64\android-ndk-r14b\ndk-build
编译成功后,会在jni的同级目录中生成libs和obj两个文件夹,其中libs下存放的是.so库文件
6、在Android Studio项目中使用NDK实现JNI功能
此时,我们已经将本地代码文件编译成.so库文件,有两种方法可以供Java代码调用本地代码
方法一:把这些编译好的.so库打包到apk里面供java调用,我们还必须在Module的build.gradle里面添加下面的代码:
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
...
}
方法二:在src/main/中创建一个名为jniLibs的文件夹,将上述生成的so文件夹放到该目录下,
在Java代码中调用本地代码中的方法,具体代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.tv);
tv.setText(JNIUtils.stringFromJNI());
}
}
效果如下: