Android NDK开发(五)--C代码回调Java代码【转】

转载请注明出处:http://blog.csdn.net/allen315410/article/details/41862479

在上篇博客里了解了Java层是怎样传递数据到C层代码,并且熟悉了大部分的实际开发知识,基本上掌握这些就可以做一个基本的NDK开发了,但是光是了解Java回调C层的数据是不是还不够啊,考虑问题要考虑可逆性,Java能回调C,那么C能否反过来回调Java呢?答案是肯定可以的,这篇博客就介绍一个C语言如何调用Java层的代码。以下是一些问题场景,我们带着这个问题场景来分析一下实现的过程。

场景1:开发中C语言层完成了一系列操作后,需要通知Java层代码此时需要做什么操作。

场景2:大家知道程序员都是比较懒惰的,Java代码中封装了大量的方法,C程序员不想重复写复杂的逻辑,这时想通过C语言回调使用Java层代码中的方法。

好,带着上面的场景,我们下面建立一个小的Demo来尝试解决这些业务场景的问题。

创建工程,在工程里面定义Java方法和Native方法

[java] view plain copy

print?

  1. package com.example.ndkcallback;
  2. public class DataProvider {
  3. /**
  4. * C调用java空方法
  5. */
  6. public void nullMethod() {
  7. System.out.println("hello from java");
  8. }
  9. /**
  10. * C调用java中的带两个int参数的方法
  11. *
  12. * @param x
  13. * @param y
  14. * @return
  15. */
  16. public int Add(int x, int y) {
  17. int result = x + y;
  18. System.out.println("result in java " + result);
  19. return result;
  20. }
  21. /**
  22. * C调用java中参数为String的方法
  23. *
  24. * @param s
  25. */
  26. public void printString(String s) {
  27. System.out.println("java " + s);
  28. }
  29. // 本地方法
  30. public native void callMethod1();
  31. public native void callMethod2();
  32. public native void callMethod3();
  33. }

编译头文件

在DOS命令行下,切换到工程目录所在的源码存放的src目录下,使用javah命令编译C语言的函数签名。而且得注意的是,由于我使用的JDK 是1.7版本的,所以必须得切换到工程目录/src目录下执行javah,如果大家使用的是JDK 1.6或者JDK 1.5,那就切换到工程目录/classes目录,执行javah命令。

注意:使用javah命令时,需要指定-encoding utf-8 参数,防止编译报乱码错误,下面是编译好的头文件:

[cpp] view plain copy

print?

  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class com_example_ndkcallback_DataProvider */
  4. #ifndef _Included_com_example_ndkcallback_DataProvider
  5. #define _Included_com_example_ndkcallback_DataProvider
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. /*
  10. * Class:     com_example_ndkcallback_DataProvider
  11. * Method:    callMethod1
  12. * Signature: ()V
  13. */
  14. JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod1
  15. (JNIEnv *, jobject);
  16. /*
  17. * Class:     com_example_ndkcallback_DataProvider
  18. * Method:    callMethod2
  19. * Signature: ()V
  20. */
  21. JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod2
  22. (JNIEnv *, jobject);
  23. /*
  24. * Class:     com_example_ndkcallback_DataProvider
  25. * Method:    callMethod3
  26. * Signature: ()V
  27. */
  28. JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod3
  29. (JNIEnv *, jobject);
  30. #ifdef __cplusplus
  31. }
  32. #endif
  33. #endif

编写C代码

有了上面的头文件,接下来就是最不好搞的C代码了,按照套路来,首先把上面编译好的头文件剪切到jni目录下,在该目录下新建一个Hello.c的C代码文件,将刚引入的头文件的函数签名拷贝到Hello.c中使用,然后就是首先引入LOG日志头文件,定义LOG日志输入,再然后就是编译C代码,如下:

[cpp] view plain copy

print?

  1. #include<stdio.h>
  2. #include<jni.h>
  3. #include"com_example_ndkcallback_DataProvider.h"
  4. #include<android/log.h>
  5. #define LOG_TAG "System.out.c"
  6. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
  7. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
  8. JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod1
  9. (JNIEnv * env, jobject obj){
  10. //在C语言中调用Java的空方法
  11. //1.找到java代码native方法所在的字节码文件
  12. //jclass (*FindClass)(JNIEnv*, const char*);
  13. jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
  14. if(clazz == 0){
  15. LOGD("find class error");
  16. return;
  17. }
  18. LOGD("find class");
  19. //2.找到class里面对应的方法
  20. // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  21. jmethodID method1 = (*env)->GetMethodID(env,clazz,"nullMethod","()V");
  22. if(method1 == 0){
  23. LOGD("find method1 error");
  24. return;
  25. }
  26. LOGD("find method1");
  27. //3.调用方法
  28. //void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  29. (*env)->CallVoidMethod(env, obj, method1);
  30. LOGD("method1 called");
  31. }
  32. JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod2
  33. (JNIEnv * env, jobject obj) {
  34. //1.找到java代码native方法所在的字节码文件
  35. //jclass (*FindClass)(JNIEnv*, const char*);
  36. jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
  37. if(clazz == 0){
  38. LOGD("find class error");
  39. return;
  40. }
  41. LOGD("find class");
  42. //2.找到class里面对应的方法
  43. // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  44. jmethodID method2 = (*env)->GetMethodID(env,clazz,"Add","(II)I");
  45. if(method2 == 0){
  46. LOGD("find method2 error");
  47. return;
  48. }
  49. LOGD("find method2");
  50. //3.调用方法
  51. //jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
  52. int result = (*env)->CallIntMethod(env, obj, method2, 3,5);
  53. LOGD("result in C = %d", result);
  54. }
  55. JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod3
  56. (JNIEnv * env, jobject obj) {
  57. //1.找到java代码native方法所在的字节码文件
  58. //jclass (*FindClass)(JNIEnv*, const char*);
  59. jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
  60. if(clazz == 0){
  61. LOGD("find class error");
  62. return;
  63. }
  64. LOGD("find class");
  65. //2.找到class里面对应的方法
  66. // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  67. jmethodID method3 = (*env)->GetMethodID(env,clazz,"printString","(Ljava/lang/String;)V");
  68. if(method3 == 0){
  69. LOGD("find method3 error");
  70. return;
  71. }
  72. LOGD("find method3");
  73. //3.调用方法
  74. //void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  75. (*env)->CallVoidMethod(env, obj, method3,(*env)->NewStringUTF(env,"haha in C ."));
  76. LOGD("method3 called");
  77. }

注意:编写C代码时大致需要如下3个重要的步骤:

1.找到java代码native方法所在的字节码文件,在jni.h中的JNINativeInterface中可以找到

[cpp] view plain copy

print?

  1. jclass (*FindClass)(JNIEnv*, const char*);

其中第1个参数是JNINativeInterface的指针env,第2个参数是java方法所在的类全路径名,路径之间用“/”来区分,不可以使用“.”

2.找到class里面对应的方法,在jni.h中的JNINativeInterface中可以找到

获取非静态方法id:

[cpp] view plain copy

print?

  1. jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);

获取静态方法id:

[cpp] view plain copy

print?

  1. jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);

其中第1个参数是JNINativeInterface的指针env,第2个参数是java字节码文件,第3个参数是java中的方法名,第四个参数是java中对应方法的签名。

3.调用方法

[cpp] view plain copy

print?

  1. void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  2. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
  3. jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
  4. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
  5. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
  6. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
  7. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
  8. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
  9. jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
  10. jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;

其中第1个参数是JNINativeInterface的指针env,第2个参数是java对象obj,第3个参数是找到的对应java中的方法,第4个参数是方法接收的参数。这里列出的是常用的方法,jni.h里的JNINativeInterface提供了大量的方法形式用来回调java中的方法,想了解的请参考jni.h这个文件。

使用javap命令查看方法签名

JDK为我们提供了这样的一个工具,该工具可以从java字节码文件中查看方法的本地签名,这个工具就是javap,使用前,先在CMD的dos命令行中,把路径切换到工程中的java字节码文件所在的目录下。

命令格式:javap -s 包名.方法所在的Java类名

如图所示的那样,黄色标注的是方法名,是(*GetMethodID)(JNIEnv*, jclass, const char*, const char*)中的第3个参数,红色标注的是方法签名,是其第4个参数。

Android.mk配置和Application.mk配置

[javascript] view plain copy

print?

  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE    := Hello
  4. LOCAL_SRC_FILES := Hello.c
  5. LOCAL_LDLIBS += -llog
  6. include $(BUILD_SHARED_LIBRARY)

[javascript] view plain copy

print?

  1. APP_PLATFORM := android-8

编译C代码

首先在cygwin中切换到当前工程目录下,执行“ndk-build clean”和“ndk-build”命令

在Java中调用Nattive方法

[java] view plain copy

print?

  1. public class MainActivity extends Activity implements OnClickListener {
  2. static {
  3. // 加载动态库.so
  4. System.loadLibrary("Hello");
  5. }
  6. private Button btn1, btn2, btn3;
  7. private DataProvider provider;
  8. @Override
  9. protected void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.activity_main);
  12. btn1 = (Button) findViewById(R.id.btn1);
  13. btn2 = (Button) findViewById(R.id.btn2);
  14. btn3 = (Button) findViewById(R.id.btn3);
  15. btn1.setOnClickListener(this);
  16. btn2.setOnClickListener(this);
  17. btn3.setOnClickListener(this);
  18. provider = new DataProvider();
  19. }
  20. @Override
  21. public void onClick(View v) {
  22. switch (v.getId()) {
  23. case R.id.btn1 : // c回调java中的空方法
  24. provider.callMethod1();
  25. break;
  26. case R.id.btn2 :// c回调java带2个int参数的方法
  27. provider.callMethod2();
  28. break;
  29. case R.id.btn3 :// c回调java带string参数的方法
  30. provider.callMethod3();
  31. break;
  32. default :
  33. break;
  34. }
  35. }
  36. }

测试

注意:以下测试的LOG中,绿色代表Java生成的LOG,蓝色代表C生成的LOG。

测试1:c回调java中的空方法

测试2:c回调java带2个int参数的方法

测试3:c回调java带string参数的方法

另外:native代码与调用的java代码不在同一个类里

上述建立的Android工程中,native代码和调用的java代码是放在同一个DataProvider类中的,这样在C代码中调用Java代码是非常方便的。但是,通常开发中我们不一定就这么干,一个项目中java文件很多,要是在其它的java文件中定义了native方法了,然后再去调另一个java类里的Java方法,这种情况下会出现什么问题呢?带着这个疑问,我们就在MainActivity.java文件中定义一个native方法,这个native方法又要调用DataProvider类的nullMethod方法。

在MainActivity.java中,我们定义这样的方法:

[java] view plain copy

print?

  1. private native void callMethod4();

切换到这个src目录下javah获取函数签名,将得到的签名头文件拷贝到jni目录下,在C文件中引用这个头文件,编写相应的C代码:

[cpp] view plain copy

print?

  1. JNIEXPORT void JNICALL Java_com_example_ndkcallback_MainActivity_callMethod4
  2. (JNIEnv * env, jobject obj){
  3. //1.找到java代码native方法所在的字节码文件
  4. //jclass (*FindClass)(JNIEnv*, const char*);
  5. jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
  6. if(clazz == 0){
  7. LOGD("find class error");
  8. return;
  9. }
  10. LOGD("find class");
  11. //2.找到class里面对应的方法
  12. // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  13. jmethodID method4 = (*env)->GetMethodID(env,clazz,"nullMethod","()V");
  14. if(method4 == 0){
  15. LOGD("find method4 error");
  16. return;
  17. }
  18. LOGD("find method4");
  19. //3.调用方法
  20. //void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  21. (*env)->CallVoidMethod(env, obj, method4);
  22. LOGD("method4 called");
  23. }

编译运行之后,报错了

实际运行的时候,程序直接崩溃了,查看日志发现,字节码class找到了,方法method找到了,但是就是没有执行method方法,显然是执行method方法这行代码出了Bug,以下是调用method方法执行的代码:

[cpp] view plain copy

print?

  1. (*env)->CallVoidMethod(env, obj, method4);

那么这行代码是为什么报错了呢?仔细观察一下,CallVoidMethod方法的第2个参数obj,这个obj是jobject类型的,默认是java native方法所在的类的对象,就是MainActivity类的对象,但是这个native方法实际上调用的java方法存在于DataProvider类的nullMethod,调用nullMethod显然需要使用DataProvider类的对象。反正就一句话:obj对象不正确,需要java方法对应的对象,即DataProvider。

知道问题了,就可以着手解决问题了。在jni.h的头文件中,JNINativeInterface提供了这样的一个方法,帮助我们通过字节码jclass找到对应的对象:

[cpp] view plain copy

print?

  1. jobject     (*AllocObject)(JNIEnv*, jclass);

这个方法第1个参数是JNINativeInterface,第2个参数是jclass,返回值jobject。我们就拿这个方法获取jobject,传给CallVoidMethod:

[cpp] view plain copy

print?

  1. JNIEXPORT void JNICALL Java_com_example_ndkcallback_MainActivity_callMethod4
  2. (JNIEnv * env, jobject obj){
  3. //1.找到java代码native方法所在的字节码文件
  4. //jclass (*FindClass)(JNIEnv*, const char*);
  5. jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
  6. if(clazz == 0){
  7. LOGD("find class error");
  8. return;
  9. }
  10. LOGD("find class");
  11. //2.找到class里面对应的方法
  12. // jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  13. jmethodID method4 = (*env)->GetMethodID(env,clazz,"nullMethod","()V");
  14. if(method4 == 0){
  15. LOGD("find method4 error");
  16. return;
  17. }
  18. LOGD("find method4");
  19. //3.通过jclass获取jobject
  20. //jobject     (*AllocObject)(JNIEnv*, jclass);
  21. jobject jobj = (*env)->AllocObject(env, clazz);
  22. if(jobj == 0){
  23. LOGD("find jobj error");
  24. return;
  25. }
  26. LOGD("find jobj");
  27. //4.调用方法
  28. //void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
  29. (*env)->CallVoidMethod(env, jobj, method4);
  30. LOGD("method4 called");
  31. }

写完代码之后,重新编译C代码文件,Refresh和clean一下工程,运行后:

说明native方法callMethod4已经运行成功了。

时间: 2024-12-15 01:35:06

Android NDK开发(五)--C代码回调Java代码【转】的相关文章

Android NDK开发篇(五):Java与原生代码通信(数据操作)

尽管说使用NDK能够提高Android程序的运行效率,可是调用起来还是略微有点麻烦.NDK能够直接使用Java的原生数据类型,而引用类型,由于Java的引用类型的实如今NDK被屏蔽了,所以在NDK使用Java的引用类型则要做对应的处理. 一.对引用数据类型的操作 尽管Java的引用类型的实如今NDK被屏蔽了,JNI还是提供了一组API,通过JNIEnv接口指针提供原生方法改动和使用Java的引用类型. 1.字符串操作 JNI把Java的字符串当作引用来处理,在NDK中使用Java的字符串,须要相

Android NDK 开发(四)java传递数据到C【转】

转载请注明出处:http://blog.csdn.net/allen315410/article/details/41845701 前面几篇文章介绍了Android NDK开发的简单概念.常见错误及处理和从第一个Hello World开始实际做一个简单的JNI开发示例,相信看完之后,大家对NDK开发有了一个概念上的认识了,那么接下来我们需要再深入一下NDK的开发,我们知道NDK开发就是使用JNI这层“协议”在Java和C之间起个“桥梁”的作用,将Java和Native C之间联立起来,让Java

Android NDK开发(四)——Java传递数据到C

转载请注明出处:http://blog.csdn.net/allen315410/article/details/41845701 前面几篇文章介绍了Android NDK开发的简单概念.常见错误及处理和从第一个Hello World开始实际做一个简单的JNI开发示例,相信看完之后,大家对NDK开发有了一个概念上的认识了,那么接下来我们需要再深入一下NDK的开发,我们知道NDK开发就是使用JNI这层"协议"在Java和C之间起个"桥梁"的作用,将Java和Nativ

Android C代码回调java方法

本文将讲述下列三种C代码回调java方法 1.c代码回调java空方法 2.c代码回调java int类型参数方法 3.c代码回调javaString类型参数方法 方法都差不多,先看c代码回调java空方法,其他两种类似: ① 找到字节码对象 //jclass (*FindClass)(JNIEnv*, const char*); //第二个参数 要回调的java方法所在的类的路径 "com/itheima/callbackjava/JNI" ② 通过字节码对象找到方法对象 //jme

Android NDK开发(六)——使用开源LAME转码mp3

转载请注明出处:http://blog.csdn.net/allen315410/article/details/42456661 在本专栏的前面几篇博客中讲述了一些Android NDK开发的基础,从环境搭建一直到利用JNI进行Java端和C端代码的互相调用,并且的讲解的Demo也是很简单易懂的,相信掌握前面博客的大部分内容,就可以着手在实际项目中利用JNI进行NDK开发了,那么既然基础过了,接下来我在这里尝试去使用真实项目中去.我们知道,C语言因为高效,而且又是最早期的高级编程之一,一直存活

android NDK开发在本地C/C++源码中设置断点单步调试具体教程

近期在学android NDK开发,折腾了一天,最终可以成功在ADT中设置断点单步调试本地C/C++源码了.网上关于这方面的资料太少了,并且大都不全,并且调试过程中会出现各种各样的问题,真是非常磨人.程序员就得有耐心. 把自己的调试过程记录下来.希望对须要的朋友有帮助. 在看本文之前,请先确保你已经成功编译了一个android NDKproject,而且可以在模拟器或者真机上执行.至于怎么编译NDKproject,包含配置.生成.so文件等等.可以參考我的前一篇博客:http://blog.cs

Android NDK开发初识

神秘的Android NDK开发往往众多程序员感到兴奋,但又不知它为何物,由于近期开发应用时,为了是开发的.apk文件不被他人解读(反编译),查阅了很多资料,其中有提到使用NDK开发,怀着好奇的心理,通过在线视频教育网站,我初步了解了NDK的神秘面纱,好东西自然要分享,接下来我们就一起来认识一下Android NDK开发. 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三

windows下用ADT进行android NDK开发的详细教程(从环境搭建、配置到编译全过程)

郑重申明:如需转载本博客,请注明出处,谢谢! 这几天在学习android NDK的开发,那么首先让我们来看看android NDK开发的本质是什么. NDK(Native Development Kit),即本地开发工具,简单地说,就是在开发android应用程序的时候,在java类中调用native函数,而native函数的接口也是在java类中定义的,但是native函数最终由本地的C/C++代码实现.简单地说,就是在java中调用C/C++函数.至于为什么要用NDK,我总结了一下,大致有以

android NDK开发在本地C/C++源代码中设置断点单步调试详细教程

最近在学android NDK开发,折腾了一天,终于能够成功在ADT中设置断点单步调试本地C/C++源代码了.网上关于这方面的资料太少了,而且大都不全,并且调试过程中会出现各种各样的问题,真是很磨人,程序猿就得有耐心.把自己的调试过程记录下来,希望对需要的朋友有帮助. 在看本文之前,请先确保你已经成功编译了一个android NDK工程,并且能够在模拟器或者真机上运行.至于怎么编译NDK工程,包括配置.生成.so文件等等,可以参考我的前一篇博客:http://blog.csdn.net/u013