欢迎转载,转载请注明出处:http://www.cnblogs.com/lanrenxinxin/p/4696991.html

开始接触Android JNI层面的内容,推荐一本不错的入门级的书《Android的设计与实现:卷一》,这两天看了一下关于Java层和Native层函数映射的章节,加深对JNI的理解。

先是写了一个非常简单的计算器,关键的运算放在Native层实现,然后把运算的结果返回到Java层,写这个的时候还是自己手动建jni文件夹,javah的命令行,写makefile文件,用ndk-build命令行来编译,后来发现要调试C代码了,才发现高版本的ndk环境已经全都集成好了,编译,运行,调试甚至和VS差不多方便,只是自己没配好而已。

下面是非常简单的计算器源码,只是用来熟悉JNI的基本语法,其中我自己碰到过的一个问题,就是LoadLibrary()调用之后,程序直接崩溃,最开始以为是模拟器是x86的模式,而编译的so文件是arm的模式,但是将模拟器改成arm之后还是崩溃,最后无奈在自己手机上测试也是如此,一打开就直接崩溃,在网上能找到的各种方法都试了,最后发现是so命名的问题具体可以参考这篇博客Android Eclipse JNI 调用 .so文件加载问题

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:orientation="vertical"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5.  
  6. <LinearLayout
  7. android:layout_width="fill_parent"
  8. android:layout_height="wrap_content">
  9. <TextView
  10. android:id = "@+id/tvResult"
  11. android:layout_width="fill_parent"
  12. android:layout_height="wrap_content"
  13. android:height="40dp"/>
  14. </LinearLayout>
  15.  
  16. <LinearLayout
  17. android:layout_width="fill_parent"
  18. android:layout_height="wrap_content">
  19. <Button
  20. android:id="@+id/btnBackSpace"
  21. android:layout_width="wrap_content"
  22. android:layout_height="wrap_content"
  23. android:width="150dp"
  24. android:text = "@string/strbtnbackspace" />
  25. <Button
  26. android:id="@+id/btnCE"
  27. android:layout_width="wrap_content"
  28. android:layout_height="wrap_content"
  29. android:width="150dp"
  30. android:text="@string/strbtnCE"/>
  31. </LinearLayout>
  32.  
  33. <LinearLayout
  34. android:layout_width="fill_parent"
  35. android:layout_height="wrap_content">
  36. <Button
  37. android:id="@+id/btn7"
  38. android:layout_width = "wrap_content"
  39. android:layout_height="wrap_content"
  40. android:width="75dp"
  41. android:text="@string/strbtn7"/>
  42. <Button
  43. android:id="@+id/btn8"
  44. android:layout_width = "wrap_content"
  45. android:layout_height="wrap_content"
  46. android:width = "75dp"
  47. android:text="@string/strbtn8"/>
  48. <Button
  49. android:id="@+id/btn9"
  50. android:layout_width="wrap_content"
  51. android:layout_height="wrap_content"
  52. android:width = "75dp"
  53. android:text="@string/strbtn9"/>
  54. <Button
  55. android:id="@+id/btnADD"
  56. android:layout_width="wrap_content"
  57. android:layout_height="wrap_content"
  58. android:width = "75dp"
  59. android:text="@string/strbtnADD"/>
  60.  
  61. </LinearLayout>
  62.  
  63. <LinearLayout
  64. android:layout_width="fill_parent"
  65. android:layout_height = "wrap_content">
  66. <Button
  67. android:id="@+id/btn4"
  68. android:layout_width="wrap_content"
  69. android:layout_height = "wrap_content"
  70. android:width="75dp"
  71. android:text="@string/strbtn4"/>
  72. <Button
  73. android:id="@+id/btn5"
  74. android:layout_width="wrap_content"
  75. android:layout_height="wrap_content"
  76. android:width="75dp"
  77. android:text="@string/strbtn5"/>
  78. <Button
  79. android:id="@+id/btn6"
  80. android:layout_width = "wrap_content"
  81. android:layout_height="wrap_content"
  82. android:width="75dp"
  83. android:text="@string/strbtn6"/>
  84. <Button
  85. android:id="@+id/btnSUB"
  86. android:layout_width="wrap_content"
  87. android:layout_height="wrap_content"
  88. android:width = "75dp"
  89. android:text="@string/strbtnSUB"/>
  90.  
  91. </LinearLayout>
  92.  
  93. <LinearLayout
  94. android:layout_width="wrap_content"
  95. android:layout_height="wrap_content">
  96.  
  97. <Button
  98. android:id="@+id/btn1"
  99. android:layout_width="wrap_content"
  100. android:layout_height="wrap_content"
  101. android:width="75dp"
  102. android:text="@string/strbtn1"/>
  103. <Button
  104. android:id="@+id/btn2"
  105. android:layout_width="wrap_content"
  106. android:layout_height="wrap_content"
  107. android:width="75dp"
  108. android:text="@string/strbtn2"/>
  109. <Button
  110. android:id="@+id/btn3"
  111. android:layout_width="wrap_content"
  112. android:layout_height="wrap_content"
  113. android:width="75dp"
  114. android:text="@string/strbtn3"/>
  115. <Button
  116. android:id="@+id/btnMUL"
  117. android:layout_width="wrap_content"
  118. android:layout_height="wrap_content"
  119. android:width = "75dp"
  120. android:text="@string/strbtnMUL"/>
  121. </LinearLayout>
  122.  
  123. <LinearLayout
  124. android:layout_width = "fill_parent"
  125. android:layout_height="wrap_content">
  126. <Button
  127. android:id="@+id/btn0"
  128. android:layout_width="wrap_content"
  129. android:layout_height="wrap_content"
  130. android:width = "75dp"
  131. android:text="@string/strbtn0"/>
  132. <Button
  133. android:id="@+id/btnC"
  134. android:layout_width="wrap_content"
  135. android:layout_height="wrap_content"
  136. android:width = "75dp"
  137. android:text="@string/strbtnC"/>
  138. <Button
  139. android:id="@+id/btnRESULT"
  140. android:layout_width="wrap_content"
  141. android:layout_height="wrap_content"
  142. android:width = "75dp"
  143. android:text="@string/strbtnRESULT"/>
  144. <Button
  145. android:id="@+id/btnDIV"
  146. android:layout_width="wrap_content"
  147. android:layout_height="wrap_content"
  148. android:width = "75dp"
  149. android:text="@string/strbtnDIV"/>
  150. </LinearLayout>
  151. </LinearLayout>

calc xml

  1. public class MainActivity extends Activity implements OnClickListener{
  2.  
  3. static{
  4.  
  5. System.loadLibrary("CalcJni");
  6. }
  7. enum OP
  8. {
  9. NON,
  10. ADD,
  11. SUB,
  12. MUL,
  13. DIV
  14. }
  15. private TextView tvResult = null;
  16. private Button btn0 =null;
  17. private Button btn1 =null;
  18. private Button btn2 =null;
  19. private Button btn3 =null;
  20. private Button btn4 =null;
  21. private Button btn5 =null;
  22. private Button btn6 =null;
  23. private Button btn7 =null;
  24. private Button btn8 =null;
  25. private Button btn9 =null;
  26. private Button btnAdd =null;
  27. private Button btnSub =null;
  28. private Button btnMul =null;
  29. private Button btnDiv =null;
  30. private Button btnEqu =null;
  31. private Button btnBackspace=null;
  32. private Button btnCE=null;
  33. private Button btnC=null;
  34. private OP operator = OP.NON;
  35.  
  36. private int num1;
  37. private int num2;
  38. private int result;
  39.  
  40. private native int Add(int num1,int num2);
  41. private native int Sub(int num1,int num2);
  42. private native int Mul(int num1,int num2);
  43. private native int Div(int num1,int num2);
  44.  
  45. @Override
  46. protected void onCreate(Bundle savedInstanceState) {
  47. super.onCreate(savedInstanceState);
  48. setContentView(R.layout.activity_main);
  49. btn0 = (Button)findViewById(R.id.btn0);
  50. btn1 = (Button)findViewById(R.id.btn1);
  51. btn2 = (Button)findViewById(R.id.btn2);
  52. btn3 = (Button)findViewById(R.id.btn3);
  53. btn4 = (Button)findViewById(R.id.btn4);
  54. btn5 = (Button)findViewById(R.id.btn5);
  55. btn6 = (Button)findViewById(R.id.btn6);
  56. btn7 = (Button)findViewById(R.id.btn7);
  57. btn8 = (Button)findViewById(R.id.btn8);
  58. btn9 = (Button)findViewById(R.id.btn9);
  59. btnAdd = (Button)findViewById(R.id.btnADD);
  60. btnSub = (Button)findViewById(R.id.btnSUB);
  61. btnMul = (Button)findViewById(R.id.btnMUL);
  62. btnDiv = (Button)findViewById(R.id.btnDIV);
  63. tvResult = (TextView)findViewById(R.id.tvResult);
  64. tvResult.setTextSize(30);
  65. tvResult.setGravity(Gravity.RIGHT);
  66. btnBackspace=(Button)findViewById(R.id.btnBackSpace);
  67. btnCE=(Button)findViewById(R.id.btnCE);
  68. btnC=(Button)findViewById(R.id.btnC);
  69. btnEqu = (Button)findViewById(R.id.btnRESULT);
  70.  
  71. btnBackspace.setOnClickListener(this);
  72. btnCE.setOnClickListener(this);
  73. btn0.setOnClickListener(this);
  74. btn1.setOnClickListener(this);
  75. btn2.setOnClickListener(this);
  76. btn3.setOnClickListener(this);
  77. btn4.setOnClickListener(this);
  78. btn5.setOnClickListener(this);
  79. btn6.setOnClickListener(this);
  80. btn7.setOnClickListener(this);
  81. btn8.setOnClickListener(this);
  82. btn9.setOnClickListener(this);
  83.  
  84. btnAdd.setOnClickListener(this);
  85. btnSub.setOnClickListener(this);
  86. btnMul.setOnClickListener(this);
  87. btnDiv.setOnClickListener(this);
  88. btnEqu.setOnClickListener(this);
  89. }
  90.  
  91. @Override
  92. public void onClick(View v) {
  93. // TODO Auto-generated method stub
  94. switch (v.getId()) {
  95. case R.id.btnBackSpace:
  96. String mystr = tvResult.getText().toString();
  97. try {
  98. tvResult.setText(mystr.substring(0, mystr.length()-1));
  99. } catch (Exception e) {
  100. // TODO: handle exception
  101. tvResult.setText("");
  102. }
  103. break;
  104. case R.id.btnCE:
  105. tvResult.setText(null);
  106. break;
  107. //btn 0 -- 9
  108. case R.id.btn0:
  109. String myString0 = tvResult.getText().toString();
  110. myString0 += "0";
  111. tvResult.setText(myString0);
  112. break;
  113. case R.id.btn1:
  114. String myString1 = tvResult.getText().toString();
  115. myString1 += "1";
  116. tvResult.setText(myString1);
  117. break;
  118. case R.id.btn2:
  119. String myString2 = tvResult.getText().toString();
  120. myString2 += "2";
  121. tvResult.setText(myString2);
  122. break;
  123. case R.id.btn3:
  124. String myString3 = tvResult.getText().toString();
  125. myString3 += "3";
  126. tvResult.setText(myString3);
  127. break;
  128. case R.id.btn4:
  129. String myString4 = tvResult.getText().toString();
  130. myString4 += "4";
  131. tvResult.setText(myString4);
  132. break;
  133. case R.id.btn5:
  134. String myString5 = tvResult.getText().toString();
  135. myString5 += "5";
  136. tvResult.setText(myString5);
  137. break;
  138. case R.id.btn6:
  139. String myString6 = tvResult.getText().toString();
  140. myString6 += "6";
  141. tvResult.setText(myString6);
  142. break;
  143. case R.id.btn7:
  144. String myString7 = tvResult.getText().toString();
  145. myString7 += "7";
  146. tvResult.setText(myString7);
  147. break;
  148. case R.id.btn8:
  149. String myString8 = tvResult.getText().toString();
  150. myString8 += "8";
  151. tvResult.setText(myString8);
  152. break;
  153. case R.id.btn9:
  154. String myString9 = tvResult.getText().toString();
  155. myString9 += "9";
  156. tvResult.setText(myString9);
  157. break;
  158.  
  159. //+-*/
  160. case R.id.btnADD:
  161. String myAddString = tvResult.getText().toString();
  162. if (myAddString.equals(null)) {
  163. return;
  164. }
  165. num1 = Integer.valueOf(myAddString);
  166. tvResult.setText(null);
  167. operator = OP.ADD;
  168.  
  169. break;
  170. case R.id.btnSUB:
  171. String mySubString = tvResult.getText().toString();
  172. if (mySubString.equals(null)) {
  173. return;
  174. }
  175. num1 = Integer.valueOf(mySubString);
  176. tvResult.setText(null);
  177. operator = OP.SUB;
  178. break;
  179. case R.id.btnMUL:
  180. String myMulString = tvResult.getText().toString();
  181. if (myMulString.equals(null)) {
  182. return;
  183. }
  184. num1 = Integer.valueOf(myMulString);
  185. tvResult.setText(null);
  186. operator = OP.MUL;
  187. break;
  188. case R.id.btnDIV:
  189. String myDivString = tvResult.getText().toString();
  190. if (myDivString.equals(null)) {
  191. return;
  192. }
  193. num1 = Integer.valueOf(myDivString);
  194. tvResult.setText(null);
  195. operator = OP.DIV;
  196. break;
  197.  
  198. case R.id.btnRESULT:
  199. String myResultString = tvResult.getText().toString();
  200. if(myResultString.equals(null)){
  201. return;
  202. }
  203. num2 = Integer.valueOf(myResultString);
  204. switch (operator) {
  205. case ADD:
  206. result = Add(num1, num2);
  207. break;
  208. case SUB:
  209. result = Sub(num1, num2);
  210. break;
  211. case MUL:
  212. result = Mul(num1, num2);
  213. break;
  214. case DIV:
  215. result = Div(num1, num2);
  216. break;
  217.  
  218. default:
  219. break;
  220. }
  221. tvResult.setText(Integer.toString(result));
  222. break;
  223. default:
  224. break;
  225. }
  226.  
  227. }
  228. }

calc java

  1. JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Add
  2. (JNIEnv * env, jobject obj, jint num1, jint num2)
  3. {
  4.  
  5. return (jint)(num1+num2);
  6.  
  7. }
  8.  
  9. JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Sub
  10. (JNIEnv * env, jobject obj , jint num1, jint num2)
  11. {
  12. return (jint)(num1-num2);
  13. }
  14.  
  15. JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Mul
  16. (JNIEnv * env, jobject obj, jint num1, jint num2)
  17. {
  18. return (jint)(num1*num2);
  19. }
  20.  
  21. JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Div
  22. (JNIEnv * env, jobject obj, jint num1, jint num2)
  23. {
  24. if(num2==) return ;
  25. return (jint)(num1/num2);
  26. }

calc native

我们经常会写如下的代码输出日志:

Log.d(TAG,”Debug Log”);

我们就以Log系统为例来学习JNI。

我们先看一下Log类的内容,在android源码的\frameworks\base\core\java\android\Log.java文件中

  1. /**
  2. * Send a {@link #DEBUG} log message.
  3. * @param tag Used to identify the source of a log message. It usually identifies
  4. * the class or activity where the log call occurs.
  5. * @param msg The message you would like logged.
  6. */
  7. public static int d(String tag, String msg) {
  8. return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
  9. }
  10.  
  11. /** @hide */ public static final int LOG_ID_MAIN = 0;
  12. /** @hide */ public static final int LOG_ID_RADIO = 1;
  13. /** @hide */ public static final int LOG_ID_EVENTS = 2;
  14. /** @hide */ public static final int LOG_ID_SYSTEM = 3;
  15.  
  16. /** @hide */ public static native int println_native(int bufID,
  17. int priority, String tag, String msg);

可以看到所有的Log的方法都调用了native 的println_native方法,在android源码中的\frameworks\base\core\jni\android_until_Log.cpp文件中实现:

  1. /*
  2. * In class android.util.Log:
  3. * public static native int println_native(int buffer, int priority, String tag, String msg)
  4. */
  5. /*
  6. *JNI方法增加了JNIEnv和jobject两参数,其余的参数和返回值只是将Java层参数映**射成JNI的数据类型,然后通过调用本地库和JNIEnv提供的JNI函数处理数据,最后返给java层
  7. */
  8. static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
  9. jint bufID, jint priority, jstring tagObj, jstring msgObj)
  10. {
  11. const char* tag = NULL;
  12. const char* msg = NULL;
  13.  
  14. if (msgObj == NULL) { //异常处理
  15. jclass npeClazz;
  16.  
  17. npeClazz = env->FindClass("java/lang/NullPointerException");
  18. assert(npeClazz != NULL);
  19. //抛出异常
  20. env->ThrowNew(npeClazz, "println needs a message");
  21. return -;
  22. }
  23.  
  24. if (bufID < || bufID >= LOG_ID_MAX) {
  25. jclass npeClazz;
  26.  
  27. npeClazz = env->FindClass("java/lang/NullPointerException");
  28. assert(npeClazz != NULL);
  29.  
  30. env->ThrowNew(npeClazz, "bad bufID");
  31. return -;
  32. }
  33.  
  34. if (tagObj != NULL)
  35. tag = env->GetStringUTFChars(tagObj, NULL);
  36. msg = env->GetStringUTFChars(msgObj, NULL);
  37. //向内核写入日志
  38. int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
  39.  
  40. if (tag != NULL)
  41. env->ReleaseStringUTFChars(tagObj, tag);
  42. env->ReleaseStringUTFChars(msgObj, msg);
  43.  
  44. return res;
  45. }

至此,JNI层已经实现了在java层声明的Native层方法,但是这两个又是如何联系到一起的呢?我们再看android_util_Log.cpp的源码

  1. /*
  2. * JNI registration.
  3. */
  4. static JNINativeMethod gMethods[] = {
  5. /* name, signature, funcPtr */
  6. { "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
  7. {"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native },
  8. };

在\dalvik\libnativehelper\include\nativehelper\Jni.h文件中有JNINativeMethod 的定义:

  1. typedef struct {
  2. const char* name; //java层声明的native函数的函数名
  3. const char* signature; //Java函数的签名
  4. void* fnPtr; //函数指针,指向JNI层的实现方法
  5. } JNINativeMethod;

我们可以看到printIn_native的对应关系:

  1. {"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的源码

  1. int register_android_util_Log(JNIEnv* env)
  2. {
  3. jclass clazz = env->FindClass("android/util/Log");
  4.  
  5. if (clazz == NULL) {
  6. LOGE("Can't find android/util/Log");
  7. return -;
  8. }
  9.  
  10. levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
  11. levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
  12. levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
  13. levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
  14. levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
  15. levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
  16.  
  17. return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
  18. }
  19.  
  20. }; // namespace android

这个函数的最后调用了AndroidRuntime::registerNativeMethods函数

可以在\frameworks\base\core\jni\AndroidRuntime.cpp 中找到registerNativeMethods的实现

  1. /*
  2. * Register native methods using JNI.
  3. */
  4. /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
  5. const char* className, const JNINativeMethod* gMethods, int numMethods)
  6. {
  7. return jniRegisterNativeMethods(env, className, gMethods, numMethods);
  8. }

他的内部实现只是调用了jniRegisterNativeMethods ()。

在\dalvik\libnativehelper\JNIHelp.c中jniRegisterNativeMethods函数的实现

  1. /*
  2. * Register native JNI-callable methods.
  3. *
  4. * "className" looks like "java/lang/String".
  5. */
  6. int jniRegisterNativeMethods(JNIEnv* env, const char* className,
  7. const JNINativeMethod* gMethods, int numMethods)
  8. {
  9. jclass clazz;
  10.  
  11. LOGV("Registering %s natives\n", className);
  12. clazz = (*env)->FindClass(env, className);
  13. if (clazz == NULL) {
  14. LOGE("Native registration unable to find class '%s'\n", className);
  15. return -;
  16. }
  17.  
  18. int result = ;
  19. if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < ) {
  20. LOGE("RegisterNatives failed for '%s'\n", className);
  21. result = -;
  22. }
  23.  
  24. (*env)->DeleteLocalRef(env, clazz);
  25. return result;
  26. }

这里是调用了JNIEnv的RegisterNatives函数,可以阅读函数的注释,注册一个类的Native方法。已经告诉了虚拟机java层和native层的映射关系。

  1. /*
  2. * Register one or more native functions in one class.
  3. *
  4. * This can be called multiple times on the same method, allowing the
  5. * caller to redefine the method implementation at will.
  6. */
  7. static jint RegisterNatives(JNIEnv* env, jclass jclazz,
  8. const JNINativeMethod* methods, jint nMethods)
  9. {
  10. JNI_ENTER();
  11.  
  12. ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);
  13. jint retval = JNI_OK;
  14. int i;
  15.  
  16. if (gDvm.verboseJni) {
  17. LOGI("[Registering JNI native methods for class %s]\n",
  18. clazz->descriptor);
  19. }
  20.  
  21. for (i = ; i < nMethods; i++) {
  22. if (!dvmRegisterJNIMethod(clazz, methods[i].name,
  23. methods[i].signature, methods[i].fnPtr))
  24. {
  25. retval = JNI_ERR;
  26. }
  27. }
  28.  
  29. JNI_EXIT();
  30. return retval;
  31. }

其作用是向clazz参数指定的类注册本地方法,这样,虚拟机就能得到Java层和JNI层之间的对应关系,就可以实现java和native层代码的交互了。我们注意到在Log系统的实例中,JNI层实现方法和注册方法中都使用了JNIEnv这个指针,通过它调用JNI函数,访问Dalvik虚拟机,进而操作Java对象。

我们可以在\Dalvik\libnativehelper\include\nativehelper\jni.h中找到JNIEnv的定义:

  1. struct _JNIEnv;
  2. struct _JavaVM;
  3. typedef const struct JNINativeInterface* C_JNIEnv;
  4. #if defined(__cplusplus) //定义了C++
  5. typedef _JNIEnv JNIEnv; //C++中的JNIEnv的类型
  6. typedef _JavaVM JavaVM;
  7. #else
  8. typedef const struct JNINativeInterface* JNIEnv;
  9. typedef const struct JNIInvokeInterface* JavaVM;
  10. #endif

这里只是用关键字typedef关键字做了类型定义,那么_JNIEnv和JNINativeInterface的定义

  1. /*
  2. * C++ object wrapper.
  3. *
  4. * This is usually overlaid on a C struct whose first element is a
  5. * JNINativeInterface*. We rely somewhat on compiler behavior.
  6. */
  7. struct _JNIEnv {
  8. /* do not rename this; it does not seem to be entirely opaque */
  9. const struct JNINativeInterface* functions;
  10.  
  11. #if defined(__cplusplus)
  12.  
  13. jint GetVersion()
  14. { return functions->GetVersion(this); }
  15.  
  16. jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
  17. jsize bufLen)
  18. { return functions->DefineClass(this, name, loader, buf, bufLen); }
  19.  
  20. jclass FindClass(const char* name)
  21. { return functions->FindClass(this, name); }
  22.  
  23. jmethodID FromReflectedMethod(jobject method)
  24. { return functions->FromReflectedMethod(this, method); }
  25.  
  26. ………..

_JNIEnv只是对const struct JNINativeInterface*类型的封装,并间接调用const struct JNINativeInterface*上定义的方法

  1. /*
  2. * Table of interface function pointers.
  3. */
  4. struct JNINativeInterface {
  5. ……
  6. jclass (*FindClass)(JNIEnv*, const char*);
  7. jboolean (*IsSameObject)(JNIEnv*, jobject, jobject);
  8. ……
  9. };

这里才真正涉及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文件中关于基本数据类型的定义

  1. /*
  2. * Primitive types that match up with Java equivalents.
  3. */
  4. #ifdef HAVE_INTTYPES_H
  5. # include <inttypes.h> /* C99 */
  6. typedef uint8_t jboolean; /* unsigned 8 bits */
  7. typedef int8_t jbyte; /* signed 8 bits */
  8. typedef uint16_t jchar; /* unsigned 16 bits */
  9. typedef int16_t jshort; /* signed 16 bits */
  10. typedef int32_t jint; /* signed 32 bits */
  11. typedef int64_t jlong; /* signed 64 bits */
  12. typedef float jfloat; /* 32-bit IEEE 754 */
  13. typedef double jdouble; /* 64-bit IEEE 754 */
  14. #else
  15. typedef unsigned char jboolean; /* unsigned 8 bits */
  16. typedef signed char jbyte; /* signed 8 bits */
  17. typedef unsigned short jchar; /* unsigned 16 bits */
  18. typedef short jshort; /* signed 16 bits */
  19. typedef int jint; /* signed 32 bits */
  20. typedef long long jlong; /* signed 64 bits */
  21. typedef float jfloat; /* 32-bit IEEE 754 */
  22. typedef double jdouble; /* 64-bit IEEE 754 */
  23. #endif

关于一些返回状态值的定义:

  1. #define JNI_FALSE 0
  2. #define JNI_TRUE 1
  3. #define JNI_OK (0) /* no error */
  4. #define JNI_ERR (-1) /* generic error */
  5. #define JNI_EDETACHED (-2) /* thread detached from the VM*/
  6. #define JNI_EVERSION (-3) /* JNI version error */
  7.  
  8. #define JNI_COMMIT 1 /* copy content, do not free buffer */
  9. #define JNI_ABORT 2 /* free buffer w/o copying back */

JNI引用类型采用了与Java类型相似的继承关系,树根是Jobject

下面是Jni.h中关于引用类型的定义,在C++中全都继承自class jobjct{};而C中都是void*的指针。

  1. #ifdef __cplusplus
  2. /*
  3. * Reference types, in C++
  4. */
  5. class _jobject {};
  6. class _jclass : public _jobject {};
  7. class _jstring : public _jobject {};
  8. class _jarray : public _jobject {};
  9. class _jobjectArray : public _jarray {}; //java层 object[]
  10. class _jbooleanArray : public _jarray {}; //java层 boolean[]
  11. class _jbyteArray : public _jarray {}; //byte[]
  12. class _jcharArray : public _jarray {}; //char[]
  13. class _jshortArray : public _jarray {}; //short[]
  14. class _jintArray : public _jarray {}; //in[]
  15. class _jlongArray : public _jarray {};
  16. class _jfloatArray : public _jarray {};
  17. class _jdoubleArray : public _jarray {};
  18. class _jthrowable : public _jobject {};
  19.  
  20. typedef _jobject* jobject;
  21. typedef _jclass* jclass;
  22. typedef _jstring* jstring;
  23. typedef _jarray* jarray;
  24. typedef _jobjectArray* jobjectArray;
  25. typedef _jbooleanArray* jbooleanArray;
  26. typedef _jbyteArray* jbyteArray;
  27. typedef _jcharArray* jcharArray;
  28. typedef _jshortArray* jshortArray;
  29. typedef _jintArray* jintArray;
  30. typedef _jlongArray* jlongArray;
  31. typedef _jfloatArray* jfloatArray;
  32. typedef _jdoubleArray* jdoubleArray;
  33. typedef _jthrowable* jthrowable;
  34. typedef _jobject* jweak;
  35.  
  36. #else /* not __cplusplus */
  37.  
  38. /*
  39. * Reference types, in C.
  40. */
  41. typedef void* jobject;
  42. typedef jobject jclass;
  43. typedef jobject jstring;
  44. typedef jobject jarray;
  45. typedef jarray jobjectArray;
  46. typedef jarray jbooleanArray;
  47. typedef jarray jbyteArray;
  48. typedef jarray jcharArray;
  49. typedef jarray jshortArray;
  50. typedef jarray jintArray;
  51. typedef jarray jlongArray;
  52. typedef jarray jfloatArray;
  53. typedef jarray jdoubleArray;
  54. typedef jobject jthrowable;
  55. typedef jobject jweak;
  56.  
  57. #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()方法,输入日志。

  1. public class MainActivity extends Activity {
  2. static{
  3. System.loadLibrary("NewJni");
  4. }
  5. private String TAG = "CCDebug";
  6. private Button btnButton = null;
  7. private native String getReply();
  8. @Override
  9. protected void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.activity_main);
  12. btnButton = (Button)findViewById(R.id.btn1);
  13. btnButton.setOnClickListener(new OnClickListener() {
  14. @Override
  15. public void onClick(View v) {
  16. // TODO Auto-generated method stub
  17. Log.d(TAG, getReply());
  18. }
  19. });
  20. }
  21. private void callBack() {
  22. Log.d(TAG, "call back form native !");
  23. throw new NullPointerException();
  24. }
  25. }

java

  1. #include <jni.h>
  2. #ifdef __cplusplus
  3. extern "C" {
  4. #endif
  5. JNIEXPORT jint JNICALL Java_com_example_newjni_MainActivity_getReply
  6. (JNIEnv * env, jobject obj);
  7.  
  8. JNIEXPORT jstring JNICALL Java_com_example_newjni_MainActivity_getReply
  9. (JNIEnv * env, jobject obj)
  10. {
  11.  
  12. jclass jcls = env->GetObjectClass(obj);
  13. jmethodID jmId = env->GetMethodID(jcls,"callBack","()V");
  14. env->CallVoidMethod(obj,jmId);
  15. if(env->ExceptionCheck())
  16. {
  17. env->ExceptionDescribe();
  18. env->ExceptionClear();
  19. }
  20.  
  21. return env->NewStringUTF("Hello From JNI!");
  22. }

native

这是利用javah生成的函数声明,严格遵守NDk的语法要求,当然,我们自己也可以像Log系统那样,自己注册函数的映射关系而不必遵守NDK语法,下面就是将getReply()函数手动注册的例子,但是手动注册我自己目前还存在几个问题:

1. native的代码始终不能下断点到JNI_Onload()函数中

2. 第一次点击Button,native层代码没有响应,必须是第二次点击才会响应

  1. public class MainActivity extends Activity {
  2. private static final String TAG = "CCDebug";
  3. Button btnButton = null;
  4. private native String getReply();
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_main);
  9. btnButton = (Button)findViewById(R.id.btn);
  10. btnButton.setOnClickListener(new OnClickListener() {
  11.  
  12. @Override
  13. public void onClick(View v) {
  14. // TODO Auto-generated method stub
  15. loadLibrary("jniException");
  16. Log.d(TAG, getReply());
  17. }
  18. });
  19. }
  20. private void callBack() {
  21. Log.d(TAG, "call back form native !");
  22. throw new NullPointerException();
  23. }
  24. /*
  25. * 如果在onClick()函数中直接调用System.loadLibrary(),在调试native代码时会出现
  26. * No symbol table is loaded. Use the "file" command.
  27. * 而不能调试native源代码
  28. */
  29. public static void loadLibrary(String libName)
  30. {
  31. System.loadLibrary(libName);
  32. }
  33. }
  1. #include <jni.h>
  2. #include <string.h>
  3. #ifdef __cplusplus
  4. extern "C" {
  5. #endif
  6. JNIEXPORT jstring JNICALL MyFunc
  7. (JNIEnv *env, jobject obj);
  8. static int registerNativeMethods(JNIEnv* env,const char *className,
  9. JNINativeMethod* gMethods,int numMethods);
  10. static int registerNatives(JNIEnv *env);
  11. #ifdef __cplusplus
  12. }
  13. #endif
  14. #define LOGD(msg) \
  15. __android_log_write(ANDROID_LOG_ERROR,"CCDebug",msg);
  16.  
  17. JNIEXPORT jstring JNICALL MyFunc
  18. (JNIEnv *env, jobject obj)
  19. {
  20. /*
  21. * 通过JNI函数GetObjectClass得到传入对象的类信息
  22. * 这里传入的对象就是调用Native方法的那个对象
  23. */
  24. jclass jcls = env->GetObjectClass(obj);
  25. //根据类信息得到callback方法的jmethodID
  26. jmethodID jmId = env->GetMethodID(jcls,"callBack","()V");
  27. //调用callback方法
  28. env->CallVoidMethod(obj,jmId);
  29. /*
  30. * 如果检查是否有异常发生
  31. * 如果有异常发生就处理,否则异常将会抛给java层的callback方法
  32. */
  33. if(env->ExceptionCheck()) //检查异常
  34. {
  35. env->ExceptionDescribe();
  36. env->ExceptionClear(); //清除异常
  37. }
  38.  
  39. return env->NewStringUTF("Show Message Form JNI!");
  40. }
  41.  
  42. static JNINativeMethod gmethods[] = {
  43. {
  44. "getReply",
  45. "()Ljava/lang/String;",
  46. (void*)MyFunc
  47. },
  48. };
  49.  
  50. static int registerNativeMethods(JNIEnv* env,const char *className,
  51. JNINativeMethod* gMethods,int numMethods)
  52. {
  53. jclass clazz;
  54. clazz = env->FindClass(className);
  55. if(clazz == NULL)
  56. {
  57. return JNI_FALSE;
  58. }
  59. //调用JNIEnv提供的注册函数向虚拟机注册
  60. if(env->RegisterNatives(clazz,gMethods,numMethods)<)
  61. {
  62. return JNI_FALSE;
  63. }
  64. return JNI_TRUE;
  65. }
  66.  
  67. static int registerNatives(JNIEnv *env)
  68. {
  69. if (!registerNativeMethods(env,"com/example/jniexception/MainActivity",gmethods,sizeof(gmethods)/sizeof(gmethods[])))
  70. {
  71. return JNI_FALSE;
  72. }
  73. return JNI_TRUE;
  74. }
  75.  
  76. jint JNI_OnLoad(JavaVM* vm, void* reserved)
  77. {
  78.  
  79. jint result = -;
  80. JNIEnv* env = NULL;
  81. if (vm->GetEnv((void**)&env,JNI_VERSION_1_4))
  82. {
  83. return result;
  84. }
  85. if (registerNatives(env)!=JNI_TRUE)
  86. {
  87. return result;
  88. }
  89. result = JNI_VERSION_1_4;
  90. return result;
  91. }

总结:这两天收获还是很大的,尽管中间也遇到了诸多的问题,网上能找到的答案也不尽然,很感谢那些给出了办法解决了我问题的人,下面附上我这两天发现的几篇我觉得很好的文章:

Android4.4源码

安卓动态调试七种武器之孔雀翎 – Ida Pro

安卓动态调试七种武器之长生剑 - Smali Instrumentation

No Symbol table is loaded

eclipse单步调试JNI

ndk配置自动编译

深入理解JNI

Android JNI初体验的更多相关文章

  1. Android RecyclerView初体验

    很早之前就听说过RecyclerView这个组件了,但一直很忙没时间学习.趁着周末,就花了一天时间来学习RecyclerView. 准备工作 在Android Studio里新建一个Android项目 ...

  2. Android wear 初体验

    近期一直在研究android wear SDK,整体感受来说就是和现有的android 其它的开发SDK还是有非常多新的东西.比如手机终端与手表端的通信机制,手表端的UI规范.可是从开发本身来讲,还是 ...

  3. Android程序初体验

    第一个程序的实现的最终功能是: 点击"正确"或者"错误"会得到一个是否正确的提示. 直接上效果图.     此次涉及代码编写的文件有4个: package co ...

  4. Android开发初体验

    本文通过开发一个应用来学习Android基本概念及构成应用的UI组件. 开发的应用名叫GeoQuiz,它能给出一道道地理知识问题.用户点击true或false按钮回答问题,应用即时做出反馈 第一步请先 ...

  5. .net程序员的android studio 初体验 (环境设置2022年10月)

      很久以前用DevExtreme写的一个Hybird APP要添加蓝牙打印功能,但是用来打包APP的phonegap被adobe关闭了,所以,只能自己用cordova去打包安卓APP,不得已,研究了 ...

  6. Android Studio 初体验

    Google在I/O */

  7. Android开发学习之路--百度地图之初体验

    手机都有gps和网络,通过gps或者网络可以定位到自己,然后通过百度,腾讯啊之类的地图可以显示我们的地理位置.这里学习下百度地图的使用.首先就是要申请开发者了,这个详细就不多讲了.http://dev ...

  8. Xamarin.iOS开发初体验

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKwAAAA+CAIAAAA5/WfHAAAJrklEQVR4nO2c/VdTRxrH+wfdU84pW0

  9. Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验

    Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...

随机推荐

  1. 【Win10】开发中的新特性及原有的变更(二)

    声明:本文内容适用于 Visual Studio 2015 RC 及 Windows 10 10069 SDK 环境下,若以后有任何变更,请以新的特性为准. 十一.x:Bind 中使用强制转换 这点是 ...

  2. Python学习-30.Python中的元组(tuple)

    元组使用()定义,元组一旦定义就无法修改. 元组的索引方式同列表,也是使用[]. 元组也可以进行切片操作,使用方式同列表一样. 可以说,一个没法修改的列表就是元组. 在没有修改操作的情况下,应尽可能使 ...

  3. AbpZero的Swagger汉化之旅

    做汉化主要是为了出一份前后端都能看得懂的在线文档,废话不多说,我们开始准备, 我们要在启动项目的Startup.cs中重定向一下swagger的读取方式 1.在这个类下面,新增一个方法: public ...

  4. WebAPI的AuthorizeAttribute扩展类中获取POST提交的数据

    在WEBAPI中,AuthorizeAttribute类重写时,如何获取post数据是个难题,网上找资料也不好使,只能自己研究,通过研究发现,WEBAPI给了我们获取POST数据的可能,下面介绍一下: ...

  5. [C#学习笔记]lock锁的解释与用法

    写在前面 前几时在写业务代码的时候,看到有用到lock这个方法的,而我竟然并不知道是做什么用的,所以查找了许多博客文章,弄懂了百分之七八十,在此做下笔记. 感谢博客 http://www.cnblog ...

  6. PHP/ThinkPHP5 框架集成微博登录入库流程示意

    PHP/ThinkPHP5 框架集成微博登录入库流程示意 第三方登陆这个东东,目前主要是 微信.微博.qq.淘宝.支付宝 等几个.他们都是基于oath2协议的.原理差不多.这里记录的是我测试的新郎微博 ...

  7. .Net常用正则判断方法

    /// <summary> /// 判断string类型否为数字 /// </summary> /// <param name="strNumber" ...

  8. MongoDB复制集成员及状态转换

    此文已由作者温正湖授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 复制集(Replica Set)是MongoDB核心组件,相比早期版本采用的主从(Master-Slave) ...

  9. for循环、for in整理

    for循环 作用:按照一定的规律,重复去做某件事情,此时我们就需要使用循环来处理了 例子1:倒着输出每一项 <script type="text/javascript"> ...

  10. 用layui遇到过的问题

    1.报错“layui.form is not a function”问题 把代码中这一串修改一下:form = layui.form(); 括号去掉就行: form = layui.form; 如果你 ...