码迷,mamicode.com
首页 > 其他好文 > 详细

NDK开发基本知识

时间:2016-03-28 00:17:51      阅读:297      评论:0      收藏:0      [点我收藏+]

标签:

(3) NDK开发中乱码问题


解决乱码思路 : C语言编译的时候用的是 ISO-8859-1 码表进行编码, 如果我们使用C语言jni开发, 需要进行转码操作;
-- 将ISO-8859-1转为UTF-8字符: String string = new String(str.getBytes("iso8859-1"), "UTF-8");


示例 : 

添加中文jni调用 : 将jni中的hello.c 中返回的字符串修改为中文, 重新编译 .so 静态库文件;
-- 修改后的hello.c文件如下 : 只改变了返回的字符串, 添加了中文;
  1. #include <jni.h>  
  2.   
  3. /* 
  4.  * 方法名称规定 : Java_完整包名类名_方法名() 
  5.  * JNIEnv 指针 
  6.  * 
  7.  * 参数介绍 : 
  8.  * env : 代表Java环境, 通过这个环境可以调用Java中的方法 
  9.  * thiz : 代表调用JNI方法的对象, 即MainActivity对象 
  10.  */  
  11. jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)  
  12. {  
  13.     /* 
  14.      * 调用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法 
  15.      * jni.h 中定义的方法  jstring (*NewStringUTF)(JNIEnv*, const char*); 
  16.      */  
  17.     return (*env)->NewStringUTF(env, "hello world jni 中文");  
  18. }  

使用NDK重新编译hello.c文件 : 修改了C源码之后, 重新将该c文件编译成so文件;
-- 编译过程: 打开cygwin, 进入cygdrive/ 下对应windows中源码项目中的jni目录, 执行 /android-ndk-r9c/ndk-build 命令;
技术分享


运行Android代码报错 : 因为jni中c文件有中文, 中文不能被识别;
[java] view plaincopy技术分享技术分享
  1. 01-31 14:36:04.803: W/dalvikvm(389): JNI WARNING: illegal continuation byte 0xd0  
  2. 01-31 14:36:04.803: W/dalvikvm(389):              string: ‘hello world jni ????‘  
  3. 01-31 14:36:04.803: W/dalvikvm(389):              in Lshuliang/han/ndkhelloworld/MainActivity;.helloFromJNI ()Ljava/lang/String; (NewStringUTF)  
  4. 01-31 14:36:04.834: I/dalvikvm(389): "main" prio=5 tid=1 NATIVE  
  5. 01-31 14:36:04.834: I/dalvikvm(389):   | group="main" sCount=0 dsCount=0 obj=0x4001f1a8 self=0xce48  
  6. 01-31 14:36:04.834: I/dalvikvm(389):   | sysTid=389 nice=0 sched=0/0 cgrp=default handle=-1345006528  
  7. 01-31 14:36:04.844: I/dalvikvm(389):   | schedstat=( 257006717 305462830 51 )  
  8. 01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.helloFromJNI(Native Method)  
  9. 01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.onCreate(MainActivity.java:26)  
  10. 01-31 14:36:04.844: I/dalvikvm(389):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)  
  11. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)  
  12. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)  
  13. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.access$1500(ActivityThread.java:117)  
  14. 01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)  
  15. 01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Handler.dispatchMessage(Handler.java:99)  
  16. 01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Looper.loop(Looper.java:123)  
  17. 01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread.main(ActivityThread.java:3683)  
  18. 01-31 14:36:04.864: I/dalvikvm(389):   at java.lang.reflect.Method.invokeNative(Native Method)  
  19. 01-31 14:36:04.874: I/dalvikvm(389):   at java.lang.reflect.Method.invoke(Method.java:507)  
  20. 01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)  
  21. 01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)  
  22. 01-31 14:36:04.874: I/dalvikvm(389):   at dalvik.system.NativeStart.main(Native Method)  
  23. 01-31 14:36:04.884: E/dalvikvm(389): VM aborting  
.


4. JNIEnv 详解


JNIEnv作用 : JNIEnv 是一个指针,指向了一组JNI函数, 这些函数可以在jni.h中查询到,通过这些函数可以实现 Java层 与 JNI层的交互 , 通过JNIEnv 调用JNI函数 可以访问java虚拟机, 操作java对象;

JNI线程相关性 : JNIEnv只在当前的线程有效,JNIEnv不能跨线程传递, 相同的Java线程调用本地方法, 所使用的JNIEnv是相同的, 一个Native方法不能被不同的Java线程调用;

JNIEnv结构体系 : JNIEnv指针指向一个线程相关的结构,线程相关结构指向一个指针数组,指针数组中的每个元素最终指向一个JNI函数.

(1) JNIEnv的C/C++声明


jni.h中声明JNIEnv : C语言中定义的JNIEnv 是 JNINativeInterface* , C++中定义的JNIEnv 是 _JNIEnv;
  1. struct _JNIEnv;  
  2. struct _JavaVM;  
  3. typedef const struct JNINativeInterface* C_JNIEnv;  
  4.   
  5. #if defined(__cplusplus)    //为了兼容C 和 C++两种代码 使用该 宏加以区分  
  6. typedef _JNIEnv JNIEnv;     //C++ 中的JNIEnv类型  
  7. typedef _JavaVM JavaVM;  
  8. #else  
  9. typedef const struct JNINativeInterface* JNIEnv;//C语言中的JNIEnv类型  
  10. typedef const struct JNIInvokeInterface* JavaVM;  
  11. #endif  

(2) C语言中的JNIEnv


关于JNIEnv指针调用解析 : C中JNIEnv就是 const struct JNINativeInterface*, JNIEnv * env等价于 JNINativeInterface** env, 因此要得到JNINativeInterface结构体中定义的函数指针, 就必须先获取到 JNINativeInterface的一级指针对象 即 *env , 该一级指针对象就是 JNINativeInterface* env, 然后通过该一级指针对象调用JNI函数 : (*env)->NewStringUTF(env, "hello");

在JNINativeInterface结构体中定义了一系列的关于Java操作的相关方法 : 
  1. /* 
  2.  * Table of interface function pointers. 
  3.  */  
  4. struct JNINativeInterface {  
  5.     void*       reserved0;  
  6.     void*       reserved1;  
  7.       
  8.     ... ...  
  9.       
  10.     jboolean    (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,  
  11.                         va_list);  
  12.     jboolean    (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID,  
  13.                         jvalue*);  
  14.     jbyte       (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);  
  15.     jbyte       (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);  
  16.       
  17.     ... ...  
  18.       
  19.     void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);  
  20.     jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);  
  21.   
  22.     /* added in JNI 1.6 */  
  23.     jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);  
  24. };  

(3) C++中的JNIEnv


C++ 中的JNIEnv: C++ 中的JNIEnv 就是 _JNIEnv 结构体, 二者是等同的; 因此在调用 JNI函数的时候, 只需要使用 env->NewStringUTF(env, "hello")方法即可, 不用在进行*运算;

.
  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.     jlong GetDirectBufferCapacity(jobject buf)  
  17.     { return functions->GetDirectBufferCapacity(this, buf); }  
  18.   
  19.     /* added in JNI 1.6 */  
  20.     jobjectRefType GetObjectRefType(jobject obj)  
  21.     { return functions->GetObjectRefType(this, obj); }  
  22. #endif /*__cplusplus*/  
  23. };  

5. JNI方法命名规则(标准JNI规范)


JNI实现的方法 与 Java中Native方法的映射关系 : 使用方法名进行映射, 可以使用 javah 工具进入 bin/classes 目录下执行命令, 即可生成头文件;

JNI方法参数介绍
-- 参数① : 第一个参数是JNI接口指针 JNIEnv;
-- 参数② : 如果Native方法是非静态的, 那么第二个参数就是对Java对象的引用, 如果Native方法是静态的, 那么第二个参数就是对Java类的Class对象的引用;

JNI方法名规范 : 返回值 + Java前缀 + 全路径类名 + 方法名 + 参数① JNIEnv + 参数② jobject + 其它参数;
-- 注意分隔符 : Java前缀 与 类名 以及类名之间的包名 和 方法名之间 使用 "_" 进行分割;

声明 非静态 方法
-- Native方法 : public int hello (String str, int i); 
-- JNI方法: jint Java_shuliang_han_Hello_hello(JNIEnv * env, jobject obj, jstring str, jint i);

声明 静态 方法 : 
-- Native方法 : public static int hello (String str, int i); 
--JNI方法 : jint Java_shuliang_han_Hello_hello(JNIEnv * env, jobject clazz, jstring str, jint i);

两种规范 : 以上是Java的标准JNI规范, 在Android中还有一套自定义的规范, 该规范是Android应用框架层 和 框架层交互使用的JNI规范, 依靠方法注册 映射 Native方法 和 JNI方法;

6. JNI方法签名规则


JNI识别Java方法 : JNI依靠函数名 和 方法签名 识别方法, 函数名是不能唯一识别一个方法的, 因为方法可以重载, 类型签名代表了 参数 和 返回值;
-- 签名规则 : (参数1类型签名参数2类型签名参数3类型签名参数N类型签名...)返回值类型签名, 注意参数列表中没有任何间隔;

Java类型 与 类型签名对照表 : 注意 boolean 与 long 不是大写首字母, 分别是 Z 与 J,  类是L全限定类名, 数组是[元素类型签名;
-- 类的签名规则 :L + 全限定名 + ; 三部分, 全限定类名以 / 分割;
Java类型 类型签名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
L全限定类名
数组 [元素类型签名


eg. long function(int n, String str, int[] arr);
该方法的签名 :(ILjava/lang/String;[I)J
.

.

四. Java调用JNI法与日志打印



1. JNI数据类型



Java数据类型 C数据类型 JNI数据类型对比 : 32位 与 64位机器可能会有出入;

Java数据类型 C本地类型 JNI定义别名
int long jint/jsize
long __int64 jlong
byte signed char jbyte
boolean unsigned char jboolean
char unsigned short jchar
short short jshort
float float jfloat
double doyble jdouble
object‘
_jobject jobject

数据类型表示方法 : int数组类型 jintArray , boolean数组 jbooleanArray ...

头文件定义类型 : 这些基本的数据类型在jni.h 中都有相应的定义 : 
  1. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  2. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
  3. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  4. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  5. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
  6. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  7. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  8. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
  9. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  10. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  11. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
  12. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  13. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  14. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
  15. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  16. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  17. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
  18. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  19. jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);  
  20. jbyteArray    (*NewByteArray)(JNIEnv*, jsize);  
  21. jcharArray    (*NewCharArray)(JNIEnv*, jsize);  
  22. jshortArray   (*NewShortArray)(JNIEnv*, jsize);  
  23. jintArray     (*NewIntArray)(JNIEnv*, jsize);  
  24. jlongArray    (*NewLongArray)(JNIEnv*, jsize);  
  25. jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);  
  26. jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);  


2. JNI在Java和C语言之间传递int类型



Java中定义的方法 : 
[java] view plaincopy技术分享技术分享
  1. //将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider  
  2. public native int add(int x, int y);  

C语言中定义的方法 : 
  1. #include <jni.h>  
  2.   
  3. //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数  
  4. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)  
  5. {  
  6.     return x + y;  
  7. }  

使用NDK工具变异该c类库 : 
在cygwin中进入cygdrive, 然后进入windows中相应的目录, 执行 /android-ndk-r9c/ndk-build 命令, 即可完成编译;
技术分享


3. NDK中C代码使用LogCat



(1) 引入头文件


NDK中断点调试 : 断点调试在NDK中实现极其困难, 因此在这里我们一般都是打印日志;

引入头文件 : 在C代码中引入下面的头文件;
  1. #include <android/log.h>  
  2. #define LOG_TAG "System.out"  
  3. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  4. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  

头文件介绍 : log.h 是关于调用 LogCat日志文件;
-- log.h头文件路径 : android-ndk-r9c\platforms\android-9\arch-arm\usr\include\android\log.h;
-- 主要方法 :  __android_log_write, 下面有该方法的解析, 传入参数 日志等级 日志标签 日志内容;
-- 宏定义 : __android_log_write 方法太麻烦, 这里做出一个映射, LOGD(...) 输出debug级别的日志, LOGI(...) 输出Info级别的日志;
--LogCat日志级别 : verbose < debug < info < warn < error < assert;

使用到的log.h文件内容解析 : __android_log_write 方法中的日志等级参数就使用 枚举中的内容 
  1. /* 
  2.  * Android log priority values, in ascending priority order. 日志等级 
  3.  */  
  4. typedef enum android_LogPriority {  
  5.     ANDROID_LOG_UNKNOWN = 0,  
  6.     ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */  
  7.     ANDROID_LOG_VERBOSE,  
  8.     ANDROID_LOG_DEBUG,  
  9.     ANDROID_LOG_INFO,  
  10.     ANDROID_LOG_WARN,  
  11.     ANDROID_LOG_ERROR,  
  12.     ANDROID_LOG_FATAL,  
  13.     ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */  
  14. } android_LogPriority;  
  15.   
  16. /* 
  17.  * Send a simple string to the log. 向LogCat中输出日志  
  18.     参数介绍: 日志优先级 , 日志标签 , 日志内容 
  19.  */  
  20. int __android_log_write(int prio, const char *tag, const char *text);  

C语言中输入输出函数占位符介绍 : 
占位符 数据类型
%d int
%ld long int
%c char
%f float
&lf double
%x 十六进制
%O 八进制
%s 字符串

.
.

(2) Android.mk增加liblog.so动态库


在该make配置文件中, 增加一行 : LOCAL_LDLIBS += -llog , 该语句添加在 LOCAL_SRC_FILES 语句下面一行;

完整的Android.mk文件 : 
[plain] view plaincopy技术分享技术分享
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := DataProvider  
  6. LOCAL_SRC_FILES := DataProvider.c  
  7. #增加log函数对应的函数库 liblog.so  libthread_db.a  
  8. LOCAL_LDLIBS += -llog -lthread_db   
  9. include $(BUILD_SHARED_LIBRARY)  

函数库位置 : android-ndk-r9c\platforms\android-9\arch-arm\usr\lib;
函数库截图 : 从该目录下的 liglog.so可以看出, 存在该库;
技术分享
引入函数库方法 : 使用 LOCAL_LDLIBS += -l函数库名, 注意函数库名不带lib前缀 和.so 后缀, 同时可以添加多个库, 使用 -l库1 -l库2 -库3 ;


(3) 编译执行


根据(1) 中的占位符, 编写打印日志代码
  1. //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中  
  2. LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);  

最终的包含打印日志的完整代码 : 注意, 这里有一处可能错误, 如果是32位机器, int类型占位符使用 %d 即可;
  1. #include <jni.h>  
  2. #include <android/log.h>  
  3. #define LOG_TAG "System.out"  
  4. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  5. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
  6.   
  7.   
  8. //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数  
  9. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)  
  10. {  
  11.     //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中  
  12.     LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);  
  13.     return x + y;  
  14. }  

重新编译C文件 : 执行 /android-ndk-r9c/ndk-build命令;
-- 第一次编译 : 出现警告, long int占位符行不通, 注意区分机器位长, 64位 与 32位不同, 这样编译出现的结果就不会打印日志;
技术分享
-- 第二次编译 : 将占位符改为 %d ;
技术分享

执行按钮之后打印的日志 : 虽然有乱码, 不过显示出来了;
技术分享

技术分享


4. 字符串处理

.

Java中的String转为C语言中的char字符串 : 下面的工具方法可以在C程序中解决这个问题;
  1. // java中的jstring, 转化为c的一个字符数组  
  2. char* Jstring2CStr(JNIEnv* env, jstring jstr) {  
  3. <span style="white-space:pre">  </span>//声明了一个字符串变量 rtn  
  4. <span style="white-space:pre">  </span>char* rtn = NULL;  
  5. <span style="white-space:pre">  </span>//找到Java中的String的Class对象  
  6. <span style="white-space:pre">  </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");  
  7. <span style="white-space:pre">  </span>//创建一个Java中的字符串 "GB2312"  
  8. <span style="white-space:pre">  </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");  
  9. <span style="white-space:pre">  </span>/* 
  10. <span style="white-space:pre">  </span> * 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组 
  11. <span style="white-space:pre">  </span> * "(Ljava/lang/String;)[B" 方法前面解析 : 
  12. <span style="white-space:pre">  </span> * -- Ljava/lang/String; 表示参数是String字符串 
  13. <span style="white-space:pre">  </span> * -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组 
  14. <span style="white-space:pre">  </span> */  
  15. <span style="white-space:pre">  </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
  16. <span style="white-space:pre">          </span>"(Ljava/lang/String;)[B");  
  17. <span style="white-space:pre">  </span>//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数  
  18. <span style="white-space:pre">  </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,  
  19. <span style="white-space:pre">          </span>strencode); // String .getByte("GB2312");  
  20. <span style="white-space:pre">  </span>//获取数组的长度  
  21. <span style="white-space:pre">  </span>jsize alen = (*env)->GetArrayLength(env, barr);  
  22. <span style="white-space:pre">  </span>//获取数组中的所有的元素 , 存放在 jbyte*数组中  
  23. <span style="white-space:pre">  </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
  24. <span style="white-space:pre">  </span>//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 ‘\0‘  
  25. <span style="white-space:pre">  </span>if (alen > 0) {  
  26. <span style="white-space:pre">      </span>rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"  
  27. <span style="white-space:pre">      </span>memcpy(rtn, ba, alen);  
  28. <span style="white-space:pre">      </span>rtn[alen] = 0;  
  29. <span style="white-space:pre">  </span>}  
  30. <span style="white-space:pre">  </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存  
  31.   
  32.   
  33. <span style="white-space:pre">  </span>return rtn;  
  34. }  

Jstring2CStr方法讲解 : 
a. 获取Java中String类型的class对象 : 参数 : 上下文环境 env, String类完整路径 ;
  1. jclass clsstring = (*env)->FindClass(env, "java/lang/String");  
b.创建Java字符串 : 使用 NewStringUTF 方法;
  1. jstring strencode = (*env)->NewStringUTF(env, "GB2312");  
c.获取String中的getBytes()方法 : 参数介绍 ① env 上下文环境 ② 完整的类路径 ③ 方法名 ④ 方法签名, 方法签名 Ljava/lang/String; 代表参数是String字符串, [B  中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组;
  1. jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
  2.         "(Ljava/lang/String;)[B");  
d. 获取数组的长度 : 
  1. jsize alen = (*env)->GetArrayLength(env, barr);  
e. 获取数组元素 : 获取数组中的所有的元素 , 存放在 jbyte*数组中;
  1. jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
f.数组拷贝: 将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 ‘\0‘;
  1. if (alen > 0) {  
  2.     rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"  
  3.     memcpy(rtn, ba, alen);  
  4.     rtn[alen] = 0;  
  5. }  
g.释放内存 : 
  1. (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存  

.

作者 : 万境绝尘 

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

.



C语言方法 : 注意调用Jstring2CStr方法之后要强转, 否则会出错, Jstring2CStr方法要定义在该方法的前面, C语言中的方法要先声明才能使用;
  1. jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)    
  2. {    
  3.     char *p = (char*)Jstring2CStr(env, str);    
  4.     //打印Java传递过来的数据    
  5.     LOGI("Java JNI string parameter is : %s", p);    
  6.         
  7.     char *append = "append";    
  8.         
  9.     //strcat(dest, source) 函数可以将source字符串 添加到dest字符串后面    
  10.     return (*env)->NewStringUTF(env, strcat(p, append));    
  11. }  

-- 如果没有强转会出现下面的错误 : char *p = Jstring2CStr(env, str);
技术分享

-- 将Jstring2CStr方法定义在主方法下面会出现下面错误 : 

技术分享
Java源码 : 
[java] view plaincopy技术分享技术分享
  1. case R.id.sayHelloInc:    
  2.     Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();    
  3.     break;    

编译之后运行结果 : 

技术分享

技术分享

5. 开发JNI程序流程


a. C语言类库接口 : 存在C语言类库, 调用接口为login_server(char* address, char* username, char* password);
b. 
Java定义本地方法 : public native void LoginServer(String address, String user, String pwd);
c. C语言JNI代码 : Java_包名_类名_LoginServer(JNIEnv* env, jobject obj, jstring address, jstring user, jstring pwd){...调C接口};


注意跨语言字符串转换: JNI方法中, 要将Java的String字符串转为C中的char*字符串;


首先验证C码农提供的代码是否可用 : 验证该api是否可用, 在一个 int main() 函数中进行测试, 根据该测试代码查看方法执行相关的情况;

6. 数组参数处理


模块讲解 : 在该模块中, Java语言传递一个int数组参数给C语言, C语言将这一组参数读取出来, 并且输出到Android的LogCat中, 这里涉及到了两个重要的JNI方法, 一个数获取数组长度方法, 一个是获取数组中每个元素的方法;


获取数组长度方法 : jni中定义 - jsize (*GetArrayLength)(JNIEnv*, jarray);
创建数组相关方法 : 
  1. jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);    
  2. jbyteArray    (*NewByteArray)(JNIEnv*, jsize);    
  3. jcharArray    (*NewCharArray)(JNIEnv*, jsize);    
  4. jshortArray   (*NewShortArray)(JNIEnv*, jsize);    
  5. jintArray     (*NewIntArray)(JNIEnv*, jsize);    
  6. jlongArray    (*NewLongArray)(JNIEnv*, jsize);    
  7. jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);    
  8. jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);    

获取数组元素相关方法 : 
  1. jboolean*   (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);    
  2. jbyte*      (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);    
  3. jchar*      (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);    
  4. jshort*     (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);    
  5. jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);    
  6. jlong*      (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);    
  7. jfloat*     (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);    
  8. jdouble*    (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);   

C语言代码 : 
  1. jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)    
  2. {    
  3.     //获取arr大小    
  4.     int len = (*env)->GetArrayLength(env, arr);    
  5.         
  6.     //在LogCat中打印出arr的大小    
  7.     LOGI("the length of array is %d", len);    
  8.         
  9.     //如果长度为0, 返回arr    
  10.     if(len == 0)    
  11.         return arr;    
  12.             
  13.     //如果长度大于0, 那么获取数组中的每个元素    
  14.     jint* p = (*env)->GetIntArrayElements(env, arr, 0);    
  15.         
  16.     //打印出数组中每个元素的值    
  17.     int i = 0;    
  18.     for(; i < len; i ++)    
  19.     {    
  20.         LOGI("arr[%d] = %d", i, *(p + i));    
  21.     }    
  22.         
  23.     return arr;    
  24.         
  25. }    


Java代码 : 
[java] view plaincopy技术分享技术分享
  1. case R.id.intMethod:    
  2.     int[] array = {12345};    
  3.     dataProvider.intMethod(array);    
  4.     break;    

执行结果 : 上面的那种LogCat竟然启动失败, 只能将就着用这个了;
技术分享

7. 本程序源码


XML布局文件 : 
[html] view plaincopy技术分享技术分享
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent" >  
  6.   
  7.     <Button   
  8.         android:id="@+id/add"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:text="调用 add 本地 方法"  
  12.         android:onClick="onClick"/>  
  13.       
  14.     <Button   
  15.         android:id="@+id/sayHelloInc"  
  16.         android:layout_width="wrap_content"  
  17.         android:layout_height="wrap_content"  
  18.         android:text="调用 sayHelloInc 本地 方法"  
  19.         android:onClick="onClick"/>  
  20.       
  21.     <Button   
  22.         android:id="@+id/intMethod"  
  23.         android:layout_width="wrap_content"  
  24.         android:layout_height="wrap_content"  
  25.         android:text="调用 intMethod 本地 方法"  
  26.         android:onClick="onClick"/>  
  27.   
  28. </LinearLayout>  

Java源码 : 
-- MainActivity源码 : 
[java] view plaincopy技术分享技术分享
  1. package shuliang.han.ndkparameterpassing;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6. import android.widget.Toast;  
  7.   
  8. public class MainActivity extends Activity {  
  9.   
  10.     static{  
  11.         System.loadLibrary("DataProvider");  
  12.     }  
  13.       
  14.     DataProvider dataProvider;  
  15.     @Override  
  16.     public void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.activity_main);  
  19.         dataProvider = new DataProvider();  
  20.     }  
  21.   
  22.     public void onClick(View view) {  
  23.           
  24.         int id = view.getId();  
  25.           
  26.         switch (id) {  
  27.             case R.id.add:  
  28.                 int result = dataProvider.add(12);  
  29.                 Toast.makeText(getApplicationContext(), "the add result : " + result, Toast.LENGTH_LONG).show();  
  30.                 break;  
  31.                   
  32.             case R.id.sayHelloInc:  
  33.                 Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();  
  34.                 break;  
  35.                   
  36.             case R.id.intMethod:  
  37.                 int[] array = {12345};  
  38.                 dataProvider.intMethod(array);  
  39.                 break;  
  40.       
  41.             default:  
  42.                 break;  
  43.         }  
  44.     }  
  45.       
  46. }  
--DataProvider源码 : 
[java] view plaincopy技术分享技术分享
  1. package shuliang.han.ndkparameterpassing;  
  2.   
  3. public class DataProvider {  
  4.   
  5.     //将Java中的两个int值 传给C语言, 进行相加后, 返回java语言 shuliang.han.ndkparameterpassing.DataProvider  
  6.     public native int add(int x, int y);  
  7.       
  8.     //将Java字符串传递给C语言, C语言处理字符串之后, 将处理结果返回给java  
  9.     public native String sayHelloInc(String s);  
  10.       
  11.     //将java中的int数组传递给C语言, C语言为每个元素加10, 返回给Java  
  12.     public native int[] intMethod(int[] nums);   
  13.       
  14. }  

JNI相关源码 : 
-- Android.mk源码 : 
[plain] view plaincopy技术分享技术分享
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := DataProvider  
  6. LOCAL_SRC_FILES := DataProvider.c  
  7. #增加log函数对应的log库  
  8. LOCAL_LDLIBS += -llog   
  9.   
  10. include $(BUILD_SHARED_LIBRARY)  
--DataProvider.c 主程序源码 : 
  1. #include <jni.h>  
  2. #include <string.h>  
  3. #include <android/log.h>  
  4. #define LOG_TAG "System.out"  
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
  7.   
  8. // java中的jstring, 转化为c的一个字符数组  
  9. char* Jstring2CStr(JNIEnv* env, jstring jstr) {  
  10. <span style="white-space:pre">  </span>//声明了一个字符串变量 rtn  
  11. <span style="white-space:pre">  </span>char* rtn = NULL;  
  12. <span style="white-space:pre">  </span>//找到Java中的String的Class对象  
  13. <span style="white-space:pre">  </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");  
  14. <span style="white-space:pre">  </span>//创建一个Java中的字符串 "GB2312"  
  15. <span style="white-space:pre">  </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");  
  16. <span style="white-space:pre">  </span>/* 
  17. <span style="white-space:pre">  </span> * 获取String中定义的方法 getBytes(), 该方法的参数是 String类型的, 返回值是 byte[]数组 
  18. <span style="white-space:pre">  </span> * "(Ljava/lang/String;)[B" 方法前面解析 : 
  19. <span style="white-space:pre">  </span> * -- Ljava/lang/String; 表示参数是String字符串 
  20. <span style="white-space:pre">  </span> * -- [B : 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组 
  21. <span style="white-space:pre">  </span> */  
  22. <span style="white-space:pre">  </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
  23. <span style="white-space:pre">          </span>"(Ljava/lang/String;)[B");  
  24. <span style="white-space:pre">  </span>//调用Java中的getBytes方法, 传入参数介绍 参数②表示调用该方法的对象, 参数③表示方法id , 参数④表示方法参数  
  25. <span style="white-space:pre">  </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,  
  26. <span style="white-space:pre">          </span>strencode); // String .getByte("GB2312");  
  27. <span style="white-space:pre">  </span>//获取数组的长度  
  28. <span style="white-space:pre">  </span>jsize alen = (*env)->GetArrayLength(env, barr);  
  29. <span style="white-space:pre">  </span>//获取数组中的所有的元素 , 存放在 jbyte*数组中  
  30. <span style="white-space:pre">  </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
  31. <span style="white-space:pre">  </span>//将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 ‘\0‘  
  32. <span style="white-space:pre">  </span>if (alen > 0) {  
  33. <span style="white-space:pre">      </span>rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"  
  34. <span style="white-space:pre">      </span>memcpy(rtn, ba, alen);  
  35. <span style="white-space:pre">      </span>rtn[alen] = 0;  
  36. <span style="white-space:pre">  </span>}  
  37. <span style="white-space:pre">  </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //释放内存  
  38.   
  39.   
  40. <span style="white-space:pre">  </span>return rtn;  
  41. }  
  42.   
  43. //方法签名, Java环境 和 调用native方法的类 必不可少, 后面的参数就是native方法的参数  
  44. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)  
  45. {  
  46.     //Java中的int对应的是C语言中的long类型, 对应JNI中的jint类型, C语言中  
  47.     LOGI("JNI_log : x = %d , y = %d" , x , y);  
  48.     return x + y;  
  49. }  
  50.   
  51. jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)  
  52. {  
  53.     char *p = (char*)Jstring2CStr(env, str);  
  54.     //打印Java传递过来的数据  
  55.     LOGI("Java JNI string parameter is : %s", p);  
  56.       
  57.     char *append = "append";  
  58.       
  59.     //strcat(dest, source) 函数可以将source字符串 添加到dest字符串后面  
  60.     return (*env)->NewStringUTF(env, strcat(p, append));  
  61. }  
  62.   
  63. jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)  
  64. {  
  65.     //获取arr大小  
  66.     int len = (*env)->GetArrayLength(env, arr);  
  67.       
  68.     //在LogCat中打印出arr的大小  
  69.     LOGI("the length of array is %d", len);  
  70.       
  71.     //如果长度为0, 返回arr  
  72.     if(len == 0)  
  73.         return arr;  
  74.           
  75.     //如果长度大于0, 那么获取数组中的每个元素  
  76.     jint* p = (*env)->GetIntArrayElements(env, arr, 0);  
  77.       
  78.     //打印出数组中每个元素的值  
  79.     int i = 0;  
  80.     for(; i < len; i ++)  
  81.     {  
  82.         LOGI("arr[%d] = %d", i, *(p + i));  
  83.     }  
  84.       
  85.     return arr;  
  86.       
  87. }  


.



8. 上传代码到GitHub



创建新项目 : han1202012/NDKParameterPassing ;
-- SSH地址 : git@github.com:han1202012/NDKParameterPassing.git ;
-- HTTP地址 : https://github.com/han1202012/NDKParameterPassing.git ;


五. C语言代码回调Java方法

.

C语言回调Java方法场景 : 
-- 复用方法 : 使用Java对象, 复用Java中的方法;
-- 激活Java : C程序后台运行, 该后台程序一直运行, 某个时间出发后需要启动Java服务, 激活Android中的某个界面, 例如使用Intent启动一个Activity;


1. C代码回调Java方法的流程


(1) 找到java对应的Class


创建一个char*数组, 然后使用jni.h中提供的FindClass方法获取jclass返回值;
  1. //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
  2. char* classname = "shulaing/han/ndk_callback/DataProvider";  
  3.   
  4.   
  5. jclass dpclazz = (*env)->FindClass(env, classname);  

(2) 找到要调用的方法的methodID


使用jni.h中提供的GetMethodID方法, 获取jmethodID, 传入参数 ①JNIEnv指针 ②Class对象 ③ 方法名 ④方法签名, 在这里方法名和方法签名确定一个方法, 方法签名就是方法的返回值 与 参数的唯一标示;
  1. //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
  2. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add""(II)I");  

找到静态方法 : 如果方法是静态的, 就使用GetStaticMethod方法获取 
  1. jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)  
  2.     { return functions->GetStaticMethodID(this, clazz, name, sig); }  



(3) 在C语言中调用相应方法


普通方法 : CallTypeMethod , 其中的Type随着返回值类型的不同而改变;
参数介绍 : ① JNIEnv指针 ②调用该native方法的对象 ③方法的methodID ④⑤... 后面是可变参数, 这些参数是
  1. jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);  
  2.   
  3. jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);  
  4. jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  5. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  6. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
  7. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  8. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  9. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
  10. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  11. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  12. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
  13. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  14. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  15. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
  16. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  17. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  18. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
  19. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  20. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  21. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
  22. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  23. jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  24. jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
  25. jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
  26. jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
  27. jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
  28. jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
  29. jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
  30. void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);  
  31. void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  32. void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  

静态方法 : CallStaticTypeMethod, 其中的Type随着返回值类型不同而改变;



.

2. 一些基本代码编写


Java代码 : 定义一个callCcode本地方法, 以及三个Java方法, 在jni中使用本地方法调用Java中的方法;
[java] view plaincopy技术分享技术分享
  1. package shulaing.han.ndk_callback;  
  2.   
  3. public class DataProvider {  
  4.   
  5.     public native void callCcode();  
  6.       
  7.     //C调用java中空方法 shulaing.han.ndk_callback.DataProvider  
  8.   
  9.     public void helloFromJava(){  
  10.         System.out.println("hello from java");  
  11.     }  
  12.       
  13.     //C调用java中的带两个int参数的方法  
  14.     public int Add(int x,int y){  
  15.         return x + y;  
  16.     }  
  17.       
  18.     //C调用java中参数为string的方法  
  19.     public void printString(String s){  
  20.         System.out.println(s);  
  21.     }  
  22.       
  23. }  

生成头文件 : 进入 bin/classed目录, 使用 javah shulaing.han.ndk_callback.DataProvider 命令, 可以在bin/classes下生成头文件;
技术分享

头文件内容 : 文件名 : shulaing_han_ndk_callback_DataProvider.h ;
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class shulaing_han_ndk_callback_DataProvider */  
  4.   
  5. #ifndef _Included_shulaing_han_ndk_callback_DataProvider  
  6. #define _Included_shulaing_han_ndk_callback_DataProvider  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     shulaing_han_ndk_callback_DataProvider 
  12.  * Method:    callCcode 
  13.  * Signature: ()V 
  14.  */  
  15. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  16.   (JNIEnv *, jobject);  
  17.   
  18. #ifdef __cplusplus  
  19. }  
  20. #endif  
  21. #endif  

编写Android.mk文件 : 注意将LogCat日志输出系统动态库加入;
[plain] view plaincopy技术分享技术分享
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := jni  
  6. LOCAL_SRC_FILES := jni.c  
  7. #增加log函数对应的log库  
  8. LOCAL_LDLIBS += -llog   
  9.   
  10. include $(BUILD_SHARED_LIBRARY)  

编写jni的C代码 : 注意加入LogCat相关导入的包;
  1. #include "shulaing_han_ndk_callback_DataProvider.h"  
  2. #include <string.h>  
  3. #include <android/log.h>  
  4. #define LOG_TAG "System.out"  
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  


3. C中回调Java的void返回值方法


使用JNIEnv指针获取Class对象 : 在jni.h文件中找到 - jclass (*FindClass)(JNIEnv*, const char*);
-- 参数介绍 : 第二个参数是类的路径字符串, 如 "/shuliang/han/ndk_callback/DataProvider" ;

获取Java类中定义的method方法 : 在jni.h中找到方法 - jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
-- 参数介绍 : 第二个参数是 Java类的Class对象, 第三个参数是方法名, 第四个参数是Java方法的签名;

方法签名生成工具 : javap , 使用javap -s 命令即可生成方法签名;
技术分享

进入bin/classed目录下 : 执行 javap -s shulaing.han.ndk_callback.DataProvider 命令, 即可显示出每个方法的签名;
[plain] view plaincopy技术分享技术分享
  1. $ javap -s shulaing.han.ndk_callback.DataProvider  
  2. Compiled from "DataProvider.java"  
  3. public class shulaing.han.ndk_callback.DataProvider extends java.lang.Object{  
  4. public shulaing.han.ndk_callback.DataProvider();  
  5.   Signature: ()V  
  6. public native void callCcode();  
  7.   Signature: ()V  
  8. public void helloFromJava();  
  9.   Signature: ()V  
  10. public int Add(int, int);  
  11.   Signature: (II)I  
  12. public void printString(java.lang.String);  
  13.   Signature: (Ljava/lang/String;)V  
  14. }  
截图 : 
技术分享


方法签名介绍 : 
-- 返回值null, 参数null : void helloFromJava() 方法的签名是 "()V", 括号里什么都没有代表参数为null, V代表返回值是void;
-- 返回值int, 参数两个int : int Add(int x,int y) 方法的签名是 "(II)I", 括号中II表示两个int类型参数, 右边括号外的I代表返回值是int类型;
-- 返回值null, 参数String : void printString(String s) 方法签名是 "(Ljava/lang/String;)V", 括号中的Ljava/lang/String; 表示参数是String类型, V表示返回值是void;

jni.h中定义的回调Java方法的相关函数 : 
  1. jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);  
  2.   
  3. jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);  
  4. jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  5. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  6. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
  7. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  8. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  9. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
  10. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  11. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  12. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
  13. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  14. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  15. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
  16. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  17. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  18. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
  19. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  20. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  21. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
  22. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  23. jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  24. jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
  25. jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
  26. jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
  27. jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
  28. jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
  29. jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
  30. void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);  
  31. void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  32. void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  

C语言代码 : 
  1. #include "shulaing_han_ndk_callback_DataProvider.h"  
  2. #include <string.h>  
  3. #include <android/log.h>  
  4. #define LOG_TAG "System.out"  
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
  7.   
  8. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  9.   (JNIEnv * env, jobject obj)  
  10. {  
  11.     //调用DataProvider对象中的helloFromJava()方法  
  12.     //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
  13.     LOGI("in code");  
  14.     //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
  15.     char* classname = "shulaing/han/ndk_callback/DataProvider";  
  16.   
  17.   
  18.     jclass dpclazz = (*env)->FindClass(env, classname);  
  19.     if(dpclazz == 0)  
  20.         LOGI("class not find !!!");  
  21.     else  
  22.         LOGI("class find !!!");  
  23.   
  24.     //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
  25.     jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava""()V");  
  26.     if(methodID == 0)  
  27.             LOGI("method not find !!!");  
  28.         else  
  29.             LOGI("method find !!!");  
  30.   
  31.     /* 
  32.      * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  33.      * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
  34.      */  
  35.     LOGI("before call method");  
  36.     (*env)->CallVoidMethod(env, obj, methodID);  
  37.     LOGI("after call method");  
  38.   
  39. }  

Java代码 : 
--XML布局文件代码 : 
[html] view plaincopy技术分享技术分享
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical"  
  6.     android:paddingBottom="@dimen/activity_vertical_margin"  
  7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  8.     android:paddingRight="@dimen/activity_horizontal_margin"  
  9.     android:paddingTop="@dimen/activity_vertical_margin"  
  10.     tools:context=".MainActivity" >  
  11.   
  12.     <Button  
  13.         android:id="@+id/call_void_method"  
  14.         android:layout_width="wrap_content"  
  15.         android:layout_height="wrap_content"  
  16.         android:onClick="onClick"  
  17.         android:text="C语言回调Java中的空方法" />  
  18.   
  19. </LinearLayout>  
--MainActivity代码 : 
[java] view plaincopy技术分享技术分享
  1. package shulaing.han.ndk_callback;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6.   
  7. public class MainActivity extends Activity {  
  8.   
  9.     static{  
  10.         System.loadLibrary("jni");  
  11.     }  
  12.     DataProvider dp;  
  13.       
  14.     @Override  
  15.     protected void onCreate(Bundle savedInstanceState) {  
  16.         super.onCreate(savedInstanceState);  
  17.         setContentView(R.layout.activity_main);  
  18.         dp = new DataProvider();  
  19.     }  
  20.   
  21.     public void onClick(View view) {  
  22.         int id = view.getId();  
  23.         switch (id) {  
  24.             case R.id.call_void_method:  
  25.                 dp.callCcode();  
  26.                 break;  
  27.       
  28.             default:  
  29.                 break;  
  30.         }  
  31.     }  
  32.   
  33. }  

执行结果 : 
技术分享

.

4. C代码回调Java中带String参数的方法


在DataProvider中添加两个native方法 : 
[java] view plaincopy技术分享技术分享
  1. public native void callCcode();  
  2. public native void callCcode1();  
  3. public native void callCcode2();  

进入bin/classes目录, 使用 javah -jni shulaing.han.ndk_callback.DataProvider 命令生成头文件 : 
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class shulaing_han_ndk_callback_DataProvider */  
  4.   
  5. #ifndef _Included_shulaing_han_ndk_callback_DataProvider  
  6. #define _Included_shulaing_han_ndk_callback_DataProvider  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     shulaing_han_ndk_callback_DataProvider 
  12.  * Method:    callCcode 
  13.  * Signature: ()V 
  14.  */  
  15. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  16.   (JNIEnv *, jobject);  
  17.   
  18. /* 
  19.  * Class:     shulaing_han_ndk_callback_DataProvider 
  20.  * Method:    callCcode1 
  21.  * Signature: ()V 
  22.  */  
  23. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
  24.   (JNIEnv *, jobject);  
  25.   
  26. /* 
  27.  * Class:     shulaing_han_ndk_callback_DataProvider 
  28.  * Method:    callCcode2 
  29.  * Signature: ()V 
  30.  */  
  31. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
  32.   (JNIEnv *, jobject);  
  33.   
  34. #ifdef __cplusplus  
  35. }  
  36. #endif  
  37. #endif  

jni C语言代码 : 这里只需要修改两处, 方法名, 获取方法id中的参数, 调用方法中最后加上一个Java参数;
  1. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
  2.   (JNIEnv *env, jobject obj)  
  3. {  
  4.     //调用DataProvider对象中的helloFromJava()方法  
  5.         //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
  6.         LOGI("in code");  
  7.         //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
  8.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
  9.   
  10.   
  11.         jclass dpclazz = (*env)->FindClass(env, classname);  
  12.         if(dpclazz == 0)  
  13.             LOGI("class not find !!!");  
  14.         else  
  15.             LOGI("class find !!!");  
  16.   
  17.         //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
  18.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString""(Ljava/lang/String;)V");  
  19.         if(methodID == 0)  
  20.                 LOGI("method not find !!!");  
  21.             else  
  22.                 LOGI("method find !!!");  
  23.   
  24.         /* 
  25.          * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  26.          * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
  27.          */  
  28.         LOGI("before call method");  
  29.         (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));  
  30.         LOGI("after call method");  
  31. }  

执行后的结果 : 
技术分享

5. C代码中回调带两个int类型的参数的方法


按照上面的流程, 不同之处就是jni中获取方法 和 方法id , 调用方法的jni函数不同 : 
  1. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
  2.   (JNIEnv *env, jobject obj)  
  3. {  
  4.     //调用DataProvider对象中的helloFromJava()方法  
  5.         //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
  6.         LOGI("in code");  
  7.         //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
  8.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
  9.   
  10.   
  11.         jclass dpclazz = (*env)->FindClass(env, classname);  
  12.         if(dpclazz == 0)  
  13.             LOGI("class not find !!!");  
  14.         else  
  15.             LOGI("class find !!!");  
  16.   
  17.         //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
  18.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add""(II)I");  
  19.         if(methodID == 0)  
  20.                 LOGI("method not find !!!");  
  21.             else  
  22.                 LOGI("method find !!!");  
  23.   
  24.         /* 
  25.          * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  26.          * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
  27.          */  
  28.         LOGI("before call method");  
  29.         (*env)->CallIntMethod(env, obj, methodID, 3, 5);  
  30.         LOGI("after call method");  
  31.   
  32. }  

Java代码 : 
[java] view plaincopy技术分享技术分享
  1. case R.id.call_int_parameter_method:  
  2.     dp.callCcode2();  
  3.     break;  
执行结果 : 
技术分享

.

作者 : 万境绝尘 

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

.


6. 完整源码 


Java源码 : 
-- DataProvider源码 : 
[java] view plaincopy技术分享技术分享
  1. package shulaing.han.ndk_callback;  
  2.   
  3.   
  4. public class DataProvider {  
  5.   
  6.     public native void callCcode();  
  7.     public native void callCcode1();  
  8.     public native void callCcode2();  
  9.       
  10.     //C调用java中空方法 shulaing.han.ndk_callback.DataProvider  
  11.   
  12.     public void helloFromJava(){  
  13.         System.out.println("hello from java");  
  14.     }  
  15.       
  16.     //C调用java中的带两个int参数的方法  
  17.     public int Add(int x,int y){  
  18.         System.out.println("the add result is : " + (x + y));  
  19.         return x + y;  
  20.     }  
  21.       
  22.     //C调用java中参数为string的方法  
  23.     public void printString(String s){  
  24.         System.out.println("in java code :" + s);  
  25.     }  
  26.       
  27. }  
-- MainActivity源码 : 
[java] view plaincopy技术分享技术分享
  1. package shulaing.han.ndk_callback;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6.   
  7. public class MainActivity extends Activity {  
  8.   
  9.     static{  
  10.         System.loadLibrary("jni");  
  11.     }  
  12.     DataProvider dp;  
  13.       
  14.     @Override  
  15.     protected void onCreate(Bundle savedInstanceState) {  
  16.         super.onCreate(savedInstanceState);  
  17.         setContentView(R.layout.activity_main);  
  18.         dp = new DataProvider();  
  19.     }  
  20.   
  21.     public void onClick(View view) {  
  22.         int id = view.getId();  
  23.         switch (id) {  
  24.             case R.id.call_void_method:  
  25.                 dp.callCcode();  
  26.                 break;  
  27.               
  28.             case R.id.call_string_parameter_method:  
  29.                 dp.callCcode1();  
  30.                 break;  
  31.       
  32.             case R.id.call_int_parameter_method:  
  33.                 dp.callCcode2();  
  34.                 break;  
  35.                   
  36.             default:  
  37.                 break;  
  38.         }  
  39.     }  
  40.   
  41. }  

XML布局文件源码 : 
[html] view plaincopy技术分享技术分享
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical"  
  6.     android:paddingBottom="@dimen/activity_vertical_margin"  
  7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  8.     android:paddingRight="@dimen/activity_horizontal_margin"  
  9.     android:paddingTop="@dimen/activity_vertical_margin"  
  10.     tools:context=".MainActivity" >  
  11.   
  12.     <Button  
  13.         android:id="@+id/call_void_method"  
  14.         android:layout_width="wrap_content"  
  15.         android:layout_height="wrap_content"  
  16.         android:onClick="onClick"  
  17.         android:text="C语言回调Java中的空方法" />  
  18.       
  19.     <Button  
  20.         android:id="@+id/call_string_parameter_method"  
  21.         android:layout_width="wrap_content"  
  22.         android:layout_height="wrap_content"  
  23.         android:onClick="onClick"  
  24.         android:text="C语言回调Java中的String参数方法" />  
  25.       
  26.     <Button  
  27.         android:id="@+id/call_int_parameter_method"  
  28.         android:layout_width="wrap_content"  
  29.         android:layout_height="wrap_content"  
  30.         android:onClick="onClick"  
  31.         android:text="C语言回调Java中的int参数方法" />  
  32.   
  33. </LinearLayout>  


jni源码 : 
-- 头文件源码 : 
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class shulaing_han_ndk_callback_DataProvider */  
  4.   
  5. #ifndef _Included_shulaing_han_ndk_callback_DataProvider  
  6. #define _Included_shulaing_han_ndk_callback_DataProvider  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     shulaing_han_ndk_callback_DataProvider 
  12.  * Method:    callCcode 
  13.  * Signature: ()V 
  14.  */  
  15. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  16.   (JNIEnv *, jobject);  
  17.   
  18. /* 
  19.  * Class:     shulaing_han_ndk_callback_DataProvider 
  20.  * Method:    callCcode1 
  21.  * Signature: ()V 
  22.  */  
  23. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
  24.   (JNIEnv *, jobject);  
  25.   
  26. /* 
  27.  * Class:     shulaing_han_ndk_callback_DataProvider 
  28.  * Method:    callCcode2 
  29.  * Signature: ()V 
  30.  */  
  31. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
  32.   (JNIEnv *, jobject);  
  33.   
  34. #ifdef __cplusplus  
  35. }  
  36. #endif  
  37. #endif  
-- Android.mk源码 :
[plain] view plaincopy技术分享技术分享
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := jni  
  6. LOCAL_SRC_FILES := jni.c  
  7. #增加log函数对应的log库  
  8. LOCAL_LDLIBS += -llog   
  9.   
  10. include $(BUILD_SHARED_LIBRARY)  
-- jni主程序源码 :  
  1. #include "shulaing_han_ndk_callback_DataProvider.h"  
  2. #include "first.h"  
  3. #include <string.h>  
  4. #include <android/log.h>  
  5. #define LOG_TAG "System.out"  
  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.   
  9. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  10.   (JNIEnv * env, jobject obj)  
  11. {  
  12.     //调用DataProvider对象中的helloFromJava()方法  
  13.     //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
  14.     LOGI("in code");  
  15.     //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
  16.     char* classname = "shulaing/han/ndk_callback/DataProvider";  
  17.   
  18.   
  19.     jclass dpclazz = (*env)->FindClass(env, classname);  
  20.     if(dpclazz == 0)  
  21.         LOGI("class not find !!!");  
  22.     else  
  23.         LOGI("class find !!!");  
  24.   
  25.     //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
  26.     jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava""()V");  
  27.     if(methodID == 0)  
  28.             LOGI("method not find !!!");  
  29.         else  
  30.             LOGI("method find !!!");  
  31.   
  32.     /* 
  33.      * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  34.      * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
  35.      */  
  36.     LOGI("before call method");  
  37.     (*env)->CallVoidMethod(env, obj, methodID);  
  38.     LOGI("after call method");  
  39.   
  40. }  
  41.   
  42. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
  43.   (JNIEnv *env, jobject obj)  
  44. {  
  45.     //调用DataProvider对象中的helloFromJava()方法  
  46.         //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
  47.         LOGI("in code");  
  48.         //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
  49.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
  50.   
  51.   
  52.         jclass dpclazz = (*env)->FindClass(env, classname);  
  53.         if(dpclazz == 0)  
  54.             LOGI("class not find !!!");  
  55.         else  
  56.             LOGI("class find !!!");  
  57.   
  58.         //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
  59.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString""(Ljava/lang/String;)V");  
  60.         if(methodID == 0)  
  61.                 LOGI("method not find !!!");  
  62.             else  
  63.                 LOGI("method find !!!");  
  64.   
  65.         /* 
  66.          * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  67.          * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
  68.          */  
  69.         LOGI("before call method");  
  70.         (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));  
  71.         LOGI("after call method");  
  72. }  
  73.   
  74. /* 
  75.  * 实际开发的情况 
  76.  * C代码工程师给我们 first.h first.c , 我们只需要将first.h引入, 然后就可以使用其中的方法了 
  77.  */  
  78. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
  79.   (JNIEnv *env, jobject obj)  
  80. {  
  81.     //调用DataProvider对象中的helloFromJava()方法  
  82.         //获取到某个对象, 获取对象中的方法, 调用获取到的方法  
  83.         LOGI("in code");  
  84.         //DataProvider完整类名 shulaing.han.ndk_callback.DataProvider  
  85.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
  86.   
  87.   
  88.         jclass dpclazz = (*env)->FindClass(env, classname);  
  89.         if(dpclazz == 0)  
  90.             LOGI("class not find !!!");  
  91.         else  
  92.             LOGI("class find !!!");  
  93.   
  94.         //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method  
  95.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add""(II)I");  
  96.         if(methodID == 0)  
  97.                 LOGI("method not find !!!");  
  98.             else  
  99.                 LOGI("method find !!!");  
  100.   
  101.         /* 
  102.          * 调用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  103.          * 参数介绍 : 后面的 ... 是可变参数, 如果该返回值void的方法有参数, 就将参数按照次序排列 
  104.          */  
  105.         LOGI("before call method");  
  106.         (*env)->CallIntMethod(env, obj, methodID, 3, 5);  
  107.         LOGI("after call method");  
  108.   
  109. }  

7. 将程序上传到GitHub中

GitHub地址 : 
-- SSH : git@github.com:han1202012/NDK_Callback.git
-- HTTP : https://github.com/han1202012/NDK_Callback.git
.


.

六. 实际开发中的环境


这里举一个简单的小例子 : 
-- 在实际开发中, C工程师会给我们c文件如下 : first.h first.c, 一个C主程序, 一个头文件, 我们只需要将这个头文件引入到jni中的C代码中即可, 在我们自定义生成的签名函数中调用 first.h中的方法;

first.h源码 : 
  1. #ifndef FIRST_H  
  2. #define FIRST_H  
  3.   
  4. extern int first(int  x, int  y);  
  5.   
  6. #endif /* FIRST_H */  

first.c源码 : 
  1. #include "first.h"  
  2.   
  3. int  first(int  x, int  y)  
  4. {  
  5.     return x + y;  
  6. }  

在签名函数中, 直接调用 first()方法即可
;

.

七 分析Log日志系统框架的JNI代码



在这里分析日志输出函数 : Log.i(TAG, "log"), 分析该日志系统的JNI层源码结构;

这里使用下载的Android2.3.3源码进行分析 : 在 http://blog.csdn.net/shulianghan/article/details/17350401 中介绍了如何使用repo 和 git 下载Android源码 和 kernel 源码;

LogJNI调用层次 : android.util.Log.java 中的接口 是通过JNI调用 本地库 并最终调用内核驱动程序 Logger 将日志信息写到 内核空间中.

分析的源码文件 : "\" 代表Android源代码的本目录;
-- Java代码 : \frameworks\base\core\java\android\util\Log.java
-- JNI层实现代码 : \frameworks\base\core\jni\android_util_Log.cpp
下面的是Android自定义的JNI规范相关的源码 : 
-- JNI规范头文件 : \dalvik\libnativehelper\include\nativehelper\jni.h
-- JNI帮助文件 : ① \dalvik\libnativehelper\include\nativehelper\JNIHelp.h  ② \dalvik\libnativehelper\JNIHelp.c
-- JNI运行时文件 :  \frameworks\base\core\jni\AndroidRuntime.cpp

这里将上面几个文件上传到CSDN资源中, 便于查看 : http://download.csdn.net/detail/han1202012/6905507 ;

1. 分析Log.java源码


Log.java分析 : 在Log.java文件中,定义了 isLoggable 和 println_native 两个Native方法, 在Java方法中, 只需要事先声明native方法, 不用实现方法体, 可以直接调用;
Log.java在Android源码中的位置 : \frameworks\base\core\java\android\util\Log.java

Log.java内容 : 
[java] view plaincopy技术分享技术分享
  1. package android.util;  
  2.   
  3. import com.android.internal.os.RuntimeInit;  
  4.   
  5. import java.io.PrintWriter;  
  6. import java.io.StringWriter;  
  7. public final class Log {  
  8.   
  9.     ... ...  
  10.       
  11.     //打印日志  
  12.     public static int d(String tag, String msg) {  
  13.         return println_native(LOG_ID_MAIN, DEBUG, tag, msg);  
  14.     }  
  15.   
  16.     //打印日志和异常  
  17.     public static int d(String tag, String msg, Throwable tr) {  
  18.         return println_native(LOG_ID_MAIN, DEBUG, tag, msg + ‘\n‘ + getStackTraceString(tr));  
  19.     }  
  20.   
  21.     //打印日志  
  22.     public static int i(String tag, String msg) {  
  23.         return println_native(LOG_ID_MAIN, INFO, tag, msg);  
  24.     }  
  25.   
  26.     ... ...  
  27.       
  28.     //声明native方法  
  29.     public static native boolean isLoggable(String tag, int level);  
  30.   
  31.     ... ...  
  32.     
  33.     /** @hide */ public static final int LOG_ID_MAIN = 0;  
  34.     /** @hide */ public static final int LOG_ID_RADIO = 1;  
  35.     /** @hide */ public static final int LOG_ID_EVENTS = 2;  
  36.     /** @hide */ public static final int LOG_ID_SYSTEM = 3;  
  37.   
  38.     //声明native方法  
  39.     /** @hide */ public static native int println_native(int bufID,  
  40.             int priority, String tag, String msg);  
  41. }  

2. 分析Log系统JNI层源码


JNI层方法: JNI层方法根据一定规则与Java层声明的Native方法进行映射, 然后可以通过JNIEnv指针提供的JNI函数对Java层进行操作;
Log系统的JNI层文件是 : android_util_Log.cpp, 该文件路径 :\frameworks\base\core\jni\android_util_Log.cpp 代码如下 :
  1. #define LOG_NAMESPACE "log.tag."  
  2. #define LOG_TAG "Log_println"  
  3.   
  4. #include <assert.h>  
  5. #include <cutils/properties.h>  
  6. #include <utils/Log.h>  
  7. #include <utils/String8.h>  
  8.   
  9. #include "jni.h"  
  10. #include "utils/misc.h"  
  11. #include "android_runtime/AndroidRuntime.h"  
  12.   
  13. ... ...  
  14.   
  15. namespace android {  
  16.   
  17. struct levels_t {  
  18.     jint verbose;  
  19.     jint debug;  
  20.     jint info;  
  21.     jint warn;  
  22.     jint error;  
  23.     jint assert;  
  24. };  
  25. static levels_t levels;  
  26.   
  27. static int toLevel(const char* value)   
  28. {  
  29.     switch (value[0]) {  
  30.         case ‘V‘return levels.verbose;  
  31.         case ‘D‘return levels.debug;  
  32.         case ‘I‘return levels.info;  
  33.         case ‘W‘return levels.warn;  
  34.         case ‘E‘return levels.error;  
  35.         case ‘A‘return levels.assert;  
  36.         case ‘S‘return -1; // SUPPRESS  
  37.     }  
  38.     return levels.info;  
  39. }  
  40.   
  41. /* 
  42.     实现Java层声明的 isLoggable 方法, 注意方法名不符合标准JNI规范 
  43.     标准的JNI规范方法名应该是 Java_包名_类名_方法名 
  44.     其中传入了JNIEnv 和 jobject 参数, JNIEnv参数是Java运行环境, 可以与JVM进行交互 
  45.     jobject参数是包含Native方法的Java类对象 
  46.     该方法中可以通过JNIEnv调用本地库进行函数处理, 最后返回给Java层函数 
  47. */  
  48. static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)  
  49. {  
  50. #ifndef HAVE_ANDROID_OS  
  51.     return false;  
  52. #else /* HAVE_ANDROID_OS */  
  53.     int len;  
  54.     char key[PROPERTY_KEY_MAX];  
  55.     char buf[PROPERTY_VALUE_MAX];  
  56.   
  57.     if (tag == NULL) {  
  58.         return false;  
  59.     }  
  60.       
  61.     jboolean result = false;  
  62.       
  63.     //调用了JNI函数  
  64.     const char* chars = env->GetStringUTFChars(tag, NULL);  
  65.   
  66.     if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {  
  67.         jclass clazz = env->FindClass("java/lang/IllegalArgumentException");  
  68.         char buf2[200];  
  69.         snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",  
  70.                 chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));  
  71.   
  72.         // release the chars!  
  73.         env->ReleaseStringUTFChars(tag, chars);  
  74.   
  75.         env->ThrowNew(clazz, buf2);  
  76.         return false;  
  77.     } else {  
  78.         strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);  
  79.         strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);  
  80.     }  
  81.       
  82.     env->ReleaseStringUTFChars(tag, chars);  
  83.   
  84.     len = property_get(key, buf, "");  
  85.     int logLevel = toLevel(buf);  
  86.     return (logLevel >= 0 && level >= logLevel) ? true : false;  
  87. #endif /* HAVE_ANDROID_OS */  
  88. }  
  89.   
  90. /* 
  91.  * In class android.util.Log: 
  92.  *  public static native int println_native(int buffer, int priority, String tag, String msg) 
  93.  */  
  94. static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,  
  95.         jint bufID, jint priority, jstring tagObj, jstring msgObj)  
  96. {  
  97.     const char* tag = NULL;  
  98.     const char* msg = NULL;  
  99.   
  100.     if (msgObj == NULL) {  
  101.         jclass npeClazz;  
  102.   
  103.         npeClazz = env->FindClass("java/lang/NullPointerException");  
  104.         assert(npeClazz != NULL);  
  105.   
  106.         env->ThrowNew(npeClazz, "println needs a message");  
  107.         return -1;  
  108.     }  
  109.   
  110.     if (bufID < 0 || bufID >= LOG_ID_MAX) {  
  111.         jclass npeClazz;  
  112.   
  113.         npeClazz = env->FindClass("java/lang/NullPointerException");  
  114.         assert(npeClazz != NULL);  
  115.   
  116.         env->ThrowNew(npeClazz, "bad bufID");  
  117.         return -1;  
  118.     }  
  119.   
  120.     if (tagObj != NULL)  
  121.         tag = env->GetStringUTFChars(tagObj, NULL);  //调用JNI函数  
  122.     msg = env->GetStringUTFChars(msgObj, NULL);  
  123.   
  124.     int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);  
  125.   
  126.     if (tag != NULL)  
  127.         env->ReleaseStringUTFChars(tagObj, tag); //调用JNI函数释放资源  
  128.     env->ReleaseStringUTFChars(msgObj, msg); //调用JNI函数释放资源  
  129.   
  130.     return res;  
  131. }  
  132.   
  133. /* 
  134.  * JNI registration. JNI方法注册 
  135.  */  
  136. static JNINativeMethod gMethods[] = {  
  137.     /* name, signature, funcPtr */  
  138.     { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },  
  139.     { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },  
  140. };  
  141.   
  142. int register_android_util_Log(JNIEnv* env)  
  143. {  
  144.     jclass clazz = env->FindClass("android/util/Log");  
  145.   
  146.     if (clazz == NULL) {  
  147.         LOGE("Can‘t find android/util/Log");  
  148.         return -1;  
  149.     }  
  150.       
  151.     levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE""I"));  
  152.     levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG""I"));  
  153.     levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO""I"));  
  154.     levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN""I"));  
  155.     levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR""I"));  
  156.     levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT""I"));  
  157.                   
  158.     return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));  
  159. }  
  160.   
  161. }; // namespace android  

3. 声明JNI 与 Native 方法的映射关系


标准JNI规范 : 在标准的JNI规范中, Java中的Native方法 与 JNI层方法 是通过方法名的对应关系进行映射的, 我们通过 javah 工具生成JNI层头文件, 头文件中定义了规范的JNI层方法名, 这个方法名就与Java Native方法对应;

Android自定义规范 : 在 \dalvik\libnativehelper\include\nativehelper\jni.h 中定义了这样的映射关系 : 
  1. typedef struct {  
  2.     const char* name;       //Java层Native函数方法名  
  3.     const char* signature;  //Java层Native函数的签名  
  4.     void*       fnPtr;      //JNI层实现的方法  
  5. } JNINativeMethod;  
.
JNINativeMethod类型数据 : 在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. };  

JNINativeMethod结构体作用 : JNINativeMethod是一个结构体类型, 声明了Native方法 与 JNI方法 的映射关系;
-- 解析上面的数组中的元素 : 
 --- Native方法 : "isLoggable" 是Java中声明的Native方法; 
 --- 方法签名 : "(Ljava/lang/String;I)Z" 表示该方法的签名, 参数是String类型 和 int类型, Z 表示 boolean类型;
 --- JNI方法 : (void*) android_util_Log_isLoggable 表示JNI层实现的方法指针;

4. 注册JNI方法到虚拟机中

映射关系体现到虚拟机中 :  在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 -1;  
  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(env, "android/util/Log", gMethods, NELEM(gMethods)) 方法注册JNI方法;

register_android_util_Log调用时机 : 该函数是在Android系统启动的时候, 通过AndroidRuntime.cpp中的register_jni_proocs方法执行, 执行该方法的时候会将 Native方法 与 JNI方法 的函数映射关系注册给 Dalvik 虚拟机;


5. 解析registerNativeMethod函数


该函数定义在AndroidRuntime.cpp中 : 该文件的路径在 \frameworks\base\core\jni\AndroidRuntime.cpp ;
  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. }  

registerNativeMethods 方法只是对 jniRegisterNativeMethods 方法的封装, 在JNIHelp.h中找到该方法的声明
  1. /* 
  2.  * Register one or more native methods with a particular class. 
  3.  */  
  4. int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,  
  5.     const JNINativeMethod* gMethods, int numMethods);  

在JNIHelp.c 中找到该方法的实现 : 最终方法中调用了 JNIEnv 的RegisterNatives 函数, 将gMethods中存放的JNINativeMethod结构体(存放Native方法 与 JNI方法关联信息) 传递到java虚拟机;
  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 -1;  
  16.     }  
  17.   
  18.     int result = 0;  
  19.     if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {  
  20.         LOGE("RegisterNatives failed for ‘%s‘\n", className);  
  21.         result = -1;  
  22.     }  
  23.   
  24.     (*env)->DeleteLocalRef(env, clazz);  
  25.     return result;  
  26. }  


6. JNI的规范


Android中JNI存在两种规范 : 一种是标准的JNI规范, 多在应用层使用; 另一种是Android中自定义的规范, 多使用在应用框架层;
-- JNI标准规范: 遵守JNI标准规函数命名方式, JNI中方法命名为 Java_包名_类名_方法名 , 可以使用javah生成签名头文件, 靠这种方式实现 Native方法 与 JNI方法之间的映射关系, 即应用直接与框架层进行交互, 这种规范常用与应用开发;
-- 函数注册规范 : 这是Android自定义的一种规范, 应用框架层采用该规范, 即应用框架层 与 框架层 进行交互, 底层源码开发多使用该规范;

NDK开发基本知识

标签:

原文地址:http://blog.csdn.net/u013037007/article/details/50992497

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!