歡迎轉載,轉載請註明出處:http://www.cnblogs.com/lanrenxinxin/p/4696991.htmlhtml
開始接觸Android JNI層面的內容,推薦一本不錯的入門級的書《Android的設計與實現:卷一》,這兩天看了一下關於Java層和Native層函數映射的章節,加深對JNI的理解。java
先是寫了一個很是簡單的計算器,關鍵的運算放在Native層實現,而後把運算的結果返回到Java層,寫這個的時候仍是本身手動建jni文件夾,javah的命令行,寫makefile文件,用ndk-build命令行來編譯,後來發現要調試C代碼了,才發現高版本的ndk環境已經全都集成好了,編譯,運行,調試甚至和VS差很少方便,只是本身沒配好而已。android
下面是很是簡單的計算器源碼,只是用來熟悉JNI的基本語法,其中我本身碰到過的一個問題,就是LoadLibrary()調用以後,程序直接崩潰,最開始覺得是模擬器是x86的模式,而編譯的so文件是arm的模式,可是將模擬器改爲arm以後仍是崩潰,最後無奈在本身手機上測試也是如此,一打開就直接崩潰,在網上能找到的各類方法都試了,最後發現是so命名的問題具體能夠參考這篇博客Android Eclipse JNI 調用 .so文件加載問題 安全
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:id = "@+id/tvResult" android:layout_width="fill_parent" android:layout_height="wrap_content" android:height="40dp"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btnBackSpace" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="150dp" android:text = "@string/strbtnbackspace" /> <Button android:id="@+id/btnCE" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="150dp" android:text="@string/strbtnCE"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btn7" android:layout_width = "wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn7"/> <Button android:id="@+id/btn8" android:layout_width = "wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtn8"/> <Button android:id="@+id/btn9" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtn9"/> <Button android:id="@+id/btnADD" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnADD"/> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height = "wrap_content"> <Button android:id="@+id/btn4" android:layout_width="wrap_content" android:layout_height = "wrap_content" android:width="75dp" android:text="@string/strbtn4"/> <Button android:id="@+id/btn5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn5"/> <Button android:id="@+id/btn6" android:layout_width = "wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn6"/> <Button android:id="@+id/btnSUB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnSUB"/> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <Button android:id="@+id/btn1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn1"/> <Button android:id="@+id/btn2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn2"/> <Button android:id="@+id/btn3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width="75dp" android:text="@string/strbtn3"/> <Button android:id="@+id/btnMUL" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnMUL"/> </LinearLayout> <LinearLayout android:layout_width = "fill_parent" android:layout_height="wrap_content"> <Button android:id="@+id/btn0" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtn0"/> <Button android:id="@+id/btnC" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnC"/> <Button android:id="@+id/btnRESULT" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnRESULT"/> <Button android:id="@+id/btnDIV" android:layout_width="wrap_content" android:layout_height="wrap_content" android:width = "75dp" android:text="@string/strbtnDIV"/> </LinearLayout> </LinearLayout>
public class MainActivity extends Activity implements OnClickListener{ static{ System.loadLibrary("CalcJni"); } enum OP { NON, ADD, SUB, MUL, DIV } private TextView tvResult = null; private Button btn0 =null; private Button btn1 =null; private Button btn2 =null; private Button btn3 =null; private Button btn4 =null; private Button btn5 =null; private Button btn6 =null; private Button btn7 =null; private Button btn8 =null; private Button btn9 =null; private Button btnAdd =null; private Button btnSub =null; private Button btnMul =null; private Button btnDiv =null; private Button btnEqu =null; private Button btnBackspace=null; private Button btnCE=null; private Button btnC=null; private OP operator = OP.NON; private int num1; private int num2; private int result; private native int Add(int num1,int num2); private native int Sub(int num1,int num2); private native int Mul(int num1,int num2); private native int Div(int num1,int num2); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn0 = (Button)findViewById(R.id.btn0); btn1 = (Button)findViewById(R.id.btn1); btn2 = (Button)findViewById(R.id.btn2); btn3 = (Button)findViewById(R.id.btn3); btn4 = (Button)findViewById(R.id.btn4); btn5 = (Button)findViewById(R.id.btn5); btn6 = (Button)findViewById(R.id.btn6); btn7 = (Button)findViewById(R.id.btn7); btn8 = (Button)findViewById(R.id.btn8); btn9 = (Button)findViewById(R.id.btn9); btnAdd = (Button)findViewById(R.id.btnADD); btnSub = (Button)findViewById(R.id.btnSUB); btnMul = (Button)findViewById(R.id.btnMUL); btnDiv = (Button)findViewById(R.id.btnDIV); tvResult = (TextView)findViewById(R.id.tvResult); tvResult.setTextSize(30); tvResult.setGravity(Gravity.RIGHT); btnBackspace=(Button)findViewById(R.id.btnBackSpace); btnCE=(Button)findViewById(R.id.btnCE); btnC=(Button)findViewById(R.id.btnC); btnEqu = (Button)findViewById(R.id.btnRESULT); btnBackspace.setOnClickListener(this); btnCE.setOnClickListener(this); btn0.setOnClickListener(this); btn1.setOnClickListener(this); btn2.setOnClickListener(this); btn3.setOnClickListener(this); btn4.setOnClickListener(this); btn5.setOnClickListener(this); btn6.setOnClickListener(this); btn7.setOnClickListener(this); btn8.setOnClickListener(this); btn9.setOnClickListener(this); btnAdd.setOnClickListener(this); btnSub.setOnClickListener(this); btnMul.setOnClickListener(this); btnDiv.setOnClickListener(this); btnEqu.setOnClickListener(this); } @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.btnBackSpace: String mystr = tvResult.getText().toString(); try { tvResult.setText(mystr.substring(0, mystr.length()-1)); } catch (Exception e) { // TODO: handle exception tvResult.setText(""); } break; case R.id.btnCE: tvResult.setText(null); break; //btn 0 -- 9 case R.id.btn0: String myString0 = tvResult.getText().toString(); myString0 += "0"; tvResult.setText(myString0); break; case R.id.btn1: String myString1 = tvResult.getText().toString(); myString1 += "1"; tvResult.setText(myString1); break; case R.id.btn2: String myString2 = tvResult.getText().toString(); myString2 += "2"; tvResult.setText(myString2); break; case R.id.btn3: String myString3 = tvResult.getText().toString(); myString3 += "3"; tvResult.setText(myString3); break; case R.id.btn4: String myString4 = tvResult.getText().toString(); myString4 += "4"; tvResult.setText(myString4); break; case R.id.btn5: String myString5 = tvResult.getText().toString(); myString5 += "5"; tvResult.setText(myString5); break; case R.id.btn6: String myString6 = tvResult.getText().toString(); myString6 += "6"; tvResult.setText(myString6); break; case R.id.btn7: String myString7 = tvResult.getText().toString(); myString7 += "7"; tvResult.setText(myString7); break; case R.id.btn8: String myString8 = tvResult.getText().toString(); myString8 += "8"; tvResult.setText(myString8); break; case R.id.btn9: String myString9 = tvResult.getText().toString(); myString9 += "9"; tvResult.setText(myString9); break; //+-*/ case R.id.btnADD: String myAddString = tvResult.getText().toString(); if (myAddString.equals(null)) { return; } num1 = Integer.valueOf(myAddString); tvResult.setText(null); operator = OP.ADD; break; case R.id.btnSUB: String mySubString = tvResult.getText().toString(); if (mySubString.equals(null)) { return; } num1 = Integer.valueOf(mySubString); tvResult.setText(null); operator = OP.SUB; break; case R.id.btnMUL: String myMulString = tvResult.getText().toString(); if (myMulString.equals(null)) { return; } num1 = Integer.valueOf(myMulString); tvResult.setText(null); operator = OP.MUL; break; case R.id.btnDIV: String myDivString = tvResult.getText().toString(); if (myDivString.equals(null)) { return; } num1 = Integer.valueOf(myDivString); tvResult.setText(null); operator = OP.DIV; break; case R.id.btnRESULT: String myResultString = tvResult.getText().toString(); if(myResultString.equals(null)){ return; } num2 = Integer.valueOf(myResultString); switch (operator) { case ADD: result = Add(num1, num2); break; case SUB: result = Sub(num1, num2); break; case MUL: result = Mul(num1, num2); break; case DIV: result = Div(num1, num2); break; default: break; } tvResult.setText(Integer.toString(result)); break; default: break; } } }
JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Add (JNIEnv * env, jobject obj, jint num1, jint num2) { return (jint)(num1+num2); } JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Sub (JNIEnv * env, jobject obj , jint num1, jint num2) { return (jint)(num1-num2); } JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Mul (JNIEnv * env, jobject obj, jint num1, jint num2) { return (jint)(num1*num2); } JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Div (JNIEnv * env, jobject obj, jint num1, jint num2) { if(num2==0) return 0; return (jint)(num1/num2); }
咱們常常會寫以下的代碼輸出日誌:app
Log.d(TAG,」Debug Log」);eclipse
咱們就以Log系統爲例來學習JNI。ide
咱們先看一下Log類的內容,在android源碼的\frameworks\base\core\java\android\Log.java文件中函數
/** * Send a {@link #DEBUG} log message. * @param tag Used to identify the source of a log message. It usually identifies * the class or activity where the log call occurs. * @param msg The message you would like logged. */ public static int d(String tag, String msg) { return println_native(LOG_ID_MAIN, DEBUG, tag, msg); } /** @hide */ public static final int LOG_ID_MAIN = 0; /** @hide */ public static final int LOG_ID_RADIO = 1; /** @hide */ public static final int LOG_ID_EVENTS = 2; /** @hide */ public static final int LOG_ID_SYSTEM = 3; /** @hide */ public static native int println_native(int bufID, int priority, String tag, String msg);
能夠看到全部的Log的方法都調用了native 的println_native方法,在android源碼中的\frameworks\base\core\jni\android_until_Log.cpp文件中實現:工具
/* * In class android.util.Log: * public static native int println_native(int buffer, int priority, String tag, String msg) */ /* *JNI方法增長了JNIEnv和jobject兩參數,其他的參數和返回值只是將Java層參數映**射成JNI的數據類型,而後經過調用本地庫和JNIEnv提供的JNI函數處理數據,最後返給java層 */ static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, jint bufID, jint priority, jstring tagObj, jstring msgObj) { const char* tag = NULL; const char* msg = NULL; if (msgObj == NULL) { //異常處理 jclass npeClazz; npeClazz = env->FindClass("java/lang/NullPointerException"); assert(npeClazz != NULL); //拋出異常 env->ThrowNew(npeClazz, "println needs a message"); return -1; } if (bufID < 0 || bufID >= LOG_ID_MAX) { jclass npeClazz; npeClazz = env->FindClass("java/lang/NullPointerException"); assert(npeClazz != NULL); env->ThrowNew(npeClazz, "bad bufID"); return -1; } if (tagObj != NULL) tag = env->GetStringUTFChars(tagObj, NULL); msg = env->GetStringUTFChars(msgObj, NULL); //向內核寫入日誌 int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); if (tag != NULL) env->ReleaseStringUTFChars(tagObj, tag); env->ReleaseStringUTFChars(msgObj, msg); return res; }
至此,JNI層已經實現了在java層聲明的Native層方法,可是這兩個又是如何聯繫到一塊兒的呢?咱們再看android_util_Log.cpp的源碼學習
/* * JNI registration. */ static JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable }, {"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native }, };
在\dalvik\libnativehelper\include\nativehelper\Jni.h文件中有JNINativeMethod 的定義:
typedef struct { const char* name; //java層聲明的native函數的函數名 const char* signature; //Java函數的簽名 void* fnPtr; //函數指針,指向JNI層的實現方法 } JNINativeMethod;
咱們能夠看到printIn_native的對應關係:
{"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native }
Java層聲明的函數名是print_native
Java層聲明的native函數的簽名爲(IILjava/lang/String;Ljava/lang/String;)I
JNI方法實現方法的指針爲(void*)android_util_Log_println_native
咱們知道了java層和JNI層的映射關係,可是如何把這種關係告訴Dalvik虛擬機呢?,咱們繼續看android_util_Log.cpp的源碼
int register_android_util_Log(JNIEnv* env) { jclass clazz = env->FindClass("android/util/Log"); if (clazz == NULL) { LOGE("Can't find android/util/Log"); return -1; } levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I")); levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I")); levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I")); levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I")); levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I")); levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I")); return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods)); } }; // namespace android
這個函數的最後調用了AndroidRuntime::registerNativeMethods函數
能夠在\frameworks\base\core\jni\AndroidRuntime.cpp 中找到registerNativeMethods的實現
/* * Register native methods using JNI. */ /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { return jniRegisterNativeMethods(env, className, gMethods, numMethods); }
他的內部實現只是調用了jniRegisterNativeMethods ()。
在\dalvik\libnativehelper\JNIHelp.c中jniRegisterNativeMethods函數的實現
/* * Register native JNI-callable methods. * * "className" looks like "java/lang/String". */ int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { jclass clazz; LOGV("Registering %s natives\n", className); clazz = (*env)->FindClass(env, className); if (clazz == NULL) { LOGE("Native registration unable to find class '%s'\n", className); return -1; } int result = 0; if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { LOGE("RegisterNatives failed for '%s'\n", className); result = -1; } (*env)->DeleteLocalRef(env, clazz); return result; }
這裏是調用了JNIEnv的RegisterNatives函數,能夠閱讀函數的註釋,註冊一個類的Native方法。已經告訴了虛擬機java層和native層的映射關係。
/* * Register one or more native functions in one class. * * This can be called multiple times on the same method, allowing the * caller to redefine the method implementation at will. */ static jint RegisterNatives(JNIEnv* env, jclass jclazz, const JNINativeMethod* methods, jint nMethods) { JNI_ENTER(); ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz); jint retval = JNI_OK; int i; if (gDvm.verboseJni) { LOGI("[Registering JNI native methods for class %s]\n", clazz->descriptor); } for (i = 0; i < nMethods; i++) { if (!dvmRegisterJNIMethod(clazz, methods[i].name, methods[i].signature, methods[i].fnPtr)) { retval = JNI_ERR; } } JNI_EXIT(); return retval; }
其做用是向clazz參數指定的類註冊本地方法,這樣,虛擬機就能獲得Java層和JNI層之間的對應關係,就能夠實現java和native層代碼的交互了。咱們注意到在Log系統的實例中,JNI層實現方法和註冊方法中都使用了JNIEnv這個指針,經過它調用JNI函數,訪問Dalvik虛擬機,進而操做Java對象。
咱們能夠在\Dalvik\libnativehelper\include\nativehelper\jni.h中找到JNIEnv的定義:
struct _JNIEnv; struct _JavaVM; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) //定義了C++ typedef _JNIEnv JNIEnv; //C++中的JNIEnv的類型 typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv; typedef const struct JNIInvokeInterface* JavaVM; #endif
這裏只是用關鍵字typedef關鍵字作了類型定義,那麼_JNIEnv和JNINativeInterface的定義
/* * C++ object wrapper. * * This is usually overlaid on a C struct whose first element is a * JNINativeInterface*. We rely somewhat on compiler behavior. */ struct _JNIEnv { /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* 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); } jmethodID FromReflectedMethod(jobject method) { return functions->FromReflectedMethod(this, method); } ………..
_JNIEnv只是對const struct JNINativeInterface*類型的封裝,並間接調用const struct JNINativeInterface*上定義的方法
/* * Table of interface function pointers. */ struct JNINativeInterface { …… jclass (*FindClass)(JNIEnv*, const char*); jboolean (*IsSameObject)(JNIEnv*, jobject, jobject); …… };
這裏才真正涉及JNI函數的調用,也只是一個接口,具體的實現要參考Dalvik虛擬機。
可是咱們能夠得出以下結論:
C++中:JNIEnv就是struct _JNIEnv。JNIEnv *env 等價於 struct _JNIEnv *env ,在調用JNI函數的時候,只須要env->FindClass(JNIEnv*,const char ),就會間接調用JNINativeInterface結構體裏面定義的函數指針,而無需首先對env解引用。
C中:JNIEnv就是const struct JNINativeInterface *。JNIEnv *env 等價於const struct JNINativeInterface ** env,所以要獲得JNINativeInterface結構體裏面的函數指針就必須先對env解引用獲得(*env),獲得const struct JNINativeInterface *,纔是真正指向JNINativeInterface結構體的指針,而後再經過它調用具體的JNI函數,所以須要這樣調用:
(*env)->FindClass(JNIEnv*,const char*)。
接下來了解關於Jni和java層數據類型的關係,Jni.h文件中關於基本數據類型的定義
/* * Primitive types that match up with Java equivalents. */ #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
關於一些返回狀態值的定義:
#define JNI_FALSE 0 #define JNI_TRUE 1 #define JNI_OK (0) /* no error */ #define JNI_ERR (-1) /* generic error */ #define JNI_EDETACHED (-2) /* thread detached from the VM*/ #define JNI_EVERSION (-3) /* JNI version error */ #define JNI_COMMIT 1 /* copy content, do not free buffer */ #define JNI_ABORT 2 /* free buffer w/o copying back */
JNI引用類型採用了與Java類型類似的繼承關係,樹根是Jobject
下面是Jni.h中關於引用類型的定義,在C++中全都繼承自class jobjct{};而C中都是void*的指針。
#ifdef __cplusplus /* * Reference types, in C++ */ class _jobject {}; class _jclass : public _jobject {}; class _jstring : public _jobject {}; class _jarray : public _jobject {}; class _jobjectArray : public _jarray {}; //java層 object[] class _jbooleanArray : public _jarray {}; //java層 boolean[] class _jbyteArray : public _jarray {}; //byte[] class _jcharArray : public _jarray {}; //char[] class _jshortArray : public _jarray {}; //short[] class _jintArray : public _jarray {}; //in[] 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接口指針值JNI實現方法的第一個參數,其類型是JNIEnv。第二個參數因本地方法是靜態仍是非靜態而不一樣,非靜態本地方法的第二個參數是對Java對象的引用,而靜態本地方法的第二個參數是對其java類的引用,其他的參數都對應與java方法的參數。能夠藉助javah 工具來生成對應的native函數聲明。
而在Java層和native層都是支持函數重載,僅僅依靠函數名沒法肯定惟一的一個方法,因此JNI提供了一套簽名規則,用一串字符串來惟一肯定一個方法:
(參數1類型簽名 參數2類型簽名……參數n類型簽名)返回值類型
和smali語言中的規則同樣,就不加以贅述了,能夠參考非蟲的《Android軟件安全與逆向分析》中的相關章節或者這篇文章smali語法文檔,只簡單舉個例子。
仍是以咱們以前的println_native爲例:
Java層的聲明 public static native int println_native(int buffer, int priority, String tag, String msg) ;
對應的簽名就是 (IILjava/lang/String;Ljava/lang/String;)I
至此咱們實現的JNI層方法和java層聲明的方法創建的惟一的映射關係。
接下來咱們繼續學習在JNI層訪問java層對象,在JNI層操做jobject,就是要訪問這個對象並操做它的變量和方法,咱們經常使用的兩個JNI函數FindClass() 和 GetObjectClass():
C++中的函數原型:
jclass FindClass(const char* name);
class GetObjectClass(jobject obj);
C中的函數原型:
jclass (*FindClass)(JNIEnv*,const char* name);
class (*GetObjectClass)(JNIEnv*,jobject obj);
經過給FindClass傳入要查找類的全限定類名(以」/」分隔路徑),返回一個jclass的對象,這樣就能夠操做這個類的方法和變量了。
下面是一個特別簡單的例子,點擊button之後,調用native層的getReply()方法,而後在native層getReply()方法的實現中反向調用java層的callBack()方法,輸入日誌。
public class MainActivity extends Activity { static{ System.loadLibrary("NewJni"); } private String TAG = "CCDebug"; private Button btnButton = null; private native String getReply(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnButton = (Button)findViewById(R.id.btn1); btnButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Log.d(TAG, getReply()); } }); } private void callBack() { Log.d(TAG, "call back form native !"); throw new NullPointerException(); } }
#include <jni.h> #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_com_example_newjni_MainActivity_getReply (JNIEnv * env, jobject obj); JNIEXPORT jstring JNICALL Java_com_example_newjni_MainActivity_getReply (JNIEnv * env, jobject obj) { jclass jcls = env->GetObjectClass(obj); jmethodID jmId = env->GetMethodID(jcls,"callBack","()V"); env->CallVoidMethod(obj,jmId); if(env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); } return env->NewStringUTF("Hello From JNI!"); }
這是利用javah生成的函數聲明,嚴格遵照NDk的語法要求,固然,咱們本身也能夠像Log系統那樣,本身註冊函數的映射關係而沒必要遵照NDK語法,下面就是將getReply()函數手動註冊的例子,可是手動註冊我本身目前還存在幾個問題:
1. native的代碼始終不能下斷點到JNI_Onload()函數中
2. 第一次點擊Button,native層代碼沒有響應,必須是第二次點擊纔會響應
public class MainActivity extends Activity { private static final String TAG = "CCDebug"; Button btnButton = null; private native String getReply(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnButton = (Button)findViewById(R.id.btn); btnButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub loadLibrary("jniException"); Log.d(TAG, getReply()); } }); } private void callBack() { Log.d(TAG, "call back form native !"); throw new NullPointerException(); } /* * 若是在onClick()函數中直接調用System.loadLibrary(),在調試native代碼時會出現 * No symbol table is loaded. Use the "file" command. * 而不能調試native源代碼 */ public static void loadLibrary(String libName) { System.loadLibrary(libName); } }
#include <jni.h> #include <string.h> #ifdef __cplusplus extern "C" { #endif JNIEXPORT jstring JNICALL MyFunc (JNIEnv *env, jobject obj); static int registerNativeMethods(JNIEnv* env,const char *className, JNINativeMethod* gMethods,int numMethods); static int registerNatives(JNIEnv *env); #ifdef __cplusplus } #endif #define LOGD(msg) \ __android_log_write(ANDROID_LOG_ERROR,"CCDebug",msg); JNIEXPORT jstring JNICALL MyFunc (JNIEnv *env, jobject obj) { /* * 經過JNI函數GetObjectClass獲得傳入對象的類信息 * 這裏傳入的對象就是調用Native方法的那個對象 */ jclass jcls = env->GetObjectClass(obj); //根據類信息獲得callback方法的jmethodID jmethodID jmId = env->GetMethodID(jcls,"callBack","()V"); //調用callback方法 env->CallVoidMethod(obj,jmId); /* * 若是檢查是否有異常發生 * 若是有異常發生就處理,不然異常將會拋給java層的callback方法 */ if(env->ExceptionCheck()) //檢查異常 { env->ExceptionDescribe(); env->ExceptionClear(); //清除異常 } return env->NewStringUTF("Show Message Form JNI!"); } static JNINativeMethod gmethods[] = { { "getReply", "()Ljava/lang/String;", (void*)MyFunc }, }; static int registerNativeMethods(JNIEnv* env,const char *className, JNINativeMethod* gMethods,int numMethods) { jclass clazz; clazz = env->FindClass(className); if(clazz == NULL) { return JNI_FALSE; } //調用JNIEnv提供的註冊函數向虛擬機註冊 if(env->RegisterNatives(clazz,gMethods,numMethods)<0) { return JNI_FALSE; } return JNI_TRUE; } static int registerNatives(JNIEnv *env) { if (!registerNativeMethods(env,"com/example/jniexception/MainActivity",gmethods,sizeof(gmethods)/sizeof(gmethods[0]))) { return JNI_FALSE; } return JNI_TRUE; } jint JNI_OnLoad(JavaVM* vm, void* reserved) { jint result = -1; JNIEnv* env = NULL; if (vm->GetEnv((void**)&env,JNI_VERSION_1_4)) { return result; } if (registerNatives(env)!=JNI_TRUE) { return result; } result = JNI_VERSION_1_4; return result; }
總結:這兩天收穫仍是很大的,儘管中間也遇到了諸多的問題,網上能找到的答案也不盡然,很感謝那些給出了辦法解決了我問題的人,下面附上我這兩天發現的幾篇我以爲很好的文章: