标准Java平台下,每个Process能够产生不少JavaVM对象,但在Android平台上,每个Process只能产生一个Dalvik VM对象,也就是说在Android进程中是经过一个虚拟器对象来服务全部Java和c/c++代码。html
在加载动态连接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved)
(若是定义了该函数)。第一个参数会传入JavaVM指针。java
在native code中调用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)
能够获得JavaVM指针。android
两种状况下,均可以用全局变量,好比
JavaVM* g_jvm
来保存得到的指针以便在任意上下文中使用。c++
JNIEnv是一个指针, 指向一个线程相关的结构, 线程相关结构指向JNI函数指针数组, 这个数组中存放了大量的JNI函数指针, 这些指针指向了具体的JNI函数。
数组
调用Java函数:JNIEnv表明Java运行环境, 可使用JNIEnv调用Java中的代码;缓存
操做Java对象:Java对象传入JNI层就是Jobject对象, 须要使用JNIEnv来操做这个Java对象;markdown
当本地c/c++代码想得到当前线程的JNIEnv时,可使用两种方法:oracle
vm->AttachCurrentThread(&env, 0)
vm->GetEnv((void**)&env, JNI_VERSION_1_6)
须要强调的是JNIEnv是跟线程相关的,最好仍是不要缓存这个JNIEnv* 。app
当建立的线程须要获取JNIEnv* 的时候,最好在刚建立的时候调用一次AttachCurrentThread,而且不要忘记线程结束的时候执行DettachCurrentThread。jvm
public native String getStringFromNative();
原生实例方法经过第二个参数获取实例引用,是jobject类型:
JNIEXPORT jstring JNICALL Java_com_dean_testndk_JNIHelper_getStringFromNative
(JNIEnv *, jobject);
public static native String getStringFromNative();
静态方法没有与实例绑定,所以第二个参数是jclass类型:
JNIEXPORT jstring JNICALL Java_com_dean_testndk_JNIHelper_getStringFromNative
(JNIEnv *, jclass);
原生代码中,c与c++调用JNI函数的语法不一样:
return (*env)->NewStringUTF(env, "Hello from JNI !");
C代码中,JNIEnv是指向JNIVativeInterface结构的指针,为了访问任何一个JNI函数,该指针须要首先被解引用。由于不了解JNI环境,因此须要将JNIEnv传递给调用者
return env->NewStringUTF("Hello from JNI !");
C++代码中,JNIEnv是C++类实例,JNI函数以成员函数的形式存在。所以不须要给调用着传递参数便可使用。
Java中有两种数据类型:
boolean, byte, char, short, int, long, float, double
Java基本数据类型,能够直接与C/C++的相应基本数据类型映射
字符串类,数组类,以及其余类
与基本类型不一样,引用类型对原生方法不透明,所以引用类型不能直接使用和修改,须要经过JNIEnv接口指针来调用JNI提供的API。
jclass FindClass(JNIEnv *env, const char *name);
jclass clz = (*env)->FindClass(env, "com/example/hello_jnicallback/JniHandler");
jclass GetObjectClass(JNIEnv *env, jobject obj);
jclass clz = (*env)->GetObjectClass(env, instance);
jobject NewObject(JNIEnv *env, jclass clazz,
jmethodID methodID, ...);
经过类,方法ID,对应参数来建立一个对象的实例:
jclass clz = (*env)->FindClass(env, "com/example/hello_jnicallback/JniHandler");
jmethodID jniHelperCtor = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
在JNI开发中,常常会在Native中调用Java的域和方法。
Java的域和方法都有两类:
以下:
public class JavaClass {
// 实例域
private String instanceFiled = "Instance Field";
// 静态域
private static String staticFiled = "Static Field";
// 实例方法
private String instanceMethod() {
return "Instance Method";
}
// 静态方法
private static String staticMethod() {
return "Static Method";
}
}
下面例子是获取实例域,方法的代码。静态域,静态方法使用基本相同,都是先获取描述它的ID,而后在经过ID调用相应方法。
void nativeCallJavaClass(JNIEnv *env, jobject instance) {
jclass clazz = env->GetObjectClass(instance);
// 获取实例域的FieldID
jfieldID instanceFieldId = env->GetFieldID(clazz, "instanceField", "Ljava/lang/String;");
// 获取实例域
jstring instanceField = (jstring) env->GetObjectField(instance, instanceFieldId);
// 获取实例方法的MethodID
jmethodID instanceMethodId = env->GetMethodID(clazz, "staticMethod", "Ljava/lang/String;");
// 调用方法得到返回值
jstring instanceMethod = (jstring) env->CallObjectMethod(clazz, instanceMethodId);
};
为了提高应用程序的性能,对于频繁使用的域和方法,能够缓存它们的ID方便下次使用。
Ljava/lang/String
; [I
[F
[Ljava/lang/String;
[Ljava/lang/Object;
[[I
[[F
类描述符:将完整的包名+类名中的.分隔符换成/分隔符
java/lang/String
或者使用域描述符[Ljava/lang/String;
数组类型的描述符:同域描述符
Ljava/lang/String;
(ILjava/lang/Object;)I
([B)V
java在bin中提供了javap命令,用于查看一个类方法的签名
cd到.class对应路径,而后执行javap -s classname
例如:
cd /Users/DeanGuo/TestNDK/app/build/intermediates/classes/debug/com/dean/testndk/;
javap -s JNIHelper
Compiled from "JNIHelper.java"
public class com.dean.testndk.JNIHelper {
public com.dean.testndk.JNIHelper();
descriptor: ()V
public static native java.lang.String getStringFromNative();
descriptor: ()Ljava/lang/String;
}
bogon:testndk DeanGuo$ javap -s JNIHelper
Warning: Binary file JNIHelper contains com.dean.testndk.JNIHelper
Compiled from "JNIHelper.java"
public class com.dean.testndk.JNIHelper {
public com.dean.testndk.JNIHelper();
descriptor: ()V
public static native java.lang.String getStringFromNative();
descriptor: ()Ljava/lang/String;
}
局部引用不能在后续的调用中呗缓存及重用,主要由于它们的使用期限仅限于原生方法,一旦原生函数返回,局部引用就被释放。也能够用void DeleteLocalRef(JNIEnv *env, jobject localRef);
函数显示的释放。
全局引用在原生方法的后续调用过程当中依然有效,除非它们被原生代码显式释放。
jclass globalClazz;
jclass localClazz = env->FindClass("java/lang/String");
globalClazz = (jclass) env->NewGlobalRef(localClazz);
env->DeleteGlobalRef(globalClazz);
与全局应用同样,弱全局引用在原生方法的后续调用过程当中依然有效。不一样的是,弱全局引用并不阻止潜在对象被垃圾回事。
jclass weakGlobalClazz;
jclass localClazz = env->FindClass("java/lang/String");
weakGlobalClazz = (jclass) env->NewWeakGlobalRef(localClazz);
if (JNI_FALSE == env->IsSameObject(weakGlobalClazz, nullptr)) {
// 有效
} else {
// 对象被垃圾回收器回收, 不可以使用
}
env->DeleteGlobalRef(weakGlobalClazz);
NDK开发中JNI时,可使用__android_log_print
打印log信息。
#include <android/log.h>
头文件加入Lib
ldLibs "log"
LOCAL_LDLIBS+= -L$(SYSROOT)/usr/lib -llog
使用__android_log_print(ANDROID_LOG_INFO, "JNITag","string From Java To C : %s", str);
#include <android/log.h>
// Android log function wrappers
static const char* kTAG = "testNDK";
#define LOGI(...) \
((void)__android_log_print(ANDROID_LOG_INFO, kTAG, __VA_ARGS__))
#define LOGW(...) \
((void)__android_log_print(ANDROID_LOG_WARN, kTAG, __VA_ARGS__))
#define LOGE(...) \
((void)__android_log_print(ANDROID_LOG_ERROR, kTAG, __VA_ARGS__))
// 使用以下:
LOGI("JNI_LOG");
若是想在NDK中使用c++STL库:
APP_STL := gnustl_static
stl "gnustl_static"
- system:使用默认最小的C++运行库,这样生成的应用体积小,内存占用小,但部分功能将没法支持
- stlport_static:使用STLport做为静态库,这项是Android开发网极力推荐的
- stlport_shared:STLport 做为动态库,这个可能产生兼容性和部分低版本的Android固件,目前不推荐使用。
- gnustl_static:使用 GNU libstdc++ 做为静态库