Android JNI 详解

简介

JNI 应该是所有Android老鸟都绕不过的“坎”吧,之所以说是“坎”是因为它比较难,因为他不仅涉及Android开发者的“本命”语言—Java,还要求开发者对C/C++有相当的基础,同时如何协调两种语言的运行时也是重难点之一。

难度是有点,不过,一旦掌握,无疑会给开发者打开一扇通往新世界的大门——openGL、openSL、OpenCV等一系列优秀成熟的库,任君采摘。既可以为你的应用插上高效的翅膀,也为你装逼提供了基础,哇哈哈哈,人生在世,何处不装逼!

本文详细简介JNI在Android中的使用。主要参考https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html。不过里边都是英文, 不喜看本文即可。

本文工程目录地址:https://github.com/MrHeLi/JNItest

环境

任何没有环境就说程序的文章都是耍流氓(我估计耍了不少,^_^)。

Android Studio 3.1.2Build #AI-173.4720617, built on April 14, 2018JRE: 1.8.0_152-release-1024-b02 amd64JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.oWindows 7 6.1

使用Android Studio创建JNI工程

相信想要学习JNI知识的都是些老鸟,创建步骤就简略一些:

File —–> New —–> New Project (在这一步需要如下图所示一样勾上C++ 支持)—-> Next —-> Next —-> Next。

和普通工程创建不同的是,创建成功后,支持C/C++会多 native-lib.cpp 和 CMakeLists.txt两个文件。关于CMakeLists.txt会另起一篇讲。

TODO: CMakeLists.txt讲解

本文的重点集中在native-lib.cpp所代表的C/C++代码和MainActivity.java代表的java代码之间的交互。

现在的开发工具真的是强大,Android studio 在创建工程时就可以将一些JNI的工具加入工程中,再也不用执行一些琐碎的javah、gcc等命令,为Android开发人员解放了双手(也可以说解放了恐惧),节约了时间

JNI下的MainActivity分析

public class MainActivity extends AppCompatActivity {    /* 使用静态代码块加载'native-lib'库,该库即为C/C++代码编译后的共享库,    * 加载后才能让java层调用C/C++的代码。    * 库名称由CMakeLists.txt文件中的add_library指定。通常此处名称是native-lib的话,    * 那么编译成功的共享库名称为libnative-lib.so。    **/    static {        System.loadLibrary("native-lib");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        // Example of a call to a native method        TextView tv = (TextView) findViewById(R.id.sample_text);        tv.setText(stringFromJNI());//本地方法调用和java函数调用毫无二致    }    /**     * 本地'native-lib'库中实现了的本地方法,共享库会打包到本应用中     * 区别于普通java函数,在函数申明中多了个native字段,以及没有函数体     */    public native String stringFromJNI();

JNI下的C/C++代码分析

#include #include #include #include #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "In C/C++:", __VA_ARGS__);using namespace std;extern "C" JNIEXPORT jstring JNICALLJava_com_dali_jnitest_MainActivity_stringFromJNI(        JNIEnv *env,        jobject /* this */) {    std::string hello = "Hello from C++";    return env->NewStringUTF(hello.c_str());}

JNI中C/C++代码对应于java中的函数,命令有一定的规则。

我们看一下本例中,java函数和C/C++函数的对应关系:

java部分:

类名:com.dali.jnitest.MainActivity

方法名:public native String stringFromJNI();

c/c++部分:

JNIEXPORT jstring JNICALL Java_com_dali_jnitest_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */)

  • JNIEXPORT : 可以当做JNI方法的函数申明关键字。
  • jstring : 函数返回值,jstring 对应的是java的String对象。
  • JNICALL : 可以认为是JNI访问的关键字,固定格式啦。
  • Java_com_dali_jnitest_MainActivity_stringFromJNI: 在C函数中的方法名格式为Java_{package_and_classname}_{function_name}(JNI_arguments) ,只需要将包名中的点换成下横线就行。
  • JNIEnv *env: JNI的环境引用,一个非常有用的变量,可以通过它调用所有JNI函数。
  • jobject /* this */:函数调用者的对象,相当于java层中的this

JNI 基础

上面做了点环境铺垫,接下来开始上正菜。

我们都知道,java的数据类型和C/C++的数据类型并不一致,典型的例子是:java中的String是一个引用数据类型,但在C语言中的String是以NULL结尾的字符串数组。所以协调数据类型,是JNI的重点内容。

JNI 定义了如下的JNI类型用于本地代码中,对应java的数据类型:

  1. java 基础数据类型:

    下表是对应关系

JNI数据类型 java数据类型
jint int
jbyte byte
jshort short
jlong long
jfloat float
jdouble double
jchar char
jboolean boolean
  1. java 引用数据类型:
JNI数据类型 java数据类型
jobject java.lang.Object
jclass java.lang.Class
jstring java.lang.String
jthrowable java.lang.Throwable

在java中,数组中的数据类型和JNI的数组类型对应定义:

JNI数据类型 java数据类型
jintArray int []
jbyteArray byte []
jshortArray short []
jlongArray long []
jfloatArray float []
jdoubleArray double []
jcharArray char []
jbooleanArray boolean []
jobjectArray Object []

本地程序调用基本顺序

  1. 使用JNI数据类型接收参数(该参数通过java程序调用传递)
  2. 对于JNI引用数据类型,将参数转换或者复制为本地类型。比如:jstring 转为 C-string, jintArray转为C’s int[]等等。基本数据类型,例如jint, jdouble可以直接使用而不需要转换。
  3. 使用本地数据类型执行程序。
  4. 创建一个JNI类型的对象,用作返回(return),将程序运行的结果复制到返回对象中。
  5. 函数返回(return)。

在JNI程序开发过程中,比较困难而极具挑战的是JNI引用类型(例如 jstring, jobject, jintArray, jobjectArray) 和C本地数据类型(例如C-string, int[] )之间的转换。幸好,JNI环境提供了大量的函数来处理这种转换。

Java & Native 程序之间参数传递

基本数据类型传递

java中的8种基本数据类型可以直接被传递和使用,因为这些类型都在jni.h中申明了:

/* Primitive types that match up with Java equivalents. */typedef uint8_t  jboolean; /* unsigned 8 bits */typedef int8_t   jbyte;    /* signed 8 bits */typedef uint16_t jchar;    /* unsigned 16 bits */typedef int16_t  jshort;   /* signed 16 bits */typedef int32_t  jint;     /* signed 32 bits */typedef int64_t  jlong;    /* signed 64 bits */typedef float    jfloat;   /* 32-bit IEEE 754 */typedef double   jdouble;  /* 64-bit IEEE 754 */

示例

Java 层:

   public class MainActivity extends AppCompatActivity {   // 在程序开始时使用静态代码块加载'native-lib'库    static {        System.loadLibrary("native-lib");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);            Log.i("In java", String.valueOf(average(3, 4)));    }    //基本数据类型在c/java之间的传递    public native double average(int arg1, int arg2);

C 层:

extern "C"JNIEXPORT jdouble JNICALLJava_com_dali_jnitest_MainActivity_average(JNIEnv *env, jobject instance, jint arg1,                                           jint arg2) {    jdouble result;//基本数据类型无需变化,在jni.h中已经设置了类型别名    /**    *想要使用该打印,请在C文件头增加下列代码:    *include     *define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "In C/C++:", __VA_ARGS__);    */    LOGI("arg1: %d, ar2: %d", arg1, arg2);    result = (arg1 + arg2) / 2;    return result;}

运行程序:

com.dali.jnitest I/In C/C++: arg1: 3, ar2: 4com.dali.jnitest I/In java: 3.0

字符串传递

示例

java层:

public class MainActivity extends AppCompatActivity {   // 在程序开始时使用静态代码块加载'native-lib'库    static {        System.loadLibrary("native-lib");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Log.i("In java", testString("hello"));    }    //字符串在c/java之间的传递    public native String testString(String str);

c层:

extern "C"JNIEXPORT jstring JNICALLJava_com_dali_jnitest_MainActivity_testString(JNIEnv *env, jobject instance, jstring str_) {    //在C中的String是以NULL结尾的字符串数组,需要通过特定方法转换,但在C++中有对应的String,是否可以不     //转换?    const char *str = env->GetStringUTFChars(str_, 0);    LOGI("str: %s", str);    char* returnValue = "hehe ,wo lai le";    env->ReleaseStringUTFChars(str_, str);    return env->NewStringUTF(returnValue);}

JNI 定义了jstring类型来代表javaString。C层函数的最后一个参数(JNI类型的jstring)是Java层的String传递到C层的引用。该程序的返回值同样也是jstring类型。

传递字符串远比基本数据类型复杂,因为Java层的String是一个对象(引用数据类型),然而C层中的string是一个以NULL结尾的char数组。所以,使用时需要在Java层的String(以JNI 的jstring表示)和C层的string(char*)之间转换。

JNI环境(通过参数JNIENV*调用)提供了这种转换的函数:

  1. 使用const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)将JNIstringjstring)类型转换为C层的stringchar*)。
  2. 使用 jstring NewStringUTF(JNIEnv*, char*)将C层的stringchar*)转为JNIstringjstring)类型。

C层函数的实现步骤为:

  1. 从JNI的jstring接收数据,并通过GetStringUTFChars()转为C层的string (char*)类型。
  2. 然后执行程序,显示接收到的参数数据,并返回另外一个字符串。
  3. 将C层的string (char*)类型通过NewStringUTF()函数转换为JNI的jstring类型并返回。

运行程序:

com.dali.jnitest I/In C/C++: str: hellocom.dali.jnitest I/In java: hehe ,wo lai le

JNI本地String函数

JNI支持Unicode(16字节字符串)和UTF-8(1-3字节编码)不同格式字符串之间的转换。UTF-8编码的字符串和C语言中的字符串一样是以NULL结尾的char数组,用于C/C++程序中。

这些JNI字符串(jstring)为:

/** UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)* 获取以NULL结尾的字符数组,也就是C-string*/// 返回表示UTF-8编码字符串的数组指针const char * GetStringUTFChars(jstring string, jboolean *isCopy);// 通知VM 本地代码不再需要UTF引用。void ReleaseStringUTFChars(jstring string, const char *utf);// 根据字符串数组,构造一个UTF-8编码的java String新对象jstring NewStringUTF(const char *bytes);// 返回UTF-8编码字符串的长度jsize GetStringUTFLength(jstring string);// 将从偏移量start开始的length长度的Unicode字符转换为UTF-8编码,并将结果放在给定的缓冲区buf中。void GetStringUTFRegion(jstring str, jsize start, jsize length, char *buf);// Unicode Strings (16-bit character)// 返回指向Unicode字符数组的指针const jchar * GetStringChars(jstring string, jboolean *isCopy);// 通知VM本机代码不再需要访问字符。void ReleaseStringChars(jstring string, const jchar *chars);// 从Unicode字符数组构造一个新的java.lang.String对象。jstring NewString(const jchar *unicodeChars, jsize length);// 返回Java字符串的长度(Unicode字符数)。jsize GetStringLength(jstring string);// 将从偏移量=start开始的length长度的Unicode字符数复制到给定的缓冲区buf。void GetStringRegion(jstring str, jsize start, jsize length, jchar *buf);

UTF-8 strings & C-strings

GetStringUTFChars()函数可用于从给定的Java的jstring创建新的C字符串(char *)。 如果无法分配内存,则该函数返回NULL。 检查NULL是一个好习惯。

第三个参数isCopy(of jboolean *),它是一个“in-out”参数,如果返回的字符串是原始java.lang.String实例的副本,则将设置为JNI_TRUE。 如果返回的字符串是指向原始String实例的直接指针,则它将设置为JNI_FALSE- 在这种情况下,本机代码不应修改返回的字符串的内容。 如果可能,JNI运行时将尝试返回直接指针; 否则,它返回一份副本。 尽管如此,我们很少对修改底层字符串感兴趣,并且经常传递NULL指针。

不使用使用GetStringUTFChars()返回的字符串时,需要来释放内存和引用以便可以对其进行垃圾回收时,始终调用ReleaseStringUTFChars()

NewStringUTF()函数使用给定的C字符串创建一个新的JNI字符串(jstring)。

JDK 1.2引入了GetStringUTFRegion(),它将jstring(或从长度开始的一部分)复制到“预分配”的C的char数组中。 可以使用它们代替GetStringUTFChars()。 由于预先分配了C的数组,因此不需要isCopy

JDK 1.2还引入了Get / ReleaseStringCritical()函数。 与GetStringUTFChars()类似,如果可能,它返回一个直接指针; 否则,它返回一份副本。 本机方法不应阻止(对于IO或其他)一对GetStringCritical()ReleaseStringCritical()调用。

有关详细说明,请始终参阅“Java Native Interface Specification”@ http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html。

基本数据类型数组传递

示例

java层:

    public class MainActivity extends AppCompatActivity {    // 在程序开始时使用静态代码块加载'native-lib'库    static {        System.loadLibrary("native-lib");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        int[] numbers = {22, 33, 33};        double[] results = sumAndAverage(numbers);        Log.i("In java, the sum is ", ""+results[0]);        Log.i("In java, the average is", " "+results[1]);    }    //基础数据类型的数组传递    public native double[] sumAndAverage(int arr[]);

C层:

在Java中,数组(array)和类(class)一样,是引用数据类型。总共有9中数组类型,其中,8种基本数据类型数组和一个java.lang.Object数组类型。对于8中基本数据类型,JNI分别定义了以下数据类型与之对应jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray

同样的,你在编码过程中需要在JNI数据和本地数组之间转换,例如:jintArray 与 C的jint[],jdoubleArray 与 C的jdouble[]。JNI环境提供了支持这些转换的函数如下:

  1. JNI ——》C本地代码:

    使用jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy),从JNI的jintArray获取C语言的本地数组jint[]

  2. C本地代码 ——》JNI:

    • 首先使用jintArray NewIntArray(JNIEnv *env, jsize len)申请一片内存
    • 然后使用void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)将数据从C本地代码的jint[] 复制到JNI层的jintArray

如上面的两个函数特定用于int类型,与此类似,JNI中总共有8组类似函数对应于8中基本数据类型。

回到示例代码,基本数据类型数组传递的典型本地代码步骤如下:

  1. 从JNI参数接收数组数据,转换为C代码的本地数据(例如,jint[])。
  2. 执行程序。
  3. 将本地C代码的数据(例如,jdouble[])转换为JNI数组(例如,jdoubleArray),并且返回JNI数据。

代码如下:

extern "C"JNIEXPORT jdoubleArray JNICALLJava_com_dali_jnitest_MainActivity_sumAndAverage(JNIEnv *env, jobject instance,                                                               jintArray arr_) {    //从JNI参数接收数组数据,转换为C代码的本地数据(例如,jint[])。    jint *arr = env->GetIntArrayElements(arr_, NULL);    if (NULL == arr) return NULL;    jsize length = env->GetArrayLength(arr_);    // 执行程序    jint sum = 0;    int i;    for (i = 0; i < length; i++) {        sum += arr[i];    }    jdouble average = (jdouble)sum / length;    env->ReleaseIntArrayElements(arr_, arr, 0); // release resources    jdouble outCArray[] = {(jdouble)sum, average};    // 将本地C代码的数据(例如,jdouble[])转换为JNI数组(例如,jdoubleArray),并且返回JNI数据。    jdoubleArray outJNIArray = env->NewDoubleArray(2);  // allocate    if (NULL == outJNIArray) return NULL;    env->SetDoubleArrayRegion(outJNIArray, 0 , 2, outCArray);  // copy    return outJNIArray;}

执行结果:

com.dali.jnitest I/In java, the sum is: 88.0com.dali.jnitest I/In java, the average is:  29.333333333333332

JNI 基本数据类型数组相关函数

// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray// PrimitiveType: int, byte, short, long, float, double, char, boolean// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jbooleanNativeType * GetArrayElements(ArrayType array, jboolean *isCopy);void ReleaseArrayElements(ArrayType array, NativeType *elems, jint mode);void GetArrayRegion(ArrayType array, jsize start, jsize length, NativeType *buffer);void SetArrayRegion(ArrayType array, jsize start, jsize length, const NativeType *buffer);ArrayType NewArray(jsize length);void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy);void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode);

总结如下:

  • GET|Release<*PrimitiveType*>ArrayElements() 函数可用于根据java的jxxxArray创建C代码的本地jxxx[]数组。
  • GET | Set ArrayRegion()可用于将jxxxArray(或从长度开始的一部分)复制到预分配的C本地数组jxxx []
  • New Array()可用于分配给定大小的新jxxxArray。 然后,您可以使用Set ArrayRegion()函数从本机数组jxxx []中填充其内容。
  • Get | ReleasePrimitiveArrayCritical()函数不允许在getelease之间阻塞调用。

访问对象的变量和函数回调

访问对象的内部变量

示例

java层:

public class MainActivity extends AppCompatActivity {    // 在程序开始时使用静态代码块加载'native-lib'库    static {        System.loadLibrary("native-lib");    }    // 实例的内部变量    private int number = 88;    private String message = "帅呆了!";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        modifyInstanceVariable();        System.out.println("In Java, int is " + number);        System.out.println("In Java, String is " + message);    }    public native void modifyInstanceVariable();}

C层:

extern "C"JNIEXPORT void JNICALLJava_com_dali_jnitest_MainActivity_modifyInstanceVariable(JNIEnv *env, jobject instance) {    // 获得调用java层对象的引用    jclass thisClass = env->GetObjectClass(instance);    // int    // 获取调用java层对象中number变量的fieldID    jfieldID fidNumber = env->GetFieldID(thisClass, "number", "I");    if (NULL == fidNumber) return;    // 通过fieldID获取number变量中的值    jint number = env->GetIntField(instance, fidNumber);    LOGI("In C, the int is %d\n", number);    // 修改变量    number = 99;    env->SetIntField(instance, fidNumber, number);    // 获取调用java层对象中message变量的fieldID    jfieldID fidMessage = env->GetFieldID(thisClass, "message", "Ljava/lang/String;");    if (NULL == fidMessage) return;    // String    // 通过fieldID获取object变量中的值    jstring message = static_cast(env->GetObjectField(instance, fidMessage));    // 通过JNI字符串创建C代码字符串    const char *cStr = env->GetStringUTFChars(message, NULL);    if (NULL == cStr) return;    LOGI("In C, the string is %s\n", cStr);    env->ReleaseStringUTFChars(message, cStr);    //创建C代码字符串,并分配给JNI字符串    message = env->NewStringUTF("C:不你很蠢");    if (NULL == message) return;    // 修改实例变量    env->SetObjectField(instance, fidMessage, message);}

执行:

com.dali.jnitest I/In C/C++: In C, the int is 88com.dali.jnitest I/In C/C++: In C, the string is java:帅呆了!com.dali.jnitest I/In Java, int is: 99com.dali.jnitest I/In Java, String is: C:不你很蠢
访问实例变量的基本步骤
  1. 通过GetObjectClass()函数class对象的引用。
  2. 从类引用中通过GetFieldID()获取要访问的实例变量的字段ID。 参数中,需要提供变量名称及其字段描述符(或签名)。 对于Java类,字段描述符的形式为“L ”,点用正斜杠(/)替换,例如,String的类描述符是“Ljava / lang / String;”。 对于基本数据类型,使用“I”表示int,“B”表示字节,“S”表示short,“J”表示long,“F”表示float,“D”表示double,“C”表示char,以及“Z”表示 boolean。 对于数组,需要加上前缀“[”,例如“[Ljava / lang / Object;” 表示一个Object数组; “[I”代表一个int数组。
  3. 基于Field ID,通过GetObjectField()Get Field()函数检索实例变量。
  4. 根据提供字段ID,并通过SetObjectField()Set Field()函数,更新实例变量。

用于访问实例变量的JNI函数如下:

//返回java层调用者实例classjclass GetObjectClass(jobject obj);//返回访问者实例的field ID   jfieldID GetFieldID(jclass cls, const char *name, const char *sig);//Get/Set 实例中变量的值,// 包含8中基本数据类型,加上一个Object类型。NativeType GetField(jobject obj, jfieldID fieldID);void SetField(jobject obj, jfieldID fieldID, NativeType value);

访问对象的静态变量

访问静态变量类似于访问实例变量,只是调用函数改为GetStaticFieldID()Get | SetStaticObjectField()Get | SetStatic Field()之类的。

示例

java层:

public class MainActivity extends AppCompatActivity {    // 在程序开始时使用静态代码块加载'native-lib'库    static {        System.loadLibrary("native-lib");    }    // 静态变量    private static double number = 55.66;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        modifyStaticVariable();        Log.i("In Java, double is ", "" + number);    }    private native void modifyStaticVariable();}

C 层:

extern "C"JNIEXPORT void JNICALLJava_com_dali_jnitest_MainActivity_modifyStaticVariable(JNIEnv *env, jobject instance) {    jclass cls = env->GetObjectClass(instance);    jfieldID fieldID = env->GetStaticFieldID(cls, "number", "D");    if (fieldID == NULL) return;    jdouble number = env->GetStaticDoubleField(cls, fieldID);    LOGI("In C, the double is %f\n", number);    number = 77.88;    env->SetStaticDoubleField(cls, fieldID, number);}

执行:

com.dali.jnitest I/In C/C++: In C, the double is 55.660000com.dali.jnitest I/In Java, double is: 77.88

用于访问静态变量的JNI函数如下:

//返回类的静态变量的字段ID。jfieldID GetStaticFieldID(jclass cls, const char *name, const char *sig);//Get/Set 实例中静态变量的值,// 包含8中基本数据类型,加上一个Object类型。NativeType GetStaticField(jclass clazz, jfieldID fieldID);void SetStaticField(jclass clazz, jfieldID fieldID, NativeType value);

回调java函数和静态函数

示例

java层:

public class MainActivity extends AppCompatActivity {    // 在程序开始时使用静态代码块加载'native-lib'库    static {        System.loadLibrary("native-lib");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        nativeMethod();    }    // 申明本地函数    private native void nativeMethod();    // 用于测试C代码调用    private void callback() {        Log.i("In Java", "");    }    private void callback(String message) {        Log.i("In Java with ", message);    }    // 用于测试C代码调用    private double callbackAverage(int n1, int n2) {        return ((double)n1 + n2) / 2.0;    }    // Static method to be called back    private static String callbackStatic() {        return "From static Java method";    }}

C 层:

java类声明一个名为nativeMethod()的本地方法,并调用此nativeMethod()。 反过来,在C层nativeMetho()回调此类中定义的各种实例和静态方法。

extern "C"JNIEXPORT void JNICALLJava_com_dali_jnitest_MainActivity_nativeMethod(JNIEnv *env, jobject instance) {    // 获取java层调用的class对象的引用    jclass thisClass = env->GetObjectClass(instance);    // 获取Method ID 用于调用“callback”函数,该函数没有参数,无返回值    jmethodID midCallBack = env->GetMethodID(thisClass, "callback", "()V");    if (NULL == midCallBack) return;    LOGI("In C, call back Java's callback()\n");    // 根据Method ID 调用该函数(无返回值)    env->CallVoidMethod(instance, midCallBack);    jmethodID midCallBackStr = env->GetMethodID(thisClass, "callback", "(Ljava/lang/String;)V");    if (NULL == midCallBackStr) return;    LOGI("In C, call back Java's called(String)\n");    jstring message = env->NewStringUTF("Hello from C");    env->CallVoidMethod(instance, midCallBackStr, message);    jmethodID midCallBackAverage = env->GetMethodID(thisClass, "callbackAverage", "(II)D");    if (NULL == midCallBackAverage) return;    jdouble average = env->CallDoubleMethod(instance, midCallBackAverage, 2, 3);    LOGI("In C, the average is %f\n", average);    jmethodID midCallBackStatic = env->GetStaticMethodID(thisClass, "callbackStatic", "()Ljava/lang/String;");    if (NULL == midCallBackStatic) return;    jstring resultJNIStr = static_cast(env->CallStaticObjectMethod(thisClass, midCallBackStatic));    const char *resultCStr = env->GetStringUTFChars(resultJNIStr, NULL);    if (NULL == resultCStr) return;    LOGI("In C, the returned string is %s\n", resultCStr);    env->ReleaseStringUTFChars(resultJNIStr, resultCStr);}

执行代码:

com.dali.jnitest I/In C/C++: In C, call back Java's callback() In C, call back Java's  called(String)com.dali.jnitest I/In Java with: Hello from Ccom.dali.jnitest I/In C/C++: In C, the average is 2.500000 In C, the returned string is From static Java method
在本地代码中回调java对象中的函数的基本步骤
  1. 通过GetObjectClass()获取class对象的引用。

  2. 通过GetMethodID()以及class对象引用获取Method ID。该函数参数中,需要提供函数名和签名。签名的格式为: “(parameters)return-type” 。太复杂了么,别着急,你可以通过javap工具 (Class File Disassembler) 列出java代码中的函数签名,-s用于打印签名,-p用于显示私有成员(函数或变量),具体如下:

     private void callback();   Signature: ()V private void callback(java.lang.String);   Signature: (Ljava/lang/String;)V private double callbackAverage(int, int);   Signature: (II)D private static java.lang.String callbackStatic();   Signature: ()Ljava/lang/String;
  3. 基于Method ID, 你可以调用CallMethod() 或者 CallVoidMethod() 亦或 CallObjectMethod(), 这些函数分别返回的类型为: <*Primitive-type*>, void and Object。 在参数列表之前附加参数(如果有)。 对于非void返回类型,该方法返回一个值。

访问实例函数和实例的静态函数的JNI函数如下:

// 返回java层实例(类或接口)函数的method IDjmethodID GetMethodID(jclass cls, const char *name, const char *sig);// 调用类的method//  包含8种基本数据类型NativeType CallMethod(jobject obj, jmethodID methodID, ...);NativeType CallMethodA(jobject obj, jmethodID methodID, const jvalue *args);NativeType CallMethodV(jobject obj, jmethodID methodID, va_list args);// 返回Java层实例静态函数的method ID  jmethodID GetStaticMethodID(jclass cls, const char *name, const char *sig);// 调用对象的函数//  包含8种基本数据类型NativeType CallStaticMethod(jclass clazz, jmethodID methodID, ...);NativeType CallStaticMethodA(jclass clazz, jmethodID methodID, const jvalue *args);NativeType CallStaticMethodV(jclass clazz, jmethodID methodID, va_list args);

访问父类的重载函数

JNI提供了一组CallNonvirtual Method()函数来调用已在此类中重写的超类’实例方法(类似于Java子类中的super。* methodName *()调用):

  1. 通过GetMethodID()函数获取Method ID。
  2. 基于Method ID,使用object, superclass, 和arguments 访问CallNonvirtualMethod()函数。

访问超累的JNI函数如下:

NativeType CallNonvirtual<type>Method(jobject obj, jclass cls, jmethodID methodID, ...);NativeType CallNonvirtual<type>MethodA(jobject obj, jclass cls, jmethodID methodID, const jvalue *args);NativeType CallNonvirtual<type>MethodV(jobject obj, jclass cls, jmethodID methodID, va_list args);

创建对象和对象数组

创建对象

可以通过NewObject()newObjectArray()函数在本机代码中构造jobjectjobjectArray,并将它们传递回Java程序。

示例

java代码:

public class MainActivity extends AppCompatActivity {    // 在程序开始时使用静态代码块加载'native-lib'库    static {        System.loadLibrary("native-lib");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Log.i("In Java,the number is :", "" + getIntegerObject(9999));    }    // 本地函数,调用之后返回创建完毕的java对象实例    // 使用给定的int返回Integer对象    private native Integer getIntegerObject(int number);

C 代码:

extern "C"JNIEXPORT jobject JNICALLJava_com_dali_jnitest_MainActivity_getIntegerObject(JNIEnv *env, jobject instance, jint number) {    // 获取java.lang.Integer类的引用    jclass cls = env->FindClass("java/lang/Integer");    // 获取Integer的构造函数Method ID,使用int参数    jmethodID midInit = env->GetMethodID(cls, "", "(I)V");    if (NULL == midInit) return NULL;    // 使用jint参数调用构造函数,分配新的实例    jobject newObj = env->NewObject(cls, midInit, number);    // 调用String的toString()方法调用打印    jmethodID midToString = env->GetMethodID(cls, "toString", "()Ljava/lang/String;");    if (NULL == midToString) return NULL;    jstring resultStr = static_cast(env->CallObjectMethod(newObj, midToString));    const char *resultCStr = env->GetStringUTFChars(resultStr, NULL);    LOGI("In C: the number is %s\n", resultCStr);    return newObj;}

代码执行:

com.dali.jnitest I/In C/C++: In C: the number is 9999com.dali.jnitest I/In Java,the number is :: 9999用于创建对象(jobject)的JNI函数是:

用于创建对象(jobject)的JNI函数是:

jclass FindClass(JNIEnv *env, const char *name);// 构造一个新的Java对象。 method ID指定要调用的构造方法 jobject NewObject(JNIEnv *env, jclass cls, jmethodID methodID, ...);jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);jobject NewObjectV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);// 在不调用对象的任何构造函数的情况下分配新的Java对象。 jobject AllocObject(JNIEnv *env, jclass cls);

对象数组

示例

java层:

public class MainActivity extends AppCompatActivity {    // 在程序开始时使用静态代码块加载'native-lib'库    static {        System.loadLibrary("native-lib");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Integer[] numbers = {11, 22, 32};  // auto-box        Double[] results = sumAndAverage2(numbers); // auto-unbox        Log.i("In Java,the sum is ","" + results[0]);        Log.i("In Java,the average is ","" + results[1]);    }    // 本地函数:接收一个Integer[], 返回Double[2],[0]为和,[1]为平均值    private native Double[] sumAndAverage2(Integer[] numbers);

C层:

extern "C"JNIEXPORT jobjectArray JNICALLJava_com_dali_jnitest_MainActivity_sumAndAverage2(JNIEnv *env,jobject instance,jobjectArray numbers) {    // 获取java.lang.Integer类的引用    jclass classInteger = env->FindClass("java/lang/Integer");    // 使用Integer.intValue() 函数    jmethodID midIntValue = env->GetMethodID(classInteger, "intValue", "()I");    if (NULL == midIntValue) return NULL;    // 获取数据中每个元素Get the value of each Integer object in the array    jsize length = env->GetArrayLength(numbers);    jint sum = 0;    int i;    for (i = 0; i < length; i++) {        jobject objInteger = env->GetObjectArrayElement(numbers, i);        if (NULL == objInteger) return NULL;        jint value = env->CallIntMethod(objInteger, midIntValue);        sum += value;    }    double average = (double)sum / length;    LOGI("In C, the sum is %d\n", sum);    LOGI("In C, the average is %f\n", average);    // 获取java.lang.Double类的引用    jclass classDouble = env->FindClass("java/lang/Double");    // 创建一个长度为2的java.lang.Double类型数组()jobjectArray    jobjectArray outJNIArray = env->NewObjectArray(2, classDouble, NULL);    // 通过构造函数构造两个Double类型对象    jmethodID midDoubleInit = env->GetMethodID(classDouble, "", "(D)V");    if (NULL == midDoubleInit) return NULL;    jobject objSum = env->NewObject(classDouble, midDoubleInit, (double)sum);    jobject objAve = env->NewObject(classDouble, midDoubleInit, average);    // 初始化jobjectArray    env->SetObjectArrayElement(outJNIArray, 0, objSum);    env->SetObjectArrayElement(outJNIArray, 1, objAve);    return outJNIArray;}

代码执行:

com.dali.jnitest I/In C/C++: In C, the sum is 65                             In C, the average is 21.666667com.dali.jnitest I/In Java,the sum is: 65.0com.dali.jnitest I/In Java,the average is: 21.666666666666668

与可以批量处理的原始数组不同,对于对象数组,你需要使用Get | SetObjectArrayElement()来处理每个元素。

用于创建和操作对象数组(jobjectArray)的JNI函数如下:

// 构造一个包含elementClass对象的数组//所有元素都设置为initialElement。jobjectArray NewObjectArray(jsize length, jclass elementClass, jobject initialElement);// 返回一个对象数组元素 jobject GetObjectArrayElement(jobjectArray array, jsize index);// 设置一个对象数组元素 void SetObjectArrayElement(jobjectArray array, jsize index, jobject value);

本地和全局引用

管理引用对于编写高效的程序至关重要。 例如,我们经常使用FindClass()GetMethodID()GetFieldID()来检索本机函数中的jclassjmethodIDjfieldID。 应该获取一次并缓存以供后续使用的值,而不是执行重复调用,以减少开销。

JNI将C代码使用的对象引用(对于jobject)分为两类:本地引用和全局引用:

  1. 在C函数中创建本地引用,并在方法退出后释放。 它在本地方法的持续时间内有效。 你还可以使用JNI函数DeleteLocalRef()显式地使本地引用无效,以便可以在中间进行垃圾回收。 对象作为本地引用传递给本地方法。 JNI函数返回的所有Java对象(jobject)都是本地引用。
  2. 在程序员通过DeleteGlobalRef()JNI函数显式释放它之前,全局引用仍然存在。 您可以通过JNI函数NewGlobalRef()从本地引用创建新的全局引用。
示例:

java代码:

public class MainActivity extends AppCompatActivity {    // 在程序开始时使用静态代码块加载'native-lib'库    static {        System.loadLibrary("native-lib");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Log.i("java:", ""+getIntegerObject2(1));        Log.i("java:", ""+getIntegerObject2(2));        Log.i("java:", ""+anotherGetIntegerObject(11));        Log.i("java:", ""+anotherGetIntegerObject(12));        Log.i("java:", ""+getIntegerObject2(3));        Log.i("java:", ""+anotherGetIntegerObject(13));    }    // 返回给定int值的java.lang.Integer的本地方法。    private native Integer getIntegerObject2(int number);    // 另一个本地方法也返回带有给定int值的java.lang.Integer。    private native Integer anotherGetIntegerObject(int number);}

C代码:

// java类"java.lang.Integer"的全局引用static jclass classInteger;static jmethodID midIntegerInit;jobject getInteger(JNIEnv *env, jobject instance, jint number) {    // 如果classInteger为NULL,获取java.lang.Integer的类引用    if (NULL == classInteger) {        LOGI("Find java.lang.Integer\n");        classInteger = env->FindClass("java/lang/Integer");        // 不加下面这行会出现报错:jni error (app bug): accessed stale local reference        classInteger = static_cast(env->NewGlobalRef(classInteger));    }    if (NULL == classInteger) return NULL;    // 如果midIntegerInit为空,获取Integer构造函数的method ID    if (NULL == midIntegerInit) {        LOGI("Get Method ID for java.lang.Integer's constructor\n");        midIntegerInit = env->GetMethodID(classInteger, "", "(I)V");    }    if (NULL == midIntegerInit) return NULL;    // 调用构造函数以使用int参数分配新实例    jobject newObj = env->NewObject(classInteger, midIntegerInit, number);    LOGI("In C, constructed java.lang.Integer with number %d\n", number);    return newObj;}extern "C"JNIEXPORT jobject JNICALLJava_com_dali_jnitest_MainActivity_getIntegerObject2(JNIEnv *env, jobject instance, jint number) {    return getInteger(env, instance, number);}extern "C"JNIEXPORT jobject JNICALLJava_com_dali_jnitest_MainActivity_anotherGetIntegerObject(JNIEnv *env, jobject instance,jint number) {    return getInteger(env, instance, number);}

在上面的程序中,我们使用classInteger = static_cast(env->NewGlobalRef(classInteger));classInteger变为全局引用,记得要使用env->DeleteLocalRef(classInteger);取消引用。

请注意,jmethodID和jfieldID不是jobject,并且无法创建全局引用。

Debug JNI 程序

TODO:

更多相关文章

  1. 箭头函数的基础使用
  2. Python技巧匿名函数、回调函数和高阶函数
  3. 浅析android通过jni控制service服务程序的简易流程
  4. Android(安卓)bluetooth介绍(四): a2dp connect流程分析
  5. Android中文API(144) —— JsonWriter
  6. Android架构分析之使用自定义硬件抽象层(HAL)模块
  7. Android中OpenMax的适配层
  8. android 包管理系统分析
  9. Android中获取屏幕相关信息(屏幕大小,状态栏、标题栏高度)

随机推荐

  1. Android音频可视化
  2. Android布局属性详解
  3. Android(安卓)圆形背景shape定义
  4. [Android]Using Text-to-Speech
  5. Android中通过NTP服务器获取时间功能源码
  6. [转]android layout布局属性
  7. Android安装
  8. Android设置无标题
  9. Android(安卓)之 对话框总结
  10. android 定时拍照并发送微博