这篇笔记是我半年前写的,当时由于某些缘由常常须要写jni方面的代码,因此就深刻学习了下jni方面的知识,如今再来看以前写的东西,一句话归纳就是深度不够,废话太多。由于这是一个不全的笔记(还有一部分想写的内容未能写上),因此当初想分享给其余同事的也很差意思分享。html
#-------------Add Now-------------#java
jni是java native interface的简写,是java和c/c++通讯的桥梁。android
jni.h是Android提供的jni调用接口头文件,我认为学习jni最好的办法是熟悉它里面的每个接口、而后看framework中是如何去使用这些接口的,相信官方考虑的老是比较周到。在了解基础知识后,遇到问题就直接查看dalvik或者art里面是哪里打印出来的,从最根本去了解它的错误。c++
基础知识:web
1. jni中严格区分c和c++调用方式,在native方法中提供的env是区分c、c++的,这个env对应的struct是不同的(细微的差异而已)。编程
2. jni中严格区分static与非static方法、参数、变量,无论是java调c/c++仍是反着调都须要注意,它们对应的jni接口(c/c++调java)和参数(java native方法)是不同的。数组
3. jni的基本数据结构是和java的基本数据结构对应起来的,并非和c/c++的基本数据结构对应。数据结构
4. 调用的时候注意不要写错名字吧,区分好static和非static、区分好class和object类型,熟悉jni的每个接口的含义,用时可以找到。oracle
5. native方法静态注册(Java_PackageName_ClassName_MethodName)时只能识别c类型的方法名,在c++中记得添加extern “C”,动态注册(RegisterNatives)时不作这样的限制,JNINativeMethod的第二个参数signature,若是是class则经过java获取;第三个参数fnPtr的返回值记得强制转换为void *类型。app
比较隐蔽的错误:
1. 引用类型,引用类型分为local、weak、global。local类型只在函数调用返回以前有效,global至关于全局变量,不主动release会一直存在。
2. 引用计数,每一种引用类型都有次数限制,这个是由dalvik或者art决定的,不须要的得手动release掉,要不crash信息也不容易看出问题所在。
3. 在非UI线程下使用env须要AttacbCurrentThread,使用后记得释放。
4. jclass、jmethod、jfield等须要在UI线程或者JNI_OnLoad中获取。
调试:
jni方面也接触一段时间了,真正让我去了解jni是由于在webrtc中存在很多jni的调用,并且须要写一些jni方法,不得不深刻了解。jni方面的crash信息不是很容易看出问题所在,不能像其余crash同样给出堆栈信息,因此对于初学者来讲比较难找出问题所在。不过若是能把该注意的地方注意了,再仔细一些问题应该不大,毕竟我相信咱们平时的项目中真正使用到jni的地方不会太多。实在不行就只能gdb了,官网中给出了Android gdb的使用方法。
为避免忘记释放,能够参考智能指针写一些实用性的东西。还能够写不少其余的,例如StringcharsScoped、LocalRefScoped等。
// Attach thread to JVM if necessary and detach at scope end if originally
// attached.
// interface. !come from android source!
#include <jni.h>
class AttachThreadScoped {
public:
explicit AttachThreadScoped(JavaVM* jvm);
~AttachThreadScoped();
JNIEnv* env();
private:
bool attached_;
JavaVM* jvm_;
JNIEnv* env_;
};
// implement
#include <assert.h>
#include <stddef.h>
AttachThreadScoped::AttachThreadScoped(JavaVM* jvm) : attached_(false), jvm_(jvm), env_(NULL) {
jint ret_val = jvm->GetEnv(reinterpret_cast<void**>(&env_), JNI_VERSION_1_4);
if (ret_val == JNI_EDETACHED) {
// Attach the thread to the Java VM.
ret_val = jvm_->AttachCurrentThread(&env_, NULL);
attached_ = ret_val == JNI_OK;
assert(attached_);
}
}
AttachThreadScoped::~AttachThreadScoped() {
if (attached_ && (jvm_->DetachCurrentThread() < 0)) {
assert(false);
}
}
JNIEnv* AttachThreadScoped::env() { return env_; }
#-------------End Add-------------#
在jni的方法中咱们是看不到char、short、int、unsigned int这样的基本数据类型的,都是看到jchar、jshort、jint等这样的基本数据类型。为何不直接使用char、short等这样的基本数据类型呢?是由于要和Java的基本数据类型对应起来(Java的基本数据有boolean、byte、char、short、int、long、float和double),Java的这些基本数据的值正是jni里面定义的这些jboolean、jchar等。并且这里还考虑到了编译器支不支持C99标准。下面咱们直接看看jni.h中是怎么考虑到这两种状况的吧:
经过宏HAVE_INTTYPES_H来判断当前编译器时候存在inttypes.h这个头文件,若是存在则直接使用C99标准的定义,若是不存在则定义和C99同样的数据类型。
注意事项:
1. jboolean是unsigned char类型
2. jchar是unsinged char类型
3. NDK中的交叉编译器是支持并使用C99标准的
#ifdef HAVE_INTTYPES_H
# include <inttypes.h> /* C99 */
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 */
#else
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
#endif
/* "cardinal indices and sizes" */
typedef jint jsize;
在c语言中数组均可以用void *来表示,固然在c++中也能够用void *来表示,毕竟c++是兼容c的。但在jni.h中c++把字符串和数组与class同样对待,当作一个类来处理,这个多是要和c区分开来。直接看源码:
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
#else /* not __cplusplus */
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
#endif /* not __cplusplus */
在jni 1.6中新增了一个方法用于获取对象的引用类型,引用类型能够分为无效引用、本地引用、全局引用和弱全局引用。
无效引用:此对象不是一个引用类型,通常状况下为空、野指针、一个值的地址或者不是jobject类型等类型,一句话归纳就是:不是一个有效的地址
本地引用:此对象的做用范围仅限于此对象所在的方法,当函数返回后此引用无效
全局引用:此对象在整个程序中均可以使用,若是不显示删除,则生命周期和JavaVM的生命周期一致
弱全局引用:在全局引用能用的地方均可以使用弱引用,可是这个引用随时都有可能会被GC回收,用的时候须要判断是否为空,若是被GC回收后此值为空,能够这样判断(env->IsSameObject(weakGlobal, NULL))
引用计数:
在jni中你是不能任意的new出无限多个引用的,这些引用都是须要占用资源的,特别是全局引用若是不删除不只会形成内存泄漏还会发生不可预知的错误,在jni的实现文件中能够知道每一种引用类型的个数都是有限制的,因此在开发过程当中须要注重引用类型的删除,以避免发生不可预知的错误。
//jni_internal.cc
static const size_t kLocalsInitial = 64; // Arbitrary.
static const size_t kLocalsMax = 512; // Arbitrary sanity check.
static size_t gGlobalsInitial = 512; // Arbitrary.
static size_t gGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.)
static const size_t kWeakGlobalsInitial = 16; // Arbitrary.
static const size_t kWeakGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.)
jobjectRefType的定义:
typedef enum jobjectRefType {
JNIInvalidRefType = 0,
JNILocalRefType = 1,
JNIGlobalRefType = 2,
JNIWeakGlobalRefType = 3
} jobjectRefType;
获取jobjectRefType的方法:
能够分为c和c++两种方式调用,不过最终c++方法也是调用c方法。
/*
C方法*/
/* added in JNI 1.6 */
jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
/*
C++方法*/
/* added in JNI 1.6 */
jobjectRefType GetObjectRefType(jobject obj)
{ return functions->GetObjectRefType(this, obj); }
下面经过一个例子来认识这些引用:
这个例子我是拿NDK里面的hello-jni来改写的,经过这个例子能够认识这些引用是如何建立和删除的。
Java代码:
//修改前
public native String stringFromJNI();
//修改后
public native String stringFromJNI(Object context);
Jni代码:
#include <string.h>
#include <jni.h>
#include <android/log.h>
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, "JNITest", __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, "JNITest", __VA_ARGS__)
extern "C" jstring
Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz, jobject context)
{
jobjectRefType type = env->GetObjectRefType(context);
if (JNILocalRefType == type) {
ALOGI("%s:%d JNILocalRefType", __FUNCTION__, __LINE__);
}
type = env->GetObjectRefType(NULL);
if (JNIInvalidRefType == type) {
ALOGI("%s:%d JNIInvalidRefType", __FUNCTION__, __LINE__);
}
jobject localContext = env->NewLocalRef(context);
type = env->GetObjectRefType(localContext);
if (JNILocalRefType == type) {
ALOGI("%s:%d JNILocalRefType", __FUNCTION__, __LINE__);
env->DeleteLocalRef(localContext);
}
jobject globalContext = env->NewGlobalRef(context);
type = env->GetObjectRefType(globalContext);
if (JNIGlobalRefType == type) {
ALOGI("%s:%d JNIGlobalRefType", __FUNCTION__, __LINE__);
env->DeleteGlobalRef(globalContext);
}
jobject weakGlobalContext = env->NewWeakGlobalRef(context);
type = env->GetObjectRefType(weakGlobalContext);
if (JNIWeakGlobalRefType == type) {
ALOGI("%s:%d JNIWeakGlobalRefType", __FUNCTION__, __LINE__);
env->DeleteWeakGlobalRef(weakGlobalContext);
}
return env->NewStringUTF("hello jni");
}
Android.mk
修改hello-jni.c为hello-jni.cpp,添加了引用log库,连接方式修改成c++方式,由于习惯写c++代码了
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_LINK_MODE := c++
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
Application.mk
由于我仅仅须要arm平台的
APP_ABI := armeabi
最后的打印为:
我实际代码的行号和以上我给出的代码不同,因此打印的行号是我实际代码存在的行号,不过实际输出结果是符合咱们所想的。
12-22 12:34:38.381: I/JNITest(6406): Java_com_example_hellojni_HelloJni_stringFromJNI:36 JNILocalRefType
12-22 12:34:38.381: I/JNITest(6406): Java_com_example_hellojni_HelloJni_stringFromJNI:41 JNIInvalidRefType
12-22 12:34:38.381: I/JNITest(6406): Java_com_example_hellojni_HelloJni_stringFromJNI:47 JNILocalRefType
12-22 12:34:38.381: I/JNITest(6406): Java_com_example_hellojni_HelloJni_stringFromJNI:54 JNIGlobalRefType
12-22 12:34:38.381: I/JNITest(6406): Java_com_example_hellojni_HelloJni_stringFromJNI:61 JNIWeakGlobalRefType
经过查看Android 4.4.2_r1源码发现jni.h提供出来的这些jni方法最终都是用c++实现的,并且还分为debug版本和release版本。
按照调用方式能够分为两类c和c++两种调用方式,这两种方式除了调用方式上没有任何不一样的地方,并且c++的调用方式本质上也是调用c方法。jni的这种划分方式是经过后缀名来划分的,编译时会根据后缀名来选择编译器,可以识别咱们常见的后缀名,例如.cc .cp .cxx .cpp .CPP .c++ .C。若是须要扩展其余后缀可使用LOCAL_CPP_EXTENSION,扩展的后缀名须要加上‘.’。
LOCAL_CPP_EXTENSION := .cc .cxx
这两种调用方式在编译阶段就已经肯定了,在jni.h中JNIEnv中的方法分别使用两个结构体_JNIEnv和JNINativeInterface来表示,若是是c++使用_JNIEnv结构体中的方法,若是是c则使用JNINativeInterface中的方法,为何要这样作呢?这样是为了充分使用c++的特性,在c++中对struct进行了扩展,里面可以直接定义方法,且默认是public类型的,定义了这样一个结构体则结构体对象能够直接去访问这些方法,让开发者写起来轻松些,可是c就没那么幸运了,c的struct虽然也能够定义方法可是也只能是函数指针这样的形式。同理jni.h中JavaVM也分别使用两个结构体_JavaVM和JNIInvokeInterface来表示。咱们直接看代码jni.h中是如何定义JNIEnv和JavaVM的。
使用编译器内置宏’__cplusplus‘来区分c和c++代码。
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
看看_JNIEnv和JNINativeInterface两个结构体:
从以下代码中咱们能够看出最终都是调用同一个方法,在咱们的代码中也能够利用这种方式把咱们写的c接口以c++的接口提供出去。一样的道理咱们对如下代码稍微修改,咱们一样能够把咱们写的c++接口以c接口的形式提供出去。
/*
c方法使用JNINativeInterface这个结构体
*/
struct JNINativeInterface {
//...
jint (*GetVersion)(JNIEnv *);
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
//...
};
/*
c++方法使用_JNIEnv这个结构体
*/
struct _JNIEnv {
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
//...
jint GetVersion()
{ return functions->GetVersion(this); }
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
//...
#endif
};
因此c和c++的JNIEnv和JavaVM是不同的,若是咱们的代码中同时存在c和c++代码,而且都须要用到JNIEnv和JavaVM时须要注意,每个jni方法都有一个本身的JNIEnv,这个JNIEnv不能传给c++代码去调用c++方式的jni方法,同理c++方式的JNIEnv不能传给c代码去调用c方式的jni方法。c代码中的JavaVM须要用到c代码的JNIEnv方法得到,同理c++代码中的JavaVM须要用到c++代码的JNIEnv方法得到。经过JNIEnv获取JavaVM的方法以下:
/*c方法*/
JavaVM *jvm = NULL;
(*env)->GetJavaVM(env, &jvm);
/*c++方法*/
JavaVM *jvm = NULL;
env->GetJavaVM(&jvm);
若是理解了我上面的说的知识就知道咱们为何是这样调用了,如下是咱们经常使用的调用jni的方式,这里以GetObjectRefType为例:
/*C方法*/
jobjectRefType type = (*env)->GetObjectRefType(env, obj);
/*C++方法*/
jobjectRefType type = env->GetObjectRefType(obj);
c、c++调用Java方法又能够分为static和非static两种方式,c、c++并不关心调用的Java方法是private仍是public的。在使用的时候须要格外注意,在jni的编程中若是你使用了不当的方式它并不会提示你使用了错误的方式,而是直接给你一个crash,crash信息并不指明是什么错误,因此在jni编程中咱们尽量的注意这些细节要不咱们会花费过多的时间在调试上。前面说了调用方式分为c和c++两种方式,在我如下全部的例子我都会使用c++的调用方式,全部的例子都是用c++写的。若是但愿使用c方式调用能够参考我上面写的那个例子就行了,仅仅存在一些细微的变化。
4.2.1 下面咱们分析调用Java非static方法
调用Java方法能够按照类型来划分分为十种,每一种类型按照参数划分又分别有三种。因此咱们看到的调用Java方法的jni接口是这样的
/*
这种调用方式把须要传的参数依次写在第二个参数以后就好,支持的类型为jni中的基本数据类型和数组类型
这种方式也是Android源码中经常使用的方式,由于灵活性较大,使用方便。
*/
_jtype Call<_jname>Method(jobject obj, jmethodID methodID, ...)
/*
这种调用方式把须要传的参数依次写在变参数组中,在Android源码中不多看到这样的使用方式
*/
_jtype Call<_jname>MethodV(jobject obj, jmethodID methodID, va_list args)
/*
这种调用方式把须要传的参数依次写在jvalue数组中,在Android源码中更少看到这样的使用方式
*/
_jtype Call<_jname>MethodA(jobject obj, jmethodID methodID, jvalue* args)
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
以上中_jtype和_jname对应的十种类型分别为:
(jobject, Object): CallObjectMethod CallObjectMethodV CallObjectMethodA
(jboolean, Boolean): CallBooleanMethod CallBooleanMethodV CallBooleanMethodA
(jbyte, Byte): CallByteMethod CallByteMethodV CallByteMethodA
(jchar, Char): CallCharMethod CallCharMethodV CallCharMethodA
(jshort, Short): CallShortMethod CallShortMethodV CallShortMethodA
(jint, Int): CallIntMethod CallIntMethodV CallIntMethodA
(jlong, Long): CallLongMethod CallLongMethodV CallLongMethodA
(jfloat, Float): CallFloatMethod CallFloatMethodV CallFloatMethodA
(jdouble, Double): CallDoubleMethod CallDoubleMethodV CallDoubleMethodA
(void, Void): CallVoidMethod CallVoidMethodV CallVoidMethodA
可是在jni.h的_JNIEnv结构体中咱们能看到的Call<_jname>Method仅仅为CallVoidMethod,其余方法都用宏来写了,这种写法在实际开发中也是常常用到的,特别是相似这种仅仅是方法名和返回值不同,把这几个宏贴出来。
#define CALL_TYPE_METHOD(_jtype, _jname) \
__NDK_FPABI__ \
_jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...) \
{ \
_jtype result; \
va_list args; \
va_start(args, methodID); \
result = functions->Call##_jname##MethodV(this, obj, methodID, \
args); \
va_end(args); \
return result; \
}
#define CALL_TYPE_METHODV(_jtype, _jname) \
__NDK_FPABI__ \
_jtype Call##_jname##MethodV(jobject obj, jmethodID methodID, \
va_list args) \
{ return functions->Call##_jname##MethodV(this, obj, methodID, args); }
#define CALL_TYPE_METHODA(_jtype, _jname) \
__NDK_FPABI__ \
_jtype Call##_jname##MethodA(jobject obj, jmethodID methodID, \
jvalue* args) \
{ return functions->Call##_jname##MethodA(this, obj, methodID, args); }
#define CALL_TYPE(_jtype, _jname) \
CALL_TYPE_METHOD(_jtype, _jname) \
CALL_TYPE_METHODV(_jtype, _jname) \
CALL_TYPE_METHODA(_jtype, _jname)
CALL_TYPE(jobject, Object)
CALL_TYPE(jboolean, Boolean)
CALL_TYPE(jbyte, Byte)
CALL_TYPE(jchar, Char)
CALL_TYPE(jshort, Short)
CALL_TYPE(jint, Int)
CALL_TYPE(jlong, Long)
CALL_TYPE(jfloat, Float)
CALL_TYPE(jdouble, Double)
4.2.2 下面咱们分析调用Java static方法
static方法的调用和非static方法的调用时相似的,仅仅在调用方法不同,其余都和非static调用方式同样了。具体的能够看非static方法的调用。
_jtype CallStatic<_jname>Method(jclass clazz, jmethodID methodID, ...)
_jtype CallStatic<_jname>MethodV(jclass clazz, jmethodID methodID, va_list args)
_jtype CallStatic<_jname>MethodA(jclass clazz, jmethodID methodID, jvalue* args)
4.2.3 如何使用以上方法
这里仍是须要区分static方法和非static方法的,以CallVoidMethod和CallStaticVoidMethod为例,他们的第一个参数是不同的,非static方法第一个参数是jobject,能够看出这个是一个类的对象,调用非static方法是经过对象来访问的。static方法是jclass,能够看出能够直接经过class来访问这个Java方法。除了第一个参数之外第二个参数也是不同的,虽然都是jmethodID可是这个jmethodID用的方法是不同的,一个是用static方法去获取static的jmethodID,另外一个是用非static的方法去获取非static的jmethodID,这样的调用方式是和Java对应的。
1. 非static方法经过调用类的对象来调用,jmethodID经过GetMethodID得到。
2. static方法经过类来调用,jmethodID经过GetStaticMethodID来得到。
我以为咱们有必要先了解下jmethodID和jfieldID,这两个分别表示方法id和字段id,咱们能够经过方法id调用方法并获取返回值,经过变量id获取字段的值和设置字段的值。
他们的定义以下,都是struct指针类型。
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */
为了方便获取这两个值一般定义几组宏来获取这两个值,这里须要注意的是在使用宏的方法里须要有一个c++类型的JNIEnv指针对象,而且名字是env,或许你能够修改下宏的名字以及调用方法。
#define FIND_CLASS(var, className) \
var = env->FindClass(className); \
LOG_FATAL_IF(! var, "Unable to find class " className);
#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! var, "Unable to find field " fieldName);
#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! var, "Unable to find method " fieldName);
#define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! var, "Unable to find field " fieldName);
#define GET_STATIC_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
var = env->GetStaticMethodID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! var, "Unable to find static method " fieldName);
定义了宏方便咱们使用,可是怎么使用呢,如今咱们来分析GetMethodID和GetFieldID的参数。先来看看这两个方法的原型吧。
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
能够看出这两个参数都是同样的,咱们能够一块儿分析。
clazz: MethodID或者FieldID所在的class,这个能够经过FindClass得到
name:咱们须要获取Java类的方法名或者字段名
sig : sig描述的是对应于Java的类型,MethodID(描述了方法的参数和返回值),FieldID(描述了字段的类型)
关于sig字段能够参考oracle的官方文档,http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/types.html#wp16432,或者能够看我从文档提取出来的(以下),最后面还举例说明了用法,这个的命名方式和jvalue同样的,基本类型的名字只不过变成了大写而已,而后扩展了数组类型和类。
注意事项:
1. signature之间不能有空格
2. 类变量须要以"L"打头,后面加有效的类名路径(包名+类名,内部类使用"$"隔开),以";"结尾
3. 数组以"["打头,后面加类型,这里的类型仍是signature的类型,不只仅指基本类型,还包括类(Z B C S I J F D Lfully-qualified-class;)
4. 参数不是必须的,返回值是必须有的,若是返回值为空则用"V"表示,参数为空能够不写("()V" "(I)V" "(ILjava/util/HashMap;)Ljava/util/HashMap;")
5. 参数和返回值的顺序不能写反,必须遵循这样的规则(参数)返回值,记得用引号引发来,须要的是字符串的形式("(arg-types)ret-type")
Type Signature Java Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
Lfully-qualified-class; fully-qualified-class
[type type[]
(arg-types)ret-type method type
For example, the Java method:
long f(int n, String s, int[] arr);
has the following type signature:
(ILjava/lang/String;[I)J
上面已经为咱们正式写例子作了大量的铺垫,下面写一个例子看看Android源码中是如何使用这些方法的,代码提取自Android 4.4.2_r1源码,并进行了一些细微的修改以方便你们以及本身从此参考,仅做参考不能直接编译使用。
1. JNI调用Java非static方法的例子
如下用到的宏在上面已经给出,往上拖动便可看到。
/*
以调用Java的HashMap和ArrayList为例,从中了解JNI如何调用非static的方法。如下代码从Android 4.4.2_r1源码中提取,通过小小的改动方便阅读。
*/
#include <jni.h>
#include <vector>
#include <list>
#include <String8.h>
struct HashmapFields {
jmethodID init;
jmethodID get;
jmethodID put;
jmethodID entrySet;
};
static jobject KeyedVectorToHashMap (JNIEnv *env, KeyedVector<String8, String8> const &map) {
jclass clazz;
HashmapFields hashmap;
FIND_CLASS(clazz, "java/util/HashMap");
GET_METHOD_ID(hashmap.init, clazz, "<init>", "()V");
GET_METHOD_ID(hashmap.get, clazz, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
GET_METHOD_ID(hashmap.put, clazz, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
GET_METHOD_ID(hashmap.entrySet, clazz, "entrySet", "()Ljava/util/Set;");
jobject hashMap = env->NewObject(clazz, hashmap.init);
for (size_t i = 0; i < map.size(); ++i) {
jstring jkey = env->NewStringUTF(map.keyAt(i).string());
jstring jvalue = env->NewStringUTF(map.valueAt(i).string());
env->CallObjectMethod(hashMap, hashmap.put, jkey, jvalue);
env->DeleteLocalRef(jkey);
env->DeleteLocalRef(jvalue);
}
return hashMap;
}
struct ArrayListFields {
jmethodID init;
jmethodID add;
};
static jobject ListOfVectorsToArrayListOfByteArray(JNIEnv *env,
List<Vector<uint8_t> > list) {
jclass clazz;
ArrayListFields arraylist;
FIND_CLASS(clazz, "java/util/ArrayList");
GET_METHOD_ID(arraylist.init, clazz, "<init>", "()V");
GET_METHOD_ID(arraylist.add, clazz, "add", "(Ljava/lang/Object;)Z");
jobject arrayList = env->NewObject(clazz, arraylist.init);
List<Vector<uint8_t> >::iterator iter = list.begin();
while (iter != list.end()) {
jbyteArray byteArray = VectorToJByteArray(env, *iter);
env->CallBooleanMethod(arrayList, arraylist.add, byteArray);
env->DeleteLocalRef(byteArray);
iter++;
}
return arrayList;
}
2. JNI调用Java static方法的例子
/*
如下这个例子是从android_opengl_GLES30.cpp拿出来的,这个是JNI调用Java static方法的例子,直接看代码就能明白了。
感受须要说明的地方是nativeClassInit这个方法自己又是一个native方法,可是并不影响咱们去理解JNI调用static方法的知识点。
GetStaticMethodID CallStaticLongMethod CallStaticObjectMethod CallStaticIntMethod
*/
static jclass nioAccessClass;
static jclass bufferClass;
static jmethodID getBasePointerID;
static jmethodID getBaseArrayID;
static jmethodID getBaseArrayOffsetID;
static jfieldID positionID;
static jfieldID limitID;
static jfieldID elementSizeShiftID;
static void
nativeClassInit(JNIEnv *_env, jclass glImplClass)
{
jclass nioAccessClassLocal = _env->FindClass("java/nio/NIOAccess");
nioAccessClass = (jclass) _env->NewGlobalRef(nioAccessClassLocal);
jclass bufferClassLocal = _env->FindClass("java/nio/Buffer");
bufferClass = (jclass) _env->NewGlobalRef(bufferClassLocal);
getBasePointerID = _env->GetStaticMethodID(nioAccessClass,
"getBasePointer", "(Ljava/nio/Buffer;)J");
getBaseArrayID = _env->GetStaticMethodID(nioAccessClass,
"getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;");
getBaseArrayOffsetID = _env->GetStaticMethodID(nioAccessClass,
"getBaseArrayOffset", "(Ljava/nio/Buffer;)I");
positionID = _env->GetFieldID(bufferClass, "position", "I");
limitID = _env->GetFieldID(bufferClass, "limit", "I");
elementSizeShiftID =
_env->GetFieldID(bufferClass, "_elementSizeShift", "I");
}
static void *
getPointer(JNIEnv *_env, jobject buffer, jarray *array, jint *remaining, jint *offset)
{
jint position;
jint limit;
jint elementSizeShift;
jlong pointer;
position = _env->GetIntField(buffer, positionID);
limit = _env->GetIntField(buffer, limitID);
elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID);
*remaining = (limit - position) << elementSizeShift;
pointer = _env->CallStaticLongMethod(nioAccessClass,
getBasePointerID, buffer);
if (pointer != 0L) {
*array = NULL;
return (void *) (jint) pointer;
}
*array = (jarray) _env->CallStaticObjectMethod(nioAccessClass,
getBaseArrayID, buffer);
*offset = _env->CallStaticIntMethod(nioAccessClass,
getBaseArrayOffsetID, buffer);
return NULL;
}
3. 字段(FieldID)方面的例子,这里就不去细分static和非static了,区别就在于调用的jni方法不同,须要本身注意。
测试例子中写了一个方法去获取类的字段,里面涉及到了普通的类和内部类,例子中的获取类的字段这个方法不错,在须要获取类的不少字段时这样写的效率很高。
/*
代码仍是提取自Android 4.4.2_r1 可是仍是不能直接编译经过的,也是稍微修改过的。
*/
struct fields_t {
jfieldID context;
jfieldID facing;
jfieldID orientation;
jfieldID canDisableShutterSound;
jfieldID face_rect;
jfieldID face_score;
jfieldID rect_left;
jfieldID rect_top;
jfieldID rect_right;
jfieldID rect_bottom;
jmethodID post_event;
jmethodID rect_constructor;
jmethodID face_constructor;
};
static int find_fields(JNIEnv *env, field *fields, int count)
{
for (int i = 0; i < count; i++) {
field *f = &fields[i];
jclass clazz = env->FindClass(f->class_name);
if (clazz == NULL) {
ALOGE("Can't find %s", f->class_name);
return -1;
}
jfieldID field = env->GetFieldID(clazz, f->field_name, f->field_type);
if (field == NULL) {
ALOGE("Can't find %s.%s", f->class_name, f->field_name);
return -1;
}
*(f->jfield) = field;
}
return 0;
}
int register_android_hardware_Camera(JNIEnv *env)
{
fields_t fields;
field fields_to_find[] = {
{ "android/hardware/Camera", "mNativeContext", "I", &fields.context },
{ "android/hardware/Camera$CameraInfo", "facing", "I", &fields.facing },
{ "android/hardware/Camera$CameraInfo", "orientation", "I", &fields.orientation },
{ "android/hardware/Camera$CameraInfo", "canDisableShutterSound", "Z",
&fields.canDisableShutterSound },
{ "android/hardware/Camera$Face", "rect", "Landroid/graphics/Rect;", &fields.face_rect },
{ "android/hardware/Camera$Face", "score", "I", &fields.face_score },
{ "android/graphics/Rect", "left", "I", &fields.rect_left },
{ "android/graphics/Rect", "top", "I", &fields.rect_top },
{ "android/graphics/Rect", "right", "I", &fields.rect_right },
{ "android/graphics/Rect", "bottom", "I", &fields.rect_bottom },
};
if (find_fields(env, fields_to_find, NELEM(fields_to_find)) < 0)
return -1;
return 0;
}
4.2.3 注意事项:
1. 内部类的用法不是斜杠("/"),是("$"),例如("android/hardware/Camera$CameraInfo")
2. 获取一个类的构造函数GetMethodID或者GetStaticMethodID方法,不过中间的名字必须是("<init>"),sig是根据构造函数的实际参数和返回值写的,sig的写法往上拖动便可看到
3. 调用Java方法的jni接口注意区分static和非static接口
这里不会花很大篇章去详细描述了,由于GetFieldID和GetStaticFieldID这两个很重要的方法已经在4.2.3描述得和清楚了,并且字段的调用方式和调用Java的方法是很相识的,使用上难度并不大。
字段的获取和调用也分为static和非static两种方式,下面先来看看有那些方法。
1. 非static方法
/*
获取字段类型的方法
*/
_jtype Get<_jname>Field
GetObjectField GetBooleanField GetByteField
GetCharField GetShortField GetIntField
GetLongField GetFloatField GetDoubleField
/*
设置字段类型的方法
*/
void Set<_jname>Field
SetObjectField SetBooleanField SetByteField
SetCharField SetShortField SetIntField
SetLongField SetFloatField SetDoubleField
2. static方法
/*
获取字段类型的方法
*/
_jtype GetStatic<_jname>Field
GetStaticObjectField GetStaticBooleanField GetStaticByteField
GetStaticCharField GetStaticShortField GetStaticIntField
GetStaticLongField GetStaticFloatField GetStaticDoubleField
/*
设置字段类型的方法
*/
void SetStatic<_jname>Field
SetStaticObjectField SetStaticBooleanField SetStaticByteField
SetStaticCharField SetStaticShortField SetStaticIntField
SetStaticLongField SetStaticFloatField SetStaticDoubleField
4.3.1 如何使用以上方法
这里以GetObjectField、SetObjectField、GetStaticObjectField和SetStaticObjectField为例:
先看看他们的原型:
jobject GetObjectField(jobject obj, jfieldID fieldID)
void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
1. 非static的类型get和set返回值不同,get方法返回值的类型正是咱们须要的类型,关于这个类型在最前面基本数据类型介绍过;第一个参数是类的对象,这个对象能够本身构造(4.2中有具体的描述),也能够经过native方法的第二个参数得到,非static的native方法第二个参数为类的对象;第二个参数是字段ID经过GetFieldID得到,关于这个我在4.2.3中有具体的描述,在这里就不说过多的描述了
2. static方法和非static方法的差别在于第一个参数和第二个参数,第一个参数是类,能够直接经过类来得到字段,这个类能够经过FindClass得到,也能够经过static native方法的第二个参数得到,static的native方法第二个参数就是类自己;第二个参数是字段ID经过GetStaticFieldID得到,关于这个我在4.2.3中有具体的描述,在这里就不过多的描述了
这里就不举例了,使用方式和调用Java方法相似的。
对于数组的操做能够分为:
1. 建立新数组(_jtype##Array New<_jname>Array)
2. 转换数组数据类型为基本数据类型指针(_jtype* Get<_jname>ArrayElements)
3. 释放转换的基本数据类型指针(void Release<_jname>ArrayElements)
4. 获取数组单个元素的值(_jtype GetObjectArrayElement(获取的类型只能为jobject,须要强制为本身须要的类型))
5. 设置数组单个元素的值(void SetObjectArrayElement)
简单介绍:
1. 前面已经介绍过基本数据类型和数组数据类型,这些类型都是和Java的类型对应起来的
2. 在C/C++中数组和指针在不少状况下是能够划等号的,可是在Java中是没有指针的概念的,jni夹在中间,因此就有了一个转换数组为指针的一组方法,那么相应的也有一组释放的方法。用得较多的状况应该就是把Java传过来的数组类型转换为jni的基本数据类型指针,而后根据须要强转为普通数据类型指针
3. 在C/C++中若是调用的Java方法参数为数组类型,那么咱们就须要在C/C++中使用建立新数组的接口了。这里有一个方法比较特殊(NewObjectArray),其余的接口都只能建立基本数据类型的数组,用它能够建立类类型的数组,类的获取仍是经过(FindClass)得到
4. 获取和设置数组的单个元素的值,通常须要一个一个获取或设置的数组元素的值通常都使用这方法,固然你也能够本身写个循环本身获取和设置
数组类型的方法汇总:
/*建立‘类’数组类型*/
jobjectArray NewObjectArray(jsize length, jclass elementClass, jobject initialElement)
/*建立基本类型数组,这里的类型是和Java的数组类型一一对应的*/
jbooleanArray NewBooleanArray(jsize length)
jbyteArray NewByteArray(jsize length)
jcharArray NewCharArray(jsize length)
jshortArray NewShortArray(jsize length)
jintArray NewIntArray(jsize length)
jlongArray NewLongArray(jsize length)
jfloatArray NewFloatArray(jsize length)
jdoubleArray NewDoubleArray(jsize length)
/*转换数组类型为对应的指针类型*/
jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy)
jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy)
jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy)
jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy)
jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy)
jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy)
jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy)
/*释放*/
void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems, jint mode)
void ReleaseByteArrayElements(jbyteArray array, jbyte* elems,jint mode)
void ReleaseCharArrayElements(jcharArray array, jchar* elems,jint mode)
void ReleaseShortArrayElements(jshortArray array, jshort* elems,jint mode)
void ReleaseIntArrayElements(jintArray array, jint* elems,jint mode)
void ReleaseLongArrayElements(jlongArray array, jlong* elems,jint mode)
void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems,jint mode)
void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems,jint mode)
/*获取和设置单个数组元素的值*/
jobject GetObjectArrayElement(jobjectArray array, jsize index)
void SetObjectArrayElement(jobjectArray array, jsize index, jobject value)
例子:
这个demo能够跑起来,整个流程也很简单Java上传传一个byte数组到jni,jni再调用Java的一个方法把传下来的byte数组转换为字符串显示出来。
Java代码:
package com.example.JniArrayTest;
import android.app.Activity;
import android.widget.TextView;
import android.widget.Toast;
import android.os.Bundle;
public class JniArrayTest extends Activity
{
private final static String TAG = "Java Array Test";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
byte tojniString[] = new byte[] {'s', 't', 'r', 'i', 'n', 'g'};
tv.setText(stringToJNI(tojniString));
setContentView(tv);
}
private void ComeFromeJni(String jniString)
{
Toast.makeText(getApplicationContext(), jniString, Toast.LENGTH_LONG).show();
}
private native String stringToJNI(byte javaString[]);
static {
System.loadLibrary("JniArrayTest");
}
}
Jni代码:
#include <string.h>
#include <jni.h>
#include <android/log.h>
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, "JniArrayTest", __VA_ARGS__)
extern "C" jstring
Java_com_example_JniArrayTest_JniArrayTest_stringToJNI(JNIEnv* env, jobject thiz, jcharArray javaString)
{
jchar *javaStr = env->GetCharArrayElements(javaString, NULL);
int charlenght = env->GetArrayLength(javaString);
ALOGI("string come from java %d:%s", charlenght, (const char *)javaStr);
jmethodID mid = env->GetMethodID(env->GetObjectClass(thiz), "ComeFromeJni", "(Ljava/lang/String;)V");
env->CallVoidMethod(thiz, mid, env->NewStringUTF((const char *)javaStr));
return env->NewStringUTF((const char *)javaStr);
}
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_LINK_MODE := c++
LOCAL_MODULE := JniArrayTest
LOCAL_SRC_FILES := JniArrayTest.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)