Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组


一、 NDK环境配置

1.首先需要下载NDKNative Development Kit),在Android studio点击 file → setting如图1.0

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第1张图片

1.0

点击seting,进入如图1.1按截图找到如下路径选择NDK → 确定应用下载

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第2张图片

1.1

NDK下载完毕,下载好的NDK包的路径是在所已经下载的SDK中,如图1.3NDK路径为C:\android-sdks\ndk-bundle,

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第3张图片

1.3

2.复制ndk-bundle路径在电脑上配置环境变量win10为例:右键“我的电脑”,点击“属性”,找到“高级属性设置”并点击,进入“系统属性”界面,选择“环境变量”并点击,进入“环境变量”界面,在“系统变量”找到“path,点击“编辑”,出现如图1.4,点击“新增”,将ndk-bundle的路径配置到path”后面。

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第4张图片

1.4

3.配置好NDK环境变量后,然后检查NDK是否配置成功,打开cmd,在命令行通过cd 切换到ndk-bundle路径下如果切换不成功出现“拒绝访问”,如图1.5NDK命令是无法使用的,这个时候打开CMD时要“以管理员的身份运行”,如图1.6,这样才能成功切换到ndk-bundle路径下

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第5张图片

1.5

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第6张图片

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

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第7张图片

 

1.7

二、JNI基本配置

1.打开local.properties,添加ndk.dir=NDK的路径,如图2.1

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第8张图片

2.1

2. 打开gradle.properties,添加:android.useDeprecatedNdk=true如图2.2

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第9张图片

2.2

3. 最后打开appbuild.gradle,在android下面添加如下代码,ndk-build生成的so包所放的libs

 sourceSets {

        main {

            jniLibs.srcDirs = ['libs']

        }

}

配置好以后,立刻Make Project编译工程,编译成功即可,如果出现报错工程无法编译通过有的as编译项目时就会报错),按如图2.3配置,即可解决问题。

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第10张图片

 

2.3

新建java访问c层的接口类

1. src\main\java\包名创建一个Jni的工具类,并定义接口函数,函数关键字用nativestatic用不用均可)stableWeightPredict()方法即要与C层的交互的函数,

 static {

        System.loadLibrary("JniLibrary");

},如图3.1

其中"JniLibrary"要和appbuild.gradleandroid/defaultConfig下面配置的ndk内的moduleName要一致moduleName 表示编译出的so文件的名字如果还要在C文件中打印日志,ndk还要配置ldLibs "log", "z", "m"。即为

ndk {

          moduleName "JniLibrary"

            ldLibs "log", "z", "m"

        }

如图3.2

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第11张图片


3.1

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第12张图片

3.2

 

2. src\main\路径下创建文件夹jni,jni下首先手动新建Android.mkApplication.mk两个文件夹,文件夹jni”要与文件夹“java”平行,如图3.3

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第13张图片

 

3.3

3.Android.mkApplication.mk内的配置分别如图3.43.5,如果想在C代码中打印日志,除了在appbuild.gradle中的NDK配置外,还要在Android.mk中添加LOCAL_LDLIBS := -llog。且配置顺序一定要按照如图3.4所示,否则执行ndk-build会报意想不到的错,其中“LOCAL_MODULE :=”后面的内容要和appbuild.gradle中的ndk配置的moduleName一致,其中LOCAL_SRC_FILES := ”后面的内容即为各种C文件的路径,C文件的路径是什么这里配置什么路径,多个C文件之间空一格,每一行想换行的话用“ \”结尾,否则编译时也会报错。

Application.mk中的APP_ABI :=”后面的配置是适配哪些cpu类型,也可以全部适配,“APP_ABI :=all”即可;“APP_PLATFORM=”后面的内容为minSdkVersionappbuild.gradle中的defaultConfig里的minSdkVersion要和APP_PLATFORM=”一致,如图3.6,仅仅这样配置还不行,编译还会报错,还要在清单文件配置,如图3,7

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第14张图片

3.4

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第15张图片

3.5

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第16张图片

3.6

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第17张图片

3.7

四、生成JNI接口所调用的C文件

1.打开android studio终端,在buildmake 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

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第18张图片 

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命令

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第19张图片

4.2

 

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第20张图片

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.hdebug目录下如图4.5

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第21张图片 

4.4

 Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第22张图片

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.mkLOCAL_SRC_FILES :=”后面(如何放置已在前面叙述过),如图4.6,图上Jni除了前4个是必须的,其他的C、H文件是你自己写的或者公司其他人写的C文件,你可以换成自己的C文件;

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第23张图片 

4.6

4.所有ch文件放到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.上述方法中,javaC交互涉及到javaC传递数组,供C调用,而c是不能直接调用java数组的需要转化成c能调用的数组类型,将java数组Sensor_datalast_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_flaglocked_weightstable_counterlast_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_flaglocked_weightstable_counterlast_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.

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第24张图片 

5.1

 

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第25张图片 

 

5.2.

2.然后将src\main\libs下面的包so文件的所有文件夹剪切到app\libs下面也就是在appbuild-gradle中设置的路径

sourceSets.main {
    jniLibs.srcDir 'libs'
    jni.srcDirs = []
}

结果如图5.3所示

Android Studio 开发NDK、JNI流程-java向C传递数组,C返回数组_第26张图片 

5.3

Activity中调用jni

一切工作准备就绪so文件也成功生成Activity中就可以根据需要传递的参数进行组织参数然后调用

double weightPredict = loadJNI.stableWeightPredict(Sensor_data, clear_flag, last_data);

返回结果与预期一样jni即为开发成功

 


你可能感兴趣的:(android)