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");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// Example of a call to a native methodTextView tv = (TextView) findViewById(R.id.sample_text);tv.setText(stringFromJNI());//本地方法调用和java函数调用毫无二致}/*** 本地'native-lib'库中实现了的本地方法,共享库会打包到本应用中* 区别于普通java函数,在函数申明中多了个native字段,以及没有函数体*/public native String stringFromJNI();

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

#include <jni.h>
#include <iostream>
#include <string>
#include <android/log.h>
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "In C/C++:", __VA_ARGS__);
using namespace std;extern "C"
JNIEXPORT jstring JNICALL
Java_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");}@Overrideprotected 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 JNICALL
Java_com_dali_jnitest_MainActivity_average(JNIEnv *env, jobject instance, jint arg1,jint arg2) {jdouble result;//基本数据类型无需变化,在jni.h中已经设置了类型别名/***想要使用该打印,请在C文件头增加下列代码:*include <android/log.h>*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: 4
com.dali.jnitest I/In java: 3.0

字符串传递

示例

java层:

public class MainActivity extends AppCompatActivity {// 在程序开始时使用静态代码块加载'native-lib'库static {System.loadLibrary("native-lib");}@Overrideprotected 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 JNICALL
Java_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: hello
com.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");}@Overrideprotected 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 JNICALL
Java_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 resourcesjdouble outCArray[] = {(jdouble)sum, average};// 将本地C代码的数据(例如,jdouble[])转换为JNI数组(例如,jdoubleArray),并且返回JNI数据。jdoubleArray outJNIArray = env->NewDoubleArray(2);  // allocateif (NULL == outJNIArray) return NULL;env->SetDoubleArrayRegion(outJNIArray, 0 , 2, outCArray);  // copyreturn outJNIArray;
}

执行结果:

com.dali.jnitest I/In java, the sum is: 88.0
com.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, jboolean
NativeType * Get<PrimitiveType>ArrayElements(ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(ArrayType array, NativeType *elems, jint mode);
void Get<PrimitiveType>ArrayRegion(ArrayType array, jsize start, jsize length, NativeType *buffer);
void Set<PrimitiveType>ArrayRegion(ArrayType array, jsize start, jsize length, const NativeType *buffer);
ArrayType New<PrimitiveType>Array(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 <PrimitiveType> ArrayRegion()可用于将jxxxArray(或从长度开始的一部分)复制到预分配的C本地数组jxxx []
  • New <PrimitiveType> Array()可用于分配给定大小的新jxxxArray。 然后,您可以使用Set <PrimitiveType> 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 = "帅呆了!";@Overrideprotected 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 JNICALL
Java_com_dali_jnitest_MainActivity_modifyInstanceVariable(JNIEnv *env, jobject instance) {// 获得调用java层对象的引用jclass thisClass = env->GetObjectClass(instance);// int// 获取调用java层对象中number变量的fieldIDjfieldID 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变量的fieldIDjfieldID fidMessage = env->GetFieldID(thisClass, "message", "Ljava/lang/String;");if (NULL == fidMessage) return;// String// 通过fieldID获取object变量中的值jstring message = static_cast<jstring>(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 88
com.dali.jnitest I/In C/C++: In C, the string is java:帅呆了!
com.dali.jnitest I/In Java, int is: 99
com.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 <primitive-type> Field()函数检索实例变量。
  4. 根据提供字段ID,并通过SetObjectField()Set <primitive-type> Field()函数,更新实例变量。

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

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

访问对象的静态变量

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

示例

java层:

public class MainActivity extends AppCompatActivity {// 在程序开始时使用静态代码块加载'native-lib'库static {System.loadLibrary("native-lib");}// 静态变量private static double number = 55.66;@Overrideprotected 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 JNICALL
Java_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.660000
com.dali.jnitest I/In Java, double is: 77.88

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

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

回调java函数和静态函数

示例

java层:

public class MainActivity extends AppCompatActivity {// 在程序开始时使用静态代码块加载'native-lib'库static {System.loadLibrary("native-lib");}@Overrideprotected 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 backprivate static String callbackStatic() {return "From static Java method";}
}

C 层:

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

extern "C"
JNIEXPORT void JNICALL
Java_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<jstring>(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 C
com.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: ()Vprivate void callback(java.lang.String);Signature: (Ljava/lang/String;)Vprivate double callbackAverage(int, int);Signature: (II)Dprivate static java.lang.String callbackStatic();Signature: ()Ljava/lang/String;
  3. 基于Method ID, 你可以调用Call<Primitive-type>Method() 或者 CallVoidMethod() 亦或 CallObjectMethod(), 这些函数分别返回的类型为: <*Primitive-type*>, void and Object。 在参数列表之前附加参数(如果有)。 对于非void返回类型,该方法返回一个值。

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

// 返回java层实例(类或接口)函数的method ID
jmethodID GetMethodID(jclass cls, const char *name, const char *sig);
// 调用类的method
// <type> 包含8种基本数据类型
NativeType Call<type>Method(jobject obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(jobject obj, jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(jobject obj, jmethodID methodID, va_list args);
// 返回Java层实例静态函数的method ID
jmethodID GetStaticMethodID(jclass cls, const char *name, const char *sig);
// 调用对象的函数
// <type> 包含8种基本数据类型
NativeType CallStatic<type>Method(jclass clazz, jmethodID methodID, ...);
NativeType CallStatic<type>MethodA(jclass clazz, jmethodID methodID, const jvalue *args);
NativeType CallStatic<type>MethodV(jclass clazz, jmethodID methodID, va_list args);

访问父类的重载函数

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

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

访问超累的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");}@Overrideprotected 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 JNICALL
Java_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, "<init>", "(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<jstring>(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 9999
com.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");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Integer[] numbers = {11, 22, 32};  // auto-boxDouble[] results = sumAndAverage2(numbers); // auto-unboxLog.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 JNICALL
Java_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 arrayjsize 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类型数组()jobjectArrayjobjectArray outJNIArray = env->NewObjectArray(2, classDouble, NULL);// 通过构造函数构造两个Double类型对象jmethodID midDoubleInit = env->GetMethodID(classDouble, "<init>", "(D)V");if (NULL == midDoubleInit) return NULL;jobject objSum = env->NewObject(classDouble, midDoubleInit, (double)sum);jobject objAve = env->NewObject(classDouble, midDoubleInit, average);// 初始化jobjectArrayenv->SetObjectArrayElement(outJNIArray, 0, objSum);env->SetObjectArrayElement(outJNIArray, 1, objAve);return outJNIArray;
}

代码执行:

com.dali.jnitest I/In C/C++: In C, the sum is 65In C, the average is 21.666667
com.dali.jnitest I/In Java,the sum is: 65.0
com.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");}@Overrideprotected 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 referenceclassInteger = static_cast<jclass>(env->NewGlobalRef(classInteger));}if (NULL == classInteger) return NULL;// 如果midIntegerInit为空,获取Integer构造函数的method IDif (NULL == midIntegerInit) {LOGI("Get Method ID for java.lang.Integer's constructor\n");midIntegerInit = env->GetMethodID(classInteger, "<init>", "(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 JNICALL
Java_com_dali_jnitest_MainActivity_getIntegerObject2(JNIEnv *env, jobject instance, jint number) {return getInteger(env, instance, number);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_dali_jnitest_MainActivity_anotherGetIntegerObject(JNIEnv *env, jobject instance,jint number) {return getInteger(env, instance, number);
}

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

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

Debug JNI 程序

TODO:

Android JNI 详解相关推荐

  1. Android 动画详解-思维导图版

    转载请注明链接: https://blog.csdn.net/feather_wch/article/details/81503233 涉及视图动画(补间动画.逐帧动画).属性动画三种动画的使用方法, ...

  2. Android.mk详解

    转载请标明出处:http://blog.csdn.net/xx326664162/article/details/52875825 文章出自:薛瑄的博客 你也可以查看我的其他同类文章,也会让你有一定的 ...

  3. Android Linker详解

    Android Linker详解 本文目的 Linker入口 So的装载 总结 本文目的 Unidbg在对So进行模拟执行的时候,需要先将So文件加载到内存,配置So的进程映像,然后使用CPU模拟器( ...

  4. 【转】Android菜单详解——理解android中的Menu--不错

    原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...

  5. Android菜单详解——理解android中的Menu

    前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...

  6. Android LayoutInflater详解

    Android LayoutInflater详解 在实际开发中LayoutInflater这个类还是非常有用的,它的作用类 似于findViewById().不同点是LayoutInflater是用来 ...

  7. android Fragments详解

    android Fragments详解一:概述 android Fragments详解二:创建Fragment 转载于:https://my.oschina.net/liangzhenghui/blo ...

  8. android WebView详解,常见漏洞详解和安全源码(下)

    上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑.  上篇:android WebView详解,常见漏洞详解和安全源码(上)  转载请注明出处:http ...

  9. android WebView详解,常见漏洞详解和安全源码(上)

    这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析.  由于博客内容长度,这次将分为上下两篇,上篇详解 WebView ...

最新文章

  1. WayOS路由智能小区组网方案
  2. python 路径双斜杠问题
  3. 【简洁代码】1061 判断题 (15分)_18行代码AC
  4. 九度OJ 1054:字符串内排序
  5. 修改Fiddler实用插件JsonViewer
  6. windows下检測文件改变
  7. Java项目导出为jar包+导出第三方jar包+使用命令行调用+传参
  8. php变量自减,PHP 中「自增、自减」运算引发的奇怪问题
  9. 如何成为优秀的技术人员
  10. WebSocket 消息推送
  11. 7个必收藏的免商用无版权的高清图片网站
  12. java过滤关键字,敏感词汇
  13. 什么是固态功率放大器?射频固态功率放大器?
  14. Linux 命令详解之df命令
  15. 解线性方程组之LU分解算法实现
  16. js验证银行卡号,并自动识别银行信息。js验证手机码。js验证数字
  17. 2018年9月26日公司断网情况处理(交换机环路,err-disable state)
  18. ibm存储管理软件 linux,IBM storwize V5000存储基础配置
  19. 高级人工智能 读书笔记
  20. Linux nmon监控、分析系统性能使用

热门文章

  1. 复旦大学EMBA2022毕业季丨毕业不忘初心 荣耀再上征程
  2. oracle sum包含空值,实验 oracle sum null值相关
  3. React Native 启动速度优化——Native 篇(内含源码分析)
  4. 二十一世纪大学英语读写教程(第二册)学习笔记(原文)——3 - The Tale of a Cultural Translator(一个文化译员的传奇)
  5. 一个高性能、轻量级的分布式内存队列系统--beanstalk
  6. javascript中document.getelementbyid缺少对象的问题原因
  7. 电暖炉和节能环保意识
  8. python wmi安装_Python wmi 模块的学习
  9. 使用APP做简单的噪音测试
  10. FTIR测试常见的问题及解答(一)