项目代码:https://github.com/VincentWei95/ndk
Android JNI开发系列:第一章 JNIEnv接口指针
Android JNI开发系列:第三章 对引用数据类型的操作
Android JNI开发系列:第八章 POSIX Socket API 面向连接的通信
Android JNI开发系列:第九章 POSIX Socket API 无连接的通信
Android JNI开发系列:第十章 POSIX Socket API 本地通信
文章目录
以下介绍的demo操作只列出相关头文件和源文件代码,具体AS配置JNI步骤操作请到该链接查看:
https://blog.csdn.net/qq_31339141/article/details/90314170
引用类型以不透明的引用方式传递给原生代码,而不是以原生数据类型的形式呈现(就是在java中的native方法传递对象实例参数与jni中的函数参数不一样,是经过转换的),因此引用类型不能直接使用和修改。
JNI提供了一系列引用数据类型操作的API:
-
字符串
-
数组
-
NIO缓冲区
-
字段
-
方法
1 字符串操作
JNI提供了java字符串与C字符串之间相互转换的必要函数。因为java字符串对象是不可变的,因此JNI不提供任何修改现有java字符串内容的函数。
JNI支持Unicode编码格式和UTF-8编码格式的字符串。
JNI函数的调用如果发生内存溢出的情况,将会返回NULL以通知原生代码虚拟机抛出异常,这样代码就会停止运行。
1.1 创建字符串
jstring javaString;
// 创建Unicode编码格式的字符串
javaString = (*env)->NewString(env, "Hello world");
// 创建UTF-8编码格式的字符串
javaString = (*env)->NewStringUTF(env, "Hello world!");
1.2 java字符串转换成C字符串
const jbyte* str;
jboolean isCopy;
// 将Unicode编码格式的java字符串转换为C字符串
// 第三个参数可选,表示让调用者确定返回的C字符串地址指向副本还是指向堆中的固定对象(也就是将java字符串复制了一份,让C字符串地址指向复制的副本)
str = (*env)->GetStringChars(env, javaString, &isCopy);
// 将UTF-8编码格式的java字符串转换为C字符串
str = (*env)->GetStringUTFChars(env, javaString, &isCopy);
if (str != 0) {
printf("Java string:%s", str);
if (isCopy == JNI_TRUE) {
print("C string is a copy of the java string");
} else {
printf("C string points to actual string");
}
}
// 释放字符串,防止内存泄漏
(*env)->ReleaseStringChars(env, javaString, str);
(*env)->ReleaseStringUTFChars(env, javaString, str);
1.3 字符串操作demo
- com_example_ndk_JNITest.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndk_JNITest */
#ifndef _Included_com_example_ndk_JNITest
#define _Included_com_example_ndk_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_ndk_JNITest
* Method: getStringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_ndk_JNITest_getStringFromJNI
(JNIEnv *, jobject);
/*
* Class: com_example_ndk_JNITest
* Method: javaStringToCString
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaStringToCString
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
- com_example_ndk_JNITest.c
#include "/include/com_example_ndk_JNITest.h"
#include <jni.h>
#include <stdio.h>
#include <syslog.h>
/*
* Class: com_example_ndk_JNITest
* Method: getStringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_ndk_JNITest_getStringFromJNI
(JNIEnv *env, jobject object) {
return (*env)->NewStringUTF(env, "hello from jni!");
}
/*
* Class: com_example_ndk_JNITest
* Method: javaStringToCString
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaStringToCString
(JNIEnv *env, jobject object, jstring javaString) {
jboolean isCopy;
const char *str = (*env)->GetStringUTFChars(env, javaString, &isCopy);
if (str != 0) {
syslog(LOG_INFO, "java string:%s", str);
if (isCopy == JNI_TRUE) {
syslog(LOG_INFO, "C string is a copy of the java string"); // 返回该结果
} else {
syslog(LOG_INFO, "C string points to actual string");
}
(*env)->ReleaseStringUTFChars(env, javaString, str);
}
}
- JNITest.java
package com.example.ndk;
public class JNITest {
static {
System.loadLibrary("ndk");
}
public native String getStringFromJNI();
public native void javaStringToCString(String javaString);
}
- JniActivity.java
package com.example.ndk;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JNITest jniTest = new JNITest();
TextView tvNdk = findViewById(R.id.tv_ndk);
tvNdk.append("1.getStringFromJNI() = " + jniTest.getStringFromJNI());
tvNdk.append("\n");
jniTest.javaStringToCString("this is a java string");
}
}
2 数组操作
2.1 创建数组
通过 NewxxxArray
函数在原生代码中创建数组实例,xxx
表示具体的数组类型,比如Int为NewIntArray创建int类型的数组。
jintArray javaArray;
javaArray = (*env)->NewIntArray(env, 10);
if (javaArray != 0) {
// 处理数组
}
2.2 访问数组元素
JNI提供两种访问java数组元素的方法,可以将数组的代码复制成C数组,或者让JNI提供直接指向数组元素的指针。
2.3 对副本操作
对数组进行操作时需要注意,当数组很大时,如果可能的话,原生代码应该只获取或设置数组元素区域而不是获取整个数组(即复制java数组的部分元素或者复制C数组的部分元素),提高操作数组时的性能。
2.3.1 将java数组复制到C数组中
通过 GetxxxArrayRegion
函数将java数组复制到C数组中。
jintArray javaArray; // java层传递到JNI的数组
jint nativeArray[10]; // 复制后的元素存储数组
(*env)->GetIntArrayRegion(env, javaArray, 0, 10, nativeArray);
2.3.2 将C数组复制给java数组
通过 SetxxxArrayRegion
函数将C数组复制给java数组中。
jintArray javaArray[10] = (*env)->NewIntArray(env, 10);
jint elements[10]; // 存放数组元素的数组
(*env)->SetIntArrayRegion(env, javaArray, 0, 10, elements);
2.4 对直接指针操作(操作数组元素)
通过 GetxxxArrayElements
函数获取指向java数组元素的直接指针。
通过 ReleasexxxArrayElements
函数释放元素指针。
jint* nativeDirectArray;
jboolean isCopy;
// 第三个参数可选,表示让调用者确定返回的C字符串地址指向副本地址还是指向堆中的固定对象
nativeDirectArray = (*env)->GetIntArrayElements(env, javaArray, &isCopy);
// 释放指向java数组元素的直接指针,防止内存泄漏
// 第四个参数是释放的模式:
// 0:将内容复制回来并释放原生数组
// JNI_COMMIT:将内容复制回来但是不释放原生数组,一般用于周期性地更新一个java数组
// JNI_ABORT:释放原生数组但不用将内容复制回来
(*env)->ReleaseIntArrayElements(env, javaArray, nativeDirectArray, 0);
2.5 数组操作demo
- com_example_ndk_JNITest.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndk_JNITest */
#ifndef _Included_com_example_ndk_JNITest
#define _Included_com_example_ndk_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_ndk_JNITest
* Method: javaArrayCopyToCArray
* Signature: ([I)V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaArrayCopyToCArray
(JNIEnv *, jobject, jintArray);
/*
* Class: com_example_ndk_JNITest
* Method: cArrayCopyToJavaArray
* Signature: ()[I
*/
JNIEXPORT jintArray JNICALL Java_com_example_ndk_JNITest_cArrayCopyToJavaArray
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
- com_example_ndk_JNITest.c
#include "/include/com_example_ndk_JNITest.h"
#include <jni.h>
#include <stdio.h>
#include <syslog.h>
/*
* Class: com_example_ndk_JNITest
* Method: javaArrayCopyToCArray
* Signature: ([I)V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaArrayCopyToCArray
(JNIEnv *env, jobject object, jintArray javaArray) {
// 获取数组长度
jint arrayLength = (*env)->GetArrayLength(env, javaArray);
syslog(LOG_INFO, "java array length is %d", arrayLength);
// 将java数组复制给native数组
jint nativeArray[arrayLength];
(*env)->GetIntArrayRegion(env, javaArray, 0, arrayLength, nativeArray);
for (jint i = 0; i < arrayLength; i++) {
syslog(LOG_INFO, "after copy show native element %d", nativeArray[i]); // 打印复制后的数组元素
}
}
/*
* Class: com_example_ndk_JNITest
* Method: cArrayCopyToJavaArray
* Signature: ()[I
*/
JNIEXPORT jintArray JNICALL Java_com_example_ndk_JNITest_cArrayCopyToJavaArray
(JNIEnv *env, jobject object) {
// 创建一个返回给java的数组
jintArray javaArray = (*env)->NewIntArray(env, 10);
jint elements[10];
for (jint i = 0; i < 10; i++) {
elements[i] = i * 2;
}
// 将元素数组设置给java数组返回
(*env)->SetIntArrayRegion(env, javaArray, 0, 10, elements);
return javaArray;
}
- JNITest.java
package com.example.ndk;
public class JNITest {
static {
System.loadLibrary("ndk");
}
public native void javaArrayCopyToCArray(int[] javaArray);
public native int[] cArrayCopyToJavaArray();
}
- JniActivity.java
package com.example.ndk;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JNITest jniTest = new JNITest();
TextView tvNdk = findViewById(R.id.tv_ndk);
tvNdk.append("2.javaArrayCopyToCArray()");
tvNdk.append("\n");
int[] array = new int[10];
for (int i = 0; i < 10; i++) {
array[i] = i * 2;
}
jniTest.javaArrayCopyToCArray(array);
int[] javaArray = jniTest.cArrayCopyToJavaArray();
if (javaArray != null) {
tvNdk.append("3.cArrayCopyToJavaArray()");
tvNdk.append("\n");
for (int i = 0; i < javaArray.length; i++) {
tvNdk.append("element[" + i + "] = " + javaArray[i]);
tvNdk.append("\n");
}
}
}
}
3 NIO操作
与数组操作相比,NIO缓冲区的数据传荣性能更好,更适合在原生代码和java应用程序之间传送大量数据。
3.1 创建直接字节缓冲区
unsigned char* buffer = (unsigned char*) malloc(1024);
...
jobject directBuffer;
directBuffer = (*env)->NewDirectByteBuffer(env, buffer, 1024);
3.2 获取直接字节缓冲区
unsigned char* buffer;
buffer = (unsigned char*) (*env)->GetDirectBufferAddress(env, directBuffer);
4 JNI访问java的成员变量和方法总结
JNI无论访问成员变量还是方法,都需要经过三个步骤:
-
获取实例clazz,通过JNI方法中的第二个函数参数jobject
-
获取id
-
获取java对象的成员变量和方法
5 JNI访问java对象的成员变量
java有两类域:实例域和静态域。类的每个实例都有自己的实例域副本,而一个类的所有实例共享同一个静态域。
public class JavaClass {
// 实例域
private String instanceField = "Instance Field";
// 静态域
private static String staticField = "Static Field";
}
JNI中获取java对象的成员变量经过三个步骤:
-
通过JNI函数中第二个参数jobject对象引用获取实例对象clazz
-
根据clazz获取实例对象的成员变量id或静态成员变量id
-
根据clazz和id获取实例对象的成员变量或静态成员变量
注意:每次从JNI调用java都需要经过两到三个函数,这会导致性能上的下降。强烈建议将所有需要的参数传递给native方法,而不是通过JNI调回java。
5.1 通过对象引用获取类
// jobject对象引用是JNI函数第二个参数jobject获取
jclass clazz;
clazz = (*env)->GetObjectClass(env, jobject);
5.2 获取java对象的成员变量id
// Ljava/lang/String:成员变量的具体类型
jfieldID instanceFieldId;
instanceFiledId = (*env)->GetFieldID(env, clazz, "instanceField", "Ljava/lang/String;"); // 分号不能少
5.3 获取java对象的静态成员变量id
jfieldID staticFieldId;
staticFieldId = (*env)->GetStaticFieldID(env, clazz, "staticFieldId", "Ljava/lang/String;"); // 分号不能少
5.4 获取java对象的成员变量
通过 GetxxxField
函数获取成员变量,比如GetIntField表示获取java对象中指定成员变量id的int类型的成员变量。
jstring instanceField;
instanceField = (*env)->GetObjectField(env, jobject, instanceFieldId);
通过 GetStaticxxxField
函数获取静态成员变量。
jstring staticField;
staticField = (*env)->GetStaticObjectField(env, clazz, staticFieldId); // 注意第二个参数是clazz,不是jobject
5.5 获取成员变量demo
- com_example_ndk_JNITest.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndk_JNITest */
#ifndef _Included_com_example_ndk_JNITest
#define _Included_com_example_ndk_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_ndk_JNITest
* Method: javaField
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaField
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
- com_example_ndk_JNITest.c
#include "/include/com_example_ndk_JNITest.h"
#include <jni.h>
#include <stdio.h>
#include <syslog.h>
/*
* Class: com_example_ndk_JNITest
* Method: javaField
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaField
(JNIEnv *env, jobject object) {
// 获取java class
jclass clazz = (*env)->GetObjectClass(env, object);
// 获取class中实例域的id
jfieldID instanceFieldId = (*env)->GetFieldID(env, clazz, "mInstanceField", "Ljava/lang/String;");
// 获取class中实例域成员变量
jstring instanceField = (*env)->GetObjectField(env, object, instanceFieldId);
// 上面的是java字符串,要转换为native的字符串才可以使用,否则会闪退
const char *instanceFieldStr = (*env)->GetStringUTFChars(env, instanceField, NULL);
syslog(LOG_INFO, "java instance field value %s", instanceFieldStr);
(*env)->ReleaseStringUTFChars(env, instanceField, instanceFieldStr);
// 获取class中静态域的id
jfieldID staticFieldId = (*env)->GetStaticFieldID(env, clazz, "sStaticField", "Ljava/lang/String;");
// 获取class中静态域成员变量
jstring staticField = (*env)->GetStaticObjectField(env, clazz, staticFieldId);
const char *staticFieldStr = (*env)->GetStringUTFChars(env, staticField, NULL);
syslog(LOG_INFO, "java static field value %s", staticFieldStr);
(*env)->ReleaseStringUTFChars(env, staticField, staticFieldStr);
}
- JNITest.java
package com.example.ndk;
public class JNITest {
// 实例域
private String mInstanceField = "instanceField";
// 静态域
private static String sStaticField = "staticField";
static {
System.loadLibrary("ndk");
}
public native void javaField();
}
- JniActivity.java
package com.example.ndk;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JNITest jniTest = new JNITest();
jniTest.javaField();
}
}
6 JNI调用java对象中的方法
java中有两类方法:实例方法和静态方法。
public class JavaClass {
// 实例方法
private String instanceMethod() {
return "Instance Method";
}
// 静态方法
private static String staticMethod() {
return "Static Method";
}
}
JNI中调用java对象方法经过三个步骤:
-
通过JNI函数中第二个参数jobject对象引用获取实例对象
-
根据clazz获取实例对象的方法id或静态方法id
-
根据clazz和id调用实例对象的方法或静态方法
6.1 获取实例方法的id
// ()Ljava/lang/String:表示方法签名
jmethodID instanceMethodId;
instanceMethodId = (*env)->GetMethodID(env, clazz, "instanceMethod", "()Ljava/lang/String;");
6.2 获取静态方法的id
jmethodID staticMethodId;
staticMethodId = (*env)->GetStaticMethodID(env, clazz, "staticMethod", "()Ljava/lang/String;");
6.3 调用实例方法
通过 CallxxxMethod
函数调用实例的方法。
jstring instanceMethodResut;
instanceMethodResult = (*env)->CallStringMethod(env, jobject, instanceMethodId); // 注意这里是jobject
6.4 调用静态方法
通过 CallStaticxxxMethod
函数调用静态方法。
jstring staticMethodResult;
staticMethodResult = (*env)->CallStaticStringMethod(env, clazz, staticMethodId); // 注意这里是clazz
6.5 调用方法demo
- com_example_ndk_JNITest.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndk_JNITest */
#ifndef _Included_com_example_ndk_JNITest
#define _Included_com_example_ndk_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_ndk_JNITest
* Method: callMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_callMethod
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
- com_example_ndk_JNITest.c
#include "/include/com_example_ndk_JNITest.h"
#include <jni.h>
#include <stdio.h>
#include <syslog.h>
/*
* Class: com_example_ndk_JNITest
* Method: callMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_callMethod
(JNIEnv *env, jobject object) {
// 获取java class
jclass clazz = (*env)->GetObjectClass(env, object);
// 获取class中实例方法的id
jmethodID instanceMethodId = (*env)->GetMethodID(env, clazz, "instanceMethod", "()Ljava/lang/String;");
// 调用java中实例方法
jstring instanceMethodResult = (*env)->CallObjectMethod(env, object, instanceMethodId);
const char *instanceMethodResultStr = (*env)->GetStringUTFChars(env, instanceMethodResult, NULL);
syslog(LOG_INFO, "java instance method return value %s", instanceMethodResultStr);
(*env)->ReleaseStringUTFChars(env, instanceMethodResult, instanceMethodResultStr);
// 获取class中静态方法的id
jmethodID staticMethodId = (*env)->GetStaticMethodID(env, clazz, "staticMethod", "()Ljava/lang/String;");
// 调用java中静态方法
jstring staticMethodResult = (*env)->CallStaticObjectMethod(env, clazz, staticMethodId);
const char *staticMethodResultStr = (*env)->GetStringUTFChars(env, staticMethodResult, NULL);
syslog(LOG_INFO, "java static method return value %s", staticMethodResultStr);
(*env)->ReleaseStringUTFChars(env, staticMethodResult, staticMethodResultStr);
}
- JNITest.java
package com.example.ndk;
public class JNITest {
// 实例方法
private String instanceMethod() {
return "instance method";
}
// 静态方法
private static String staticMethod() {
return "static method";
}
static {
System.loadLibrary("ndk");
}
public native void callMethod();
}
- JniActivity.java
package com.example.ndk;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JNITest jniTest = new JNITest();
jniTest.callMethod();
}
}
7 域和方法描述符(JNI调用获取id时需要用到的类型签名声明)
JNI | Java |
---|---|
Z | java中的boolean类型 |
B | java中的byte类型 |
C | java中的Char类型 |
S | java中的short类型 |
I | java中的int类型 |
J | java中的long类型 |
F | java中的float类型 |
D | java中的double类型 |
L全类名路径 | java中的对象类型,如 Ljava/lang/String; (注意分号不能丢) |
[type | java中的数组类型 |
(arg-type)ret-type | java中的方法类型,例如 (III)V方法签名表示方法传递三个int类型参数,返回类型为void |