一、 NDK环境配置
1.首先需要下载NDK(Native Development Kit),在Android studio上点击 file → setting如图1.0
图1.0
点击seting,进入如图1.1,按截图找到如下路径 → 选择NDK → 确定应用下载
图1.1
NDK下载完毕,下载好的NDK包的路径是在所已经下载的SDK中,如图1.3,NDK路径为C:\android-sdks\ndk-bundle,
图1.3
2.复制ndk-bundle路径,在电脑上配置环境变量,以win10为例:右键“我的电脑”,点击“属性”,找到“高级属性设置”并点击,进入“系统属性”界面,选择“环境变量”并点击,进入“环境变量”界面,在“系统变量”找到“path”,点击“编辑”,出现如图1.4,点击“新增”,将ndk-bundle的路径配置到“path”后面。
图1.4
3.配置好NDK环境变量后,然后检查NDK是否配置成功,打开cmd,在命令行通过cd 切换到ndk-bundle路径下,如果切换不成功,出现“拒绝访问”,如图1.5,NDK命令是无法使用的,这个时候打开CMD时要“以管理员的身份运行”,如图1.6,这样才能成功切换到ndk-bundle路径下。
图1.5
图1.6
4.按截图步骤操作命令,在ndk-bunde路径下输入ndk-build命令,出现下面一段话:
Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
C:\android-sdks\ndk-bundle\build\\..\build\core\build-local.mk:151: *** Android NDK: Aborting . Stop。
NDK的环境即为配置成功,如图1.7。
图1.7
二、JNI基本配置
1.打开local.properties,添加ndk.dir=NDK的路径,如图2.1
图2.1
2. 打开gradle.properties,添加:android.useDeprecatedNdk=true,如图2.2:
图2.2
3. 最后打开app内build.gradle,在android下面添加如下代码,ndk-build生成的so包所放的libs:
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
配置好以后,立刻Make Project编译工程,编译成功即可,如果出现报错,工程无法编译通过(有的as编译项目时就会报错),按如图2.3配置,即可解决问题。
图2.3
1. 在src\main\java\包名,创建一个Jni的工具类,并定义接口函数,函数关键字用native(static用不用均可),stableWeightPredict()方法即要与C层的交互的函数,
static {
System.loadLibrary("JniLibrary");
},如图3.1
其中"JniLibrary"要和app内build.gradle中的android/defaultConfig下面配置的ndk内的moduleName要一致,moduleName 表示编译出的so文件的名字;如果还要在C文件中打印日志,ndk还要配置ldLibs "log", "z", "m"。即为
ndk {
moduleName "JniLibrary"
ldLibs "log", "z", "m"
}
如图3.2。
图3.1
图3.2
2. 在src\main\路径下,创建文件夹“jni”,jni下首先手动新建Android.mk和Application.mk两个文件夹,文件夹“jni”要与文件夹“java”平行,如图3.3
图3.3
3.Android.mk和Application.mk内的配置分别如图3.4和3.5,如果想在C代码中打印日志,除了在app内build.gradle中的NDK配置外,还要在Android.mk中添加LOCAL_LDLIBS := -llog。且配置顺序一定要按照如图3.4所示,否则执行ndk-build会报意想不到的错,其中“LOCAL_MODULE :=”后面的内容要和app内build.gradle中的ndk配置的moduleName一致,其中“LOCAL_SRC_FILES := ”后面的内容即为各种C文件的路径,C文件的路径是什么这里配置什么路径,多个C文件之间空一格,每一行想换行的话用“ \”结尾,否则编译时也会报错。
Application.mk中的“APP_ABI :=”后面的配置是适配哪些cpu类型,也可以全部适配,“APP_ABI :=all”即可;“APP_PLATFORM:=”后面的内容为minSdkVersion,app内build.gradle中的defaultConfig里的minSdkVersion要和“APP_PLATFORM:=”一致,如图3.6,仅仅这样配置还不行,编译还会报错,还要在清单文件配置,如图3,7。
图3.4
图3.5
图3.6
图3.7
四、生成JNI接口所调用的C文件
1.打开android studio终端,在build下make project编译工程,使loadJNI.java文件生成loadJNI.class文件。 loadJNI.class文件在C:\Users\hand-hitech2\Desktop\AndroidStudioWork\hand16\app\build\intermediates\classes\debug\com\hand\hand16\loadJNI下面,如图4.1
图4.1
2.为了方便在android studio中使用NDK命令编译,打开android studio中的命令提示框,如图4.2,图4.3所示,在Terminal命令下,切换到ndk-bundle路径下执行命令ndk-build如果出现“拒绝访问”,将在后期无法使用ndk命令进行其他编译,首先重启电脑,如果重启电脑还是出现“拒绝访问”,打开cmd所在文件夹位置,右键 “属性”,打开命令提示符属性设置对话框,找到“选项”,勾选“使用旧版控制台”,然后重启电脑,重启android studio,同时以管理员的身份运行AS,这样设置执行ndk-build命令,就有权限使用它了,如果还是出现“拒绝访问”,你想在as中使用命令行,需要你继续解决这个“拒绝访问”的问题,或者你可以用电脑上的cmd,以管理员身份运行,在cmd中执行ndk命令。
图4.2
图4.3
在图4.3中,先通过cd\命令切换到C:\>,然后找到debug所在路径,也就是生成的class文件所在的上一级目录,一定要在上一级哦,不然会报错:找不到xxx类,通过命令cd\ Users\hand-hitech2\Desktop\AndroidStudioWork\hand16\app\build\intermediates\classes\debug,切换到debug路径下,通过javah 包名+文件名,即javah com.hand.hand16.loadJNI.loadJNI生成.h头文件,如图4.4,生成的com_hand_hand16_loadJNI_loadJNI.h在debug目录下,如图4.5。
图4.4
图4.5
3.把生成的com_hand_hand16_loadJNI_loadJNI.h文件拷贝到“jni”文件夹下,在“jni”文件夹下新建com_hand_hand16_loadJNI_loadJNI.c文件,同时把转化后的C文件、h文件全部拷贝到“jni”文件夹下,公司生成的c文件有时会是以“.mm”结尾,这个时候要把“.mm”的文件改成“.c”,然后把每个“.c”文件的路径放到Android.mk的“LOCAL_SRC_FILES :=”后面(如何放置已在前面叙述过),如图4.6,图上Jni除了前4个是必须的,其他的C、H文件是你自己写的或者公司其他人写的C文件,你可以换成自己的C文件;
图4.6
4.所有c、h文件放到“jni”文件夹后,将下面这些代码拷贝到com_hand_hand16_loadJNI_loadJNI.h中去,如图4.7所示.
#include
#include
#include
#include
#include "stableWeightPredict_types.h"
图4.7
5. 把com_hand_hand16_loadJNI_loadJNI.h里面的内容复制到com_hand_hand16_loadJNI_loadJNI.c,并实现里面的函数,修改如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
#include "com_hand_hand16_loadJNI_loadJNI.h"
/**
* Created by wcf on 2018-04-24.
*/
/*
* Class: com_hand_ndkdemo_myJNI
* Method: stableWeightPredict
* Signature: ([DIDDDDD)D
*/
JNIEXPORT jdouble JNICALL Java_com_hand_hand16_loadJNI_loadJNI_stableWeightPredict
(JNIEnv *env, jclass jz, jdoubleArray Sensor_data, jdouble clear_flag, jdoubleArray last_data){
return ;
}
为了能在C代码中打印日志,除了前面说的配置之外,还要把下面的内容拷贝进去:
#include
#include
#include
#define TAG "jni调试测试重量" // 这个是自定义的LOG的标识
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) // 定义LOGE类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
同时为了能调用到转化后的C代码,还要添加以下内容:
#include "stableWeightPredict_initialize.h"
这样添加以后,com_hand_hand16_loadJNI_loadJNI.c结果如下
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
#include "com_hand_hand16_loadJNI_loadJNI.h"
#include
#include
#include
#include "rt_nonfinite.h"
#include "stableWeightPredict.h"
#define TAG "jni调试测试重量" // 这个是自定义的LOG的标识
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) // 定义LOGE类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
JNIEXPORT jdouble JNICALL Java_com_hand_hand16_loadJNI_loadJNI_stableWeightPredict
(JNIEnv *env, jclass jz, jdoubleArray Sensor_data, jdouble clear_flag, jdoubleArray last_data){
return;
}
6.上述方法中,java与C交互涉及到java向C传递数组,供C调用,而c是不能直接调用java数组的,需要转化成c能调用的数组类型,将java数组Sensor_data和last_data做如下编写,
jdouble * arr_Sensor_data= (*env)->GetDoubleArrayElements(env,Sensor_data,NULL);
jdouble * arr_last_data= (*env)->GetDoubleArrayElements(env,last_data,NULL);
然后调用返回重量的方法,原生的返回重量的方法仅仅返回重量值,但是由于java通过jni调用返回重量的方法stableWeightPredict()一次后,还需要把stableWeightPredict()方法中最后计算的status_flag,locked_weight,stable_counter,last_weight获取到,并再次通过java调用jni传递给stableWeightPredict(),所以,把stableWeightPredict()返回单独一个重量值修改为返回带有上述4个状态值的数组。stableWeightPredict.c修改stableWeightPredict()函数有返回double型改为返回指针“*”,stableWeightPredict.c结果如下:
Double * stableWeightPredict( const double Sensor_data[ 8 ] , double clear_flag ,
//Double * stableWeightPredict(const double Sensor_data[8], double clear_flag,
double last_data[8], double *status_flag, double *locked_weight, double
*stable_counter, double *last_weight)
{
double output_weight;
double d1;
double b_Sensor_data[8];
int i1;
double cur_weight;
double data_diff;
output_weight = 0.0;
switch ((int)*status_flag) {
case 0:
if (clear_flag == 1.0) {
*status_flag = 1.0;
} else {
d1 = 0.0;
for (i1 = 0; i1 < 8; i1++) {
data_diff = Sensor_data[i1] - last_data[i1];
d1 += data_diff * data_diff;
}
if (d1 > 260.0) {
*status_flag = 2.0;
*stable_counter = 0.0;
}
}
output_weight = *locked_weight;
*last_weight = *locked_weight;
break;
case 1:
d1 = 0.0;
for (i1 = 0; i1 < 8; i1++) {
data_diff = Sensor_data[i1] - last_data[i1];
d1 += data_diff * data_diff;
}
if (d1 > 260.0) {
*status_flag = 2.0;
*stable_counter = 0.0;
}
memcpy(&last_data[0], &Sensor_data[0], sizeof(double) << 3);
*last_weight = 0.0;
if (clear_flag == 1.0) {
*status_flag = 0.0;
*locked_weight = 0.0;
}
break;
case 2:
memcpy(&b_Sensor_data[0], &Sensor_data[0], sizeof(double) << 3);
cur_weight = weightPredictFunction(b_Sensor_data);
if (fabs(cur_weight - *last_weight) < 2.0) {
(*stable_counter)++;
if (*stable_counter >= 3.0) {
if (cur_weight > 2.0) {
*status_flag = 0.0;
*locked_weight = cur_weight;
} else {
*status_flag = 1.0;
}
}
} else {
*stable_counter = 0.0;
if ((0.0 >= cur_weight) || rtIsNaN(cur_weight)) {
cur_weight = 0.0;
}
}
output_weight = cur_weight;
*last_weight = cur_weight;
if (clear_flag == 1.0) {
*status_flag = 0.0;
*locked_weight = 0.0;
}
memcpy(&last_data[0], &Sensor_data[0], sizeof(double) << 3);
break;
}
double arr[4] = {output_weight,*status_flag,*locked_weight,*stable_counter};
return arr;
}
同时修改头文件stableWeightPredict.h如下,有返回double型改为返回指针“*”,结果如下:
#define STABLEWEIGHTPREDICT_H
// Include Files
#include
#include
#include
#include
#include "stableWeightPredict_types.h"
// Function Declarations
extern double * stableWeightPredict(const double Sensor_data[8], double clear_flag,
double last_data[8], double *status_flag, double *locked_weight, double
*stable_counter, double *last_weight);
#endif
然后在com_hand_hand16_loadJNI_loadJNI.c中的stableWeightPredict_initialize()方法下面调用函数
stableWeightPredict(const double Sensor_data[8], double clear_flag,double last_data[8], double *status_flag, double *locked_weight, double *stable_counter, double *last_weight);
返回值也是指针“*”,同时传递参数时要通过“&”先获取java通过jni传递参数的内存地址,如下所示:
Double *weight
=stableWeightPredict(arr_Sensor_data,clear_flag,arr_last_data,&status_flag,&locked_weight,&stable_counter,&last_weight);
返回的是数组的内存地址指针,status_flag,locked_weight,stable_counter,last_weight也不在返回给java调用,直接在com_hand_hand16_loadJNI_loadJNI.c中通过全局变量来接收每次调用stableWeightPredict()函数返回的值,并供下次调用,com_hand_hand16_loadJNI_loadJNI.c最终结果如下:
* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
#include "com_hand_hand16_loadJNI_loadJNI.h"
#include
#include
#include
#include "stableWeightPredict.h"
#define TAG "jni调试测试重量" // 这个是自定义的LOG的标识
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) // 定义LOGE类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
double status_flag = 0;
double locked_weight = 0;
double stable_counter = 0;
double last_weight = 0;
JNIEXPORT jdouble JNICALL Java_com_hand_hand16_loadJNI_loadJNI_stableWeightPredict
(JNIEnv *env, jclass jz, jdoubleArray Sensor_data, jdouble clear_flag, jdoubleArray last_data){
jdouble * arr_Sensor_data= (*env)->GetDoubleArrayElements(env,Sensor_data,NULL);
jdouble * arr_last_data= (*env)->GetDoubleArrayElements(env,last_data,NULL);
stableWeightPredict_initialize();
double *weight=stableWeightPredict(arr_Sensor_data,clear_flag,arr_last_data,&status_flag,&locked_weight,&stable_counter,&last_weight);
//LOGE("数组内存地址== %p",weight);
//赋值
last_weight =*(weight+0);
status_flag=*(weight+1);
locked_weight=*(weight+2);
stable_counter=*(weight+3);
for(int i=0;i<4;i++){
LOGE("数组值==%f",*(weight+i));
}
stableWeightPredict_terminate();
return last_weight;
}
其中*(weight+i)是通过内存地址获取C数组的每一个数组内容,和java还是不一样的,特别注意;
五、最重要一步,生成so文件
1.打开android studio,打开Terminal命令框,切换到“jni”路径下,命令:cd\Users\hand-hitech2\Desktop\AndroidStudioWork\hand16\app\src\main\jni,在“jni”路径下输入命令:ndk-build,没有报错,就会成功生成so文件,如果报错,请根据所报的错误一一解决,如图5.1和如图5.2.
图5.1
图5.2.
2.然后将src\main\libs下面的包so文件的所有文件夹剪切到app\libs下面,也就是在app内build-gradle中设置的路径
sourceSets.main {
jniLibs.srcDir 'libs'
jni.srcDirs = []
}
结果如图5.3所示
图5.3
六、Activity中调用jni
一切工作准备就绪,so文件也成功生成,在Activity中就可以根据需要传递的参数进行组织参数,然后调用:
double weightPredict = loadJNI.stableWeightPredict(Sensor_data, clear_flag, last_data);
返回结果与预期一样,jni即为开发成功。