项目代码:https://github.com/VincentWei95/ndk
Android JNI开发系列:第一章 JNIEnv接口指针
Android JNI开发系列:第二章 数据类型
Android JNI开发系列:第三章 对引用数据类型的操作
Android JNI开发系列:第四章 异常处理
Android JNI开发系列:第五章 局部和全局引用
Android JNI开发系列:第六章 线程
Android JNI开发系列:第七章 POSIX线程
Android JNI开发系列:第八章 POSIX Socket API 面向连接的通信
Android JNI开发系列:第九章 POSIX Socket API 无连接的通信
Android JNI开发系列:第十章 POSIX Socket API 本地通信
文章目录
1 字符串操作1.1 创建字符串1.2 java字符串转换成C字符串1.3 字符串操作demo
2 数组操作2.1 创建数组2.2 访问数组元素2.3 对副本操作2.3.1 将java数组复制到C数组中2.3.2 将C数组复制给java数组
2.4 对直接指针操作(操作数组元素)2.5 数组操作demo
3 NIO操作3.1 创建直接字节缓冲区3.2 获取直接字节缓冲区
4 JNI访问java的成员变量和方法总结5 JNI访问java对象的成员变量5.1 通过对象引用获取类5.2 获取java对象的成员变量id5.3 获取java对象的静态成员变量id5.4 获取java对象的成员变量5.5 获取成员变量demo
6 JNI调用java对象中的方法6.1 获取实例方法的id6.2 获取静态方法的id6.3 调用实例方法6.4 调用静态方法6.5 调用方法demo
7 域和方法描述符(JNI调用获取id时需要用到的类型签名声明)
以下介绍的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
;
javaString
= (*env
)->NewString(env
, "Hello world");
javaString
= (*env
)->NewStringUTF(env
, "Hello world!");
1.2 java字符串转换成C字符串
const jbyte
* str
;
jboolean isCopy
;
str
= (*env
)->GetStringChars(env
, javaString
, &isCopy
);
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
#include
<jni.h>
#ifndef _Included_com_example_ndk_JNITest
#define _Included_com_example_ndk_JNITest
#ifdef __cplusplus
extern
"C" {
#endif
JNIEXPORT jstring JNICALL Java_com_example_ndk_JNITest_getStringFromJNI
(JNIEnv
*, jobject
);
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>
JNIEXPORT jstring JNICALL Java_com_example_ndk_JNITest_getStringFromJNI
(JNIEnv
*env
, jobject object
) {
return (*env
)->NewStringUTF(env
, "hello from jni!");
}
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
;
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
;
nativeDirectArray
= (*env
)->GetIntArrayElements(env
, javaArray
, &isCopy
);
(*env
)->ReleaseIntArrayElements(env
, javaArray
, nativeDirectArray
, 0);
2.5 数组操作demo
com_example_ndk_JNITest.h
#include
<jni.h>
#ifndef _Included_com_example_ndk_JNITest
#define _Included_com_example_ndk_JNITest
#ifdef __cplusplus
extern
"C" {
#endif
JNIEXPORT
void JNICALL Java_com_example_ndk_JNITest_javaArrayCopyToCArray
(JNIEnv
*, jobject
, jintArray
);
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>
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
);
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
]);
}
}
JNIEXPORT jintArray JNICALL Java_com_example_ndk_JNITest_cArrayCopyToJavaArray
(JNIEnv
*env
, jobject object
) {
jintArray javaArray
= (*env
)->NewIntArray(env
, 10);
jint elements
[10];
for (jint i
= 0; i
< 10; i
++) {
elements
[i
] = i
* 2;
}
(*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 通过对象引用获取类
jclass clazz
;
clazz
= (*env
)->GetObjectClass(env
, jobject
);
5.2 获取java对象的成员变量id
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
);
5.5 获取成员变量demo
com_example_ndk_JNITest.h
#include
<jni.h>
#ifndef _Included_com_example_ndk_JNITest
#define _Included_com_example_ndk_JNITest
#ifdef __cplusplus
extern
"C" {
#endif
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>
JNIEXPORT
void JNICALL Java_com_example_ndk_JNITest_javaField
(JNIEnv
*env
, jobject object
) {
jclass clazz
= (*env
)->GetObjectClass(env
, object
);
jfieldID instanceFieldId
= (*env
)->GetFieldID(env
, clazz
, "mInstanceField", "Ljava/lang/String;");
jstring instanceField
= (*env
)->GetObjectField(env
, object
, instanceFieldId
);
const char *instanceFieldStr
= (*env
)->GetStringUTFChars(env
, instanceField
, NULL
);
syslog(LOG_INFO
, "java instance field value %s", instanceFieldStr
);
(*env
)->ReleaseStringUTFChars(env
, instanceField
, instanceFieldStr
);
jfieldID staticFieldId
= (*env
)->GetStaticFieldID(env
, clazz
, "sStaticField", "Ljava/lang/String;");
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
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
);
6.4 调用静态方法
通过 CallStaticxxxMethod 函数调用静态方法。
jstring staticMethodResult
;
staticMethodResult
= (*env
)->CallStaticStringMethod(env
, clazz
, staticMethodId
);
6.5 调用方法demo
com_example_ndk_JNITest.h
#include
<jni.h>
#ifndef _Included_com_example_ndk_JNITest
#define _Included_com_example_ndk_JNITest
#ifdef __cplusplus
extern
"C" {
#endif
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>
JNIEXPORT
void JNICALL Java_com_example_ndk_JNITest_callMethod
(JNIEnv
*env
, jobject object
) {
jclass clazz
= (*env
)->GetObjectClass(env
, object
);
jmethodID instanceMethodId
= (*env
)->GetMethodID(env
, clazz
, "instanceMethod", "()Ljava/lang/String;");
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
);
jmethodID staticMethodId
= (*env
)->GetStaticMethodID(env
, clazz
, "staticMethod", "()Ljava/lang/String;");
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时需要用到的类型签名声明)
JNIJava
Zjava中的boolean类型Bjava中的byte类型Cjava中的Char类型Sjava中的short类型Ijava中的int类型Jjava中的long类型Fjava中的float类型Djava中的double类型L全类名路径java中的对象类型,如 Ljava/lang/String;(注意分号不能丢)[typejava中的数组类型(arg-type)ret-typejava中的方法类型,例如 (III)V方法签名表示方法传递三个int类型参数,返回类型为void