Android JNI/NDK开发(2)JNI实现C/C++与Android/JAVA相互调用

前言
一直觉得无论是从行业形式来看,还是从就业角度说,学习JNI/NDK开发是一条必经之路。很多说Android和IOS几乎没有市场了,其实只能说现在开发APP的需求没那么多了,再加上之前培训机构疯狂向市场输出Android/IOS开发人员,导致市场供过于求,难免出现一职难求的现象。不过我不认为Android会死,虽然APP的需求少了,但是Android又不是只能做APP,除非你只会APP开发。哈哈

第一部分:环境配置和简单demo运行参考
Android JNI/NDK开发(一)NDK真的很难吗?

1.1JNI/NDK在Android studio 上基本配置

参考连接http://blog.csdn.net/krubo1/article/details/50547681

1.2Android Studio 定制快速生成Jni 头文件工具 NDK教程

参考连接http://blog.csdn.net/silver_R/article/details/48457077

备注1:
在jni文件下添加一个空的C文件 empty.c 能解决如下报错

Error:Execution failed for task ‘:app:compileDebugNdk’. 
NDK_PROJECT_PATH=null

备注2:
在 app > build.gradle 中配置ndk

        //ndk配置
        ndk {
            moduleName "RLibs"
            //so文件包名
            stl "stlport_static"
            //NDK中C++标准库、STL的配置
            //如未配置可能出现错误: fatal error: iostream: No such file or directory
            ldLibs "log", "z", "m"
            abiFilters "armeabi", "armeabi-v7a", "x86"
        }

第二部分(使用Android studio实现):JNI实现C++与Java之间函数的相互调用

我们知道Java和C/C++是两种不同的语言,不能直接通信,所以需要通过中间桥梁JNI将他们连接起来。

JNI充当中间桥梁,那么它肯定存在很多函数可以将Java的基本数据类型转化为JNI通用的类型,相对于C/C++也是一样。下面给出一个

JNI中文学习文档http://www.cnblogs.com/jycboy/archive/2016/04/15/5396876.html#jialianbendi

给出一片文档就算了?那怎么可能啊,当时我在网上看到这个文档的时候我觉得JNI没问题了,但还是动手试了一下,不试不知道,动手一敲,一脸懵逼。那还是跟着看看Java层代码是如何跟C/C++代码交互的吧。

需求:
1.1:Java调用C++方法获取基本数据类型(int,double,string)
1.1:Java调用C++无返回值方法
2.1:C++调用Java非静态方法获取基本数据类型(int,double,string)
2.2:C++调用Java静态方法获取返回值
2.3:C++调用Java无返回值方法

OK,Java中写一个native工具类,定义native方法,以及Java中方法的实现

/**
 * native工具类
 *
 * @author ZhongDaFeng
 * @date 2017/5/3 14:48
 */
public class NativeUtils {

    public static Context mContext;

    static {
        System.loadLibrary("RLibs");
    }

    //从native获取int类型数据
    public native int getIntFromNative(int javaInt);

    //从native获取double类型数据
    public native double getDoubleFromNative(double javaDouble);

    //从native获取string类型数据
    public native String getStringFromNative(String javaString);

    //从native获取ArrayList<String>类型数据
    public native ArrayList<String> getArrayListFromNative(ArrayList<String> javaList);

    //模拟native层调用安卓Toast
    public native void nativeCallAndroidToast();

    //模拟native层调用java基本类型信息
    public native String nativeCallJavaBaseMsg();


    //native获取int类型数据
    public int getIntFromJava(int nativeInt) {
        int javaInt = 100;
        return javaInt + nativeInt;
    }

    //native获取double类型数据
    public double getDoubleFromJava(double nativeDouble) {
        double javaDouble = 100.0;
        return javaDouble + nativeDouble;
    }

    //native获取string类型数据
    public String getStringFromJava(String nativeString) {
        String javaStr = " Java Static String : ";
        String split = " call ";
        return nativeString + split + javaStr ;
    }

    //native获取ArrayList<String>类型数据
    public ArrayList<String> getArrayListFromJava(ArrayList<String> nativeList) {
        return null;
    }

    //native 获取静态方法数据
    public static String getStringFromJavaWithStatic(String nativeString) {
        String javaStr = " java static string  ";
        String split = " && ";
        return javaStr + split + nativeString;
    }

    // 提示信息,提供JNI调用
    public void showToast(String msg) {
        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
    }

}

根据这个类生成com_r_ndk_NativeUtils.h头文件(文章开头有说明如何生成)

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_r_ndk_NativeUtils */

#ifndef _Included_com_r_ndk_NativeUtils
#define _Included_com_r_ndk_NativeUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    getIntFromNative
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_r_ndk_NativeUtils_getIntFromNative
  (JNIEnv *, jobject, jint);

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    getDoubleFromNative
 * Signature: (D)D
 */
JNIEXPORT jdouble JNICALL Java_com_r_ndk_NativeUtils_getDoubleFromNative
  (JNIEnv *, jobject, jdouble);

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    getStringFromNative
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_r_ndk_NativeUtils_getStringFromNative
  (JNIEnv *, jobject, jstring);

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    getArrayListFromNative
 * Signature: (Ljava/util/ArrayList;)Ljava/util/ArrayList;
 */
JNIEXPORT jobject JNICALL Java_com_r_ndk_NativeUtils_getArrayListFromNative
  (JNIEnv *, jobject, jobject);

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    nativeCallAndroidToast
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_r_ndk_NativeUtils_nativeCallAndroidToast
  (JNIEnv *, jobject);

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    nativeCallJavaBaseMsg
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_r_ndk_NativeUtils_nativeCallJavaBaseMsg
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

复制h文件,修改为C/C++文件(随便命名)实现业务逻辑

第一个需求:Java调用C++方法获取基本数据类型(int,double,string)

先编写C++逻辑

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    getIntFromNative
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_r_ndk_NativeUtils_getIntFromNative
  (JNIEnv * env, jobject obj, jint j_int){
   int result=50;
   result+=j_int;
   return result;
  }

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    getDoubleFromNative
 * Signature: (D)D
 */
JNIEXPORT jdouble JNICALL Java_com_r_ndk_NativeUtils_getDoubleFromNative
  (JNIEnv * env, jobject obj, jdouble j_double){
    double result=50.0;
     result+=j_double;
     return result;
  }

这两个函数,不难看出,JNI接受从Java传递过来的int /double类型的值,在本地代码(C++)中加上一定的值然后再返回给Java
jint/jdouble/jstring 等等是JNI的数据类型(第一篇博客中有说明),在C++中int/double类型可以直接转为对应的JNI类型使用。
所以到这里我们解决了一个问题:
从Java传递值过来,在生成h头文件的时候会直接转化为JNI对应的数据类型 int=>jint,而对于C/C++中int/double等类型可以直接转化为JNI的类型使用,那么JNI就将Java和C++这些基本的数据类型连接起来了。

这个不难,相对可恶的是string类型的数据,我们知道其实string不是Java的基本数据类型,而是封装的对象,所以这个string的处理比较麻烦一点。现在来分析一下string字符串处理的函数,代码:

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    getStringFromNative
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_r_ndk_NativeUtils_getStringFromNative
  (JNIEnv * env, jobject obj, jstring j_string){

      const char *java_str_char = env->GetStringUTFChars(j_string, NULL);//获取java字符串
      if(java_str_char == NULL){
        return NULL;
      }
      char const *native_char = " Native :";
      char const *split_char = " call ";
      std::string const& temp = std::string(java_str_char) +std::string(split_char) + std::string(native_char);
      char const *result = temp.c_str();
      env->ReleaseStringUTFChars( j_string, java_str_char);//释放
      return env->NewStringUTF(result);
  }

对于int、double这类数据JNI和C++是可以直接转换使用,那么对于不能直接转换的怎么办呢?肯定会有一些函数的对吧,我们查看刚刚那个JNI中文文档
解读一下这个函数的代码(C++实现)
函数第1行获取Java的string类型数据转化为C++中的字符,JNI函数:env->GetStringUTFChars(j_string, NULL);
接着判断字符串是否==null,据了解,C/C++不会报空指针之类的错误,如果这里为空没有判断,下面代码还是使用会导致很严重问题,比如说你找错找的一脸懵逼,完全不知道哪里的问题,培养严谨的编程习惯,这些一定要判断,(获取接下来的示例中我可能没有写这些判断,不要学我,我只是demo快速写逻辑而已),接下来的代码就是在C++中创建字符,然后转成string类型,将Java传递过来的string字符串和C++的字符串拼接起来再返回出去。这里还要注意:env->ReleaseStringUTFChars( j_string, java_str_char);//释放资源方法,第一行代码获取字符串,这里释放资源,成对使用。
其实如果懂得C++编程的话这里还是很简单的,无非就是如何在C++中创建字符,传唤成为字符串,拼接起来。
C++部分的代码写完了,在Java中调用,代码:

                //模拟Java层调用native基本信息
                msg = mNativeUtils.getStringFromNative(" Java ");
                int nativeInt = mNativeUtils.getIntFromNative(33);
                double nativeDouble = mNativeUtils.getDoubleFromNative(33.33);
                String result = msg + " int: " + nativeInt + " double: " + nativeDouble;
                mtv_text.setText(result);

效果图
这里写图片描述

第二个需求:C++调用Java非静态方法获取基本数据类型(int,double,string)

直接看C++代码

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    nativeCallJavaBaseMsg
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_r_ndk_NativeUtils_nativeCallJavaBaseMsg
   (JNIEnv * env, jobject obj){

    jclass j_class = env->FindClass("com/r/ndk/NativeUtils");
    jobject j_object = env->AllocObject(j_class);

    jmethodID j_method_id1 = env->GetMethodID(j_class, "getStringFromJava","(Ljava/lang/String;)Ljava/lang/String;");
    jmethodID j_method_id2 = env->GetMethodID(j_class, "getDoubleFromJava","(D)D");
    jmethodID j_method_id3 = env->GetMethodID(j_class, "getIntFromJava","(I)I");

    jstring j_str1 = (jstring) env->NewStringUTF(" Native ") ;
    jstring j_str2 = (jstring) env->CallObjectMethod(j_object, j_method_id1,j_str1);//从java层获取string
    jdouble j_double=(jdouble) env->CallDoubleMethod(j_object, j_method_id2,66.66);//从Java层获取double
    jint j_int=(jint) env->CallIntMethod(j_object, j_method_id3,66);//从Java层获取int

    string j_double_str=double2string(j_double);
    string j_int_str=int2string(j_int);
    string tip_int=" int: ";
    string tip_double=" double: ";

    const char * result_string_char = env->GetStringUTFChars(j_str2, NULL);//获取java字符串
    string result_number_str = tip_int + j_int_str + tip_double + j_double_str;//拼接数字
    string result_str = std::string(result_string_char)  + result_number_str;//拼接数字和字符串
    const char* result_char = result_str.c_str();//字符串转换为char
    env->ReleaseStringUTFChars( j_str2, result_string_char);//释放
    return env->NewStringUTF(result_char);
   }

解析:在C++层获取Java数据,首先需要知道要调用的 方法 在哪个 参数 类型等
1.首先获取需要操作的类:env->FindClass(“com/r/ndk/NativeUtils”);
备注:填写类的绝对路径,将 . 换成 /
2.找到需要调用的方法的ID:env->GetMethodID(j_class, “getStringFromJava”,”(Ljava/lang/String;)Ljava/lang/String;”);
备注:第一个参数 类名,第二个参数方法名,第三个参数签名类型
JNI使用Java虚拟机的类型签名表述。下表列出了这些类型签名:
这里写图片描述
例如,Java方法:

long f (int n, String s, int[] arr);

具有以下类型签名:

(ILjava/lang/String;[I)J

特别注意一点:全限定的类后面一定要加 ; 否则开发中遇到运行报错,一脸懵逼不知到什么问题
获取方法ID可以查看JNI中文文档,基本上是:GetMethodID,对于调用静态方法:GetStaticMethodID

3.调用方法:env->CallObjectMethod(j_object, j_method_id1,j_str1);
备注:第一个参数对象,第二个参数方法ID,第三个参数传递的数值(可传多个参数,多种类型的值)
调用方法原型 CallMethod(参数,参数,参数…)type:object,int,double,void ….
静态方法调用:CallStaticObjectMethod(j_class, j_method_id2 ,j_string1); 不同于普通方法,第一个参数需要传class而不是object

这个C++函数主要是介绍这三个方法的使用,其他的逻辑就是,获取到数值转化为string拼接然后返回,很简单的啦。其中有两个方法的代码没有贴上来,是int/double转化为C++字符串的代码,之后在完整的demo中会给出。
Java调用代码:

                //模拟native层调用Java基本数据类型
                msg = mNativeUtils.nativeCallJavaBaseMsg();
                mtv_text.setText(msg);

这个demo在C++层中获取Java的基本数据类型,简单处理一下,再返回给Java展示。

这里写图片描述

到这里基本上实现了C++和Java之间数据的相互调用和传递,接着再实现一下,当C++调用Java之后,Java在处理一下的方法:在C++中传递一个string字符串,Java接收之后Toast提示给用户。(顺便讲解一下C++调用Java静态方法的用例)
看C++代码

/*
 * Class:     com_r_ndk_NativeUtils
 * Method:    nativeCallAndroidToast
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_r_ndk_NativeUtils_nativeCallAndroidToast
  (JNIEnv * env, jobject obj){

    jclass j_class = env->FindClass("com/r/ndk/NativeUtils");
    jobject j_object = env->AllocObject(j_class);

    jmethodID j_method_id1 = env->GetMethodID(j_class, "showToast","(Ljava/lang/String;)V");//获取方法ID
    jmethodID j_method_id2 = env->GetStaticMethodID(j_class, "getStringFromJavaWithStatic","(Ljava/lang/String;)Ljava/lang/String;");//获取静态方法ID
    jstring j_string1=(jstring)env->NewStringUTF(" c++ string ");
    jstring j_string2 = (jstring)env->CallStaticObjectMethod(j_class, j_method_id2 ,j_string1);//调用静态方法
    env->CallVoidMethod(j_object,j_method_id1,j_string2);//调用方法

  }

Java代码

    // 提示信息,提供JNI调用
    public void showToast(String msg) {
        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
    }

Java调用代码

    //模拟native层调用安卓Toast
    mNativeUtils.nativeCallAndroidToast();

效果:
这里写图片描述

嗯,到这里基本上Java和C++之间的相互都过了一遍,对于拓展方面可以参考JNI中文文档进行学习,开发中多少都会遇到坑,多查查资料,解决之后共享出来,一起进步。

源码下载

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值