這篇筆記是我半年前寫的,當時由於某些緣由常常須要寫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)