标签:
(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文件如下 : 只改变了返回的字符串, 添加了中文;
-
#include <jni.h>
-
-
-
-
-
-
-
-
-
-
jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)
-
{
-
-
-
-
-
return (*env)->NewStringUTF(env, "hello world jni 中文");
-
}
使用NDK重新编译hello.c文件 : 修改了C源码之后, 重新将该c文件编译成so文件;
-- 编译过程: 打开cygwin, 进入cygdrive/ 下对应windows中源码项目中的jni目录, 执行 /android-ndk-r9c/ndk-build 命令;
运行Android代码报错 : 因为jni中c文件有中文, 中文不能被识别;
-
01-31 14:36:04.803: W/dalvikvm(389): JNI WARNING: illegal continuation byte 0xd0
-
01-31 14:36:04.803: W/dalvikvm(389): string: ‘hello world jni ????‘
-
01-31 14:36:04.803: W/dalvikvm(389): in Lshuliang/han/ndkhelloworld/MainActivity;.helloFromJNI ()Ljava/lang/String; (NewStringUTF)
-
01-31 14:36:04.834: I/dalvikvm(389): "main" prio=5 tid=1 NATIVE
-
01-31 14:36:04.834: I/dalvikvm(389): | group="main" sCount=0 dsCount=0 obj=0x4001f1a8 self=0xce48
-
01-31 14:36:04.834: I/dalvikvm(389): | sysTid=389 nice=0 sched=0/0 cgrp=default handle=-1345006528
-
01-31 14:36:04.844: I/dalvikvm(389): | schedstat=( 257006717 305462830 51 )
-
01-31 14:36:04.844: I/dalvikvm(389): at shuliang.han.ndkhelloworld.MainActivity.helloFromJNI(Native Method)
-
01-31 14:36:04.844: I/dalvikvm(389): at shuliang.han.ndkhelloworld.MainActivity.onCreate(MainActivity.java:26)
-
01-31 14:36:04.844: I/dalvikvm(389): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
-
01-31 14:36:04.853: I/dalvikvm(389): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)
-
01-31 14:36:04.853: I/dalvikvm(389): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
-
01-31 14:36:04.853: I/dalvikvm(389): at android.app.ActivityThread.access$1500(ActivityThread.java:117)
-
01-31 14:36:04.864: I/dalvikvm(389): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
-
01-31 14:36:04.864: I/dalvikvm(389): at android.os.Handler.dispatchMessage(Handler.java:99)
-
01-31 14:36:04.864: I/dalvikvm(389): at android.os.Looper.loop(Looper.java:123)
-
01-31 14:36:04.864: I/dalvikvm(389): at android.app.ActivityThread.main(ActivityThread.java:3683)
-
01-31 14:36:04.864: I/dalvikvm(389): at java.lang.reflect.Method.invokeNative(Native Method)
-
01-31 14:36:04.874: I/dalvikvm(389): at java.lang.reflect.Method.invoke(Method.java:507)
-
01-31 14:36:04.874: I/dalvikvm(389): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
-
01-31 14:36:04.874: I/dalvikvm(389): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
-
01-31 14:36:04.874: I/dalvikvm(389): at dalvik.system.NativeStart.main(Native Method)
-
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;
-
struct _JNIEnv;
-
struct _JavaVM;
-
typedef const struct JNINativeInterface* C_JNIEnv;
-
-
#if defined(__cplusplus) //为了兼容C 和 C++两种代码 使用该 宏加以区分
-
typedef _JNIEnv JNIEnv;
-
typedef _JavaVM JavaVM;
-
#else
-
typedef const struct JNINativeInterface* JNIEnv;
-
typedef const struct JNIInvokeInterface* JavaVM;
-
#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操作的相关方法 :
-
-
-
-
struct JNINativeInterface {
-
void* reserved0;
-
void* reserved1;
-
-
... ...
-
-
jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,
-
va_list);
-
jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID,
-
jvalue*);
-
jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
-
jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);
-
-
... ...
-
-
void* (*GetDirectBufferAddress)(JNIEnv*, jobject);
-
jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject);
-
-
-
jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
-
};
(3) C++中的JNIEnv
C++ 中的JNIEnv: C++ 中的JNIEnv 就是 _JNIEnv 结构体, 二者是等同的; 因此在调用 JNI函数的时候, 只需要使用 env->NewStringUTF(env, "hello")方法即可, 不用在进行*运算;
.
-
-
-
-
-
-
-
struct _JNIEnv {
-
-
const struct JNINativeInterface* functions;
-
-
#if defined(__cplusplus)
-
-
jint GetVersion()
-
{ return functions->GetVersion(this); }
-
-
jlong GetDirectBufferCapacity(jobject buf)
-
{ return functions->GetDirectBufferCapacity(this, buf); }
-
-
-
jobjectRefType GetObjectRefType(jobject obj)
-
{ return functions->GetObjectRefType(this, obj); }
-
#endif /*__cplusplus*/
-
};
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 中都有相应的定义 :
-
jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
-
jbyteArray (*NewByteArray)(JNIEnv*, jsize);
-
jcharArray (*NewCharArray)(JNIEnv*, jsize);
-
jshortArray (*NewShortArray)(JNIEnv*, jsize);
-
jintArray (*NewIntArray)(JNIEnv*, jsize);
-
jlongArray (*NewLongArray)(JNIEnv*, jsize);
-
jfloatArray (*NewFloatArray)(JNIEnv*, jsize);
-
jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize);
2. JNI在Java和C语言之间传递int类型
Java中定义的方法 :
-
-
public native int add(int x, int y);
C语言中定义的方法 :
-
#include <jni.h>
-
-
-
jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
-
{
-
return x + y;
-
}
使用NDK工具变异该c类库 :
在cygwin中进入cygdrive, 然后进入windows中相应的目录, 执行 /android-ndk-r9c/ndk-build 命令,
即可完成编译;
3. NDK中C代码使用LogCat
(1) 引入头文件
NDK中断点调试 : 断点调试在NDK中实现极其困难, 因此在这里我们一般都是打印日志;
引入头文件 : 在C代码中引入下面的头文件;
-
#include <android/log.h>
-
#define LOG_TAG "System.out"
-
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-
#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 方法中的日志等级参数就使用 枚举中的内容
-
-
-
-
typedef enum android_LogPriority {
-
ANDROID_LOG_UNKNOWN = 0,
-
ANDROID_LOG_DEFAULT,
-
ANDROID_LOG_VERBOSE,
-
ANDROID_LOG_DEBUG,
-
ANDROID_LOG_INFO,
-
ANDROID_LOG_WARN,
-
ANDROID_LOG_ERROR,
-
ANDROID_LOG_FATAL,
-
ANDROID_LOG_SILENT,
-
} android_LogPriority;
-
-
-
-
-
-
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文件 :
-
LOCAL_PATH := $(call my-dir)
-
-
include $(CLEAR_VARS)
-
-
LOCAL_MODULE := DataProvider
-
LOCAL_SRC_FILES := DataProvider.c
-
#增加log函数对应的函数库 liblog.so libthread_db.a
-
LOCAL_LDLIBS += -llog -lthread_db
-
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) 中的占位符, 编写打印日志代码:
-
-
LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);
最终的包含打印日志的完整代码 : 注意, 这里有一处可能错误, 如果是32位机器, int类型占位符使用 %d 即可;
-
#include <jni.h>
-
#include <android/log.h>
-
#define LOG_TAG "System.out"
-
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-
-
-
-
jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
-
{
-
-
LOGI("JNI_日志 : x = %ld , y = %ld" , x , y);
-
return x + y;
-
}
重新编译C文件 : 执行
/android-ndk-r9c/ndk-build命令;
-- 第一次编译 : 出现警告, long int占位符行不通, 注意区分机器位长, 64位 与 32位不同, 这样编译出现的结果就不会打印日志;
-- 第二次编译 : 将占位符改为 %d ;
执行按钮之后打印的日志 : 虽然有乱码, 不过显示出来了;
4. 字符串处理
.
Java中的String转为C语言中的char字符串 : 下面的工具方法可以在C程序中解决这个问题;
-
-
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>char* rtn = NULL;
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");
-
<span style="white-space:pre"> </span>
-
-
-
-
-
-
<span style="white-space:pre"> </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
-
<span style="white-space:pre"> </span>"(Ljava/lang/String;)[B");
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
-
<span style="white-space:pre"> </span>strencode);
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jsize alen = (*env)->GetArrayLength(env, barr);
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>if (alen > 0) {
-
<span style="white-space:pre"> </span>rtn = (char*) malloc(alen + 1);
-
<span style="white-space:pre"> </span>memcpy(rtn, ba, alen);
-
<span style="white-space:pre"> </span>rtn[alen] = 0;
-
<span style="white-space:pre"> </span>}
-
<span style="white-space:pre"> </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0);
-
-
-
<span style="white-space:pre"> </span>return rtn;
-
}
Jstring2CStr方法讲解 :
a. 获取Java中String类型的class对象 : 参数 : 上下文环境 env, String类完整路径 ;
-
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
b.创建Java字符串 : 使用 NewStringUTF 方法;
-
jstring strencode = (*env)->NewStringUTF(env, "GB2312");
c.
获取String中的getBytes()方法 : 参数介绍 ① env 上下文环境 ② 完整的类路径 ③ 方法名 ④ 方法签名, 方法签名 Ljava/lang/String; 代表参数是String字符串, [B 中括号表示这是一个数组, B代表byte类型, 返回值是一个byte数组;
-
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
-
"(Ljava/lang/String;)[B");
d.
获取数组的长度 :
-
jsize alen = (*env)->GetArrayLength(env, barr);
e. 获取数组元素 : 获取数组中的所有的元素 , 存放在 jbyte*数组中;
-
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
f.
数组拷贝: 将Java数组中所有元素拷贝到C的char*数组中, 注意C语言数组结尾要加一个 ‘\0‘;
-
if (alen > 0) {
-
rtn = (char*) malloc(alen + 1);
-
memcpy(rtn, ba, alen);
-
rtn[alen] = 0;
-
}
g.
释放内存 :
-
(*env)->ReleaseByteArrayElements(env, barr, ba, 0);
C语言方法 : 注意调用Jstring2CStr方法之后要强转, 否则会出错, Jstring2CStr方法要定义在该方法的前面, C语言中的方法要先声明才能使用;
-
jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)
-
{
-
char *p = (char*)Jstring2CStr(env, str);
-
-
LOGI("Java JNI string parameter is : %s", p);
-
-
char *append = "append";
-
-
-
return (*env)->NewStringUTF(env, strcat(p, append));
-
}
--
如果没有强转会出现下面的错误 : char *p = Jstring2CStr(env, str);
--
将Jstring2CStr方法定义在主方法下面会出现下面错误 :
Java源码 :
-
case R.id.sayHelloInc:
-
Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();
-
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);
创建数组相关方法 :
-
jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
-
jbyteArray (*NewByteArray)(JNIEnv*, jsize);
-
jcharArray (*NewCharArray)(JNIEnv*, jsize);
-
jshortArray (*NewShortArray)(JNIEnv*, jsize);
-
jintArray (*NewIntArray)(JNIEnv*, jsize);
-
jlongArray (*NewLongArray)(JNIEnv*, jsize);
-
jfloatArray (*NewFloatArray)(JNIEnv*, jsize);
-
jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize);
获取数组元素相关方法 :
-
jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);
-
jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);
-
jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);
-
jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);
-
jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
-
jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);
-
jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);
-
jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);
C语言代码 :
-
jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)
-
{
-
-
int len = (*env)->GetArrayLength(env, arr);
-
-
-
LOGI("the length of array is %d", len);
-
-
-
if(len == 0)
-
return arr;
-
-
-
jint* p = (*env)->GetIntArrayElements(env, arr, 0);
-
-
-
int i = 0;
-
for(; i < len; i ++)
-
{
-
LOGI("arr[%d] = %d", i, *(p + i));
-
}
-
-
return arr;
-
-
}
Java代码 :
-
case R.id.intMethod:
-
int[] array = {1, 2, 3, 4, 5};
-
dataProvider.intMethod(array);
-
break;
执行结果 : 上面的那种LogCat竟然启动失败, 只能将就着用这个了;
7. 本程序源码
XML布局文件 :
-
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
xmlns:tools="http://schemas.android.com/tools"
-
android:orientation="vertical"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent" >
-
-
<Button
-
android:id="@+id/add"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:text="调用 add 本地 方法"
-
android:onClick="onClick"/>
-
-
<Button
-
android:id="@+id/sayHelloInc"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:text="调用 sayHelloInc 本地 方法"
-
android:onClick="onClick"/>
-
-
<Button
-
android:id="@+id/intMethod"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:text="调用 intMethod 本地 方法"
-
android:onClick="onClick"/>
-
-
</LinearLayout>
Java源码 :
-- MainActivity源码 :
-
package shuliang.han.ndkparameterpassing;
-
-
import android.app.Activity;
-
import android.os.Bundle;
-
import android.view.View;
-
import android.widget.Toast;
-
-
public class MainActivity extends Activity {
-
-
static{
-
System.loadLibrary("DataProvider");
-
}
-
-
DataProvider dataProvider;
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
dataProvider = new DataProvider();
-
}
-
-
public void onClick(View view) {
-
-
int id = view.getId();
-
-
switch (id) {
-
case R.id.add:
-
int result = dataProvider.add(1, 2);
-
Toast.makeText(getApplicationContext(), "the add result : " + result, Toast.LENGTH_LONG).show();
-
break;
-
-
case R.id.sayHelloInc:
-
Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();
-
break;
-
-
case R.id.intMethod:
-
int[] array = {1, 2, 3, 4, 5};
-
dataProvider.intMethod(array);
-
break;
-
-
default:
-
break;
-
}
-
}
-
-
}
--
DataProvider源码 :
-
package shuliang.han.ndkparameterpassing;
-
-
public class DataProvider {
-
-
-
public native int add(int x, int y);
-
-
-
public native String sayHelloInc(String s);
-
-
-
public native int[] intMethod(int[] nums);
-
-
}
JNI相关源码 :
-- Android.mk源码 :
-
LOCAL_PATH := $(call my-dir)
-
-
include $(CLEAR_VARS)
-
-
LOCAL_MODULE := DataProvider
-
LOCAL_SRC_FILES := DataProvider.c
-
#增加log函数对应的log库
-
LOCAL_LDLIBS += -llog
-
-
include $(BUILD_SHARED_LIBRARY)
--
DataProvider.c 主程序源码 :
-
#include <jni.h>
-
#include <string.h>
-
#include <android/log.h>
-
#define LOG_TAG "System.out"
-
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-
-
-
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>char* rtn = NULL;
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");
-
<span style="white-space:pre"> </span>
-
-
-
-
-
-
<span style="white-space:pre"> </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
-
<span style="white-space:pre"> </span>"(Ljava/lang/String;)[B");
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
-
<span style="white-space:pre"> </span>strencode);
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jsize alen = (*env)->GetArrayLength(env, barr);
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
-
<span style="white-space:pre"> </span>
-
<span style="white-space:pre"> </span>if (alen > 0) {
-
<span style="white-space:pre"> </span>rtn = (char*) malloc(alen + 1);
-
<span style="white-space:pre"> </span>memcpy(rtn, ba, alen);
-
<span style="white-space:pre"> </span>rtn[alen] = 0;
-
<span style="white-space:pre"> </span>}
-
<span style="white-space:pre"> </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0);
-
-
-
<span style="white-space:pre"> </span>return rtn;
-
}
-
-
-
jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
-
{
-
-
LOGI("JNI_log : x = %d , y = %d" , x , y);
-
return x + y;
-
}
-
-
jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)
-
{
-
char *p = (char*)Jstring2CStr(env, str);
-
-
LOGI("Java JNI string parameter is : %s", p);
-
-
char *append = "append";
-
-
-
return (*env)->NewStringUTF(env, strcat(p, append));
-
}
-
-
jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)
-
{
-
-
int len = (*env)->GetArrayLength(env, arr);
-
-
-
LOGI("the length of array is %d", len);
-
-
-
if(len == 0)
-
return arr;
-
-
-
jint* p = (*env)->GetIntArrayElements(env, arr, 0);
-
-
-
int i = 0;
-
for(; i < len; i ++)
-
{
-
LOGI("arr[%d] = %d", i, *(p + i));
-
}
-
-
return arr;
-
-
}
.
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返回值;
-
-
char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
-
jclass dpclazz = (*env)->FindClass(env, classname);
(2) 找到要调用的方法的methodID
使用jni.h中提供的GetMethodID方法, 获取jmethodID, 传入参数 ①JNIEnv指针 ②Class对象 ③ 方法名 ④方法签名, 在这里方法名和方法签名确定一个方法, 方法签名就是方法的返回值 与 参数的唯一标示;
-
-
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
找到静态方法 : 如果方法是静态的, 就使用GetStaticMethod方法获取
-
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
-
{ return functions->GetStaticMethodID(this, clazz, name, sig); }
(3) 在C语言中调用相应方法
普通方法 : CallTypeMethod , 其中的Type随着返回值类型的不同而改变;
参数介绍 : ① JNIEnv指针 ②调用该native方法的对象 ③方法的methodID ④⑤... 后面是可变参数, 这些参数是
-
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
-
-
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
-
jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
-
jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
-
jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
-
jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
-
jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
-
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
-
void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
静态方法 : CallStaticTypeMethod, 其中的Type随着返回值类型不同而改变;
.
2. 一些基本代码编写
Java代码 : 定义一个callCcode本地方法, 以及三个Java方法, 在jni中使用本地方法调用Java中的方法;
-
package shulaing.han.ndk_callback;
-
-
public class DataProvider {
-
-
public native void callCcode();
-
-
-
-
public void helloFromJava(){
-
System.out.println("hello from java");
-
}
-
-
-
public int Add(int x,int y){
-
return x + y;
-
}
-
-
-
public void printString(String s){
-
System.out.println(s);
-
}
-
-
}
生成头文件 : 进入 bin/classed目录, 使用
javah shulaing.han.ndk_callback.DataProvider 命令, 可以在bin/classes下生成头文件;
头文件内容 : 文件名 : shulaing_han_ndk_callback_DataProvider.h ;
-
-
#include <jni.h>
-
-
-
#ifndef _Included_shulaing_han_ndk_callback_DataProvider
-
#define _Included_shulaing_han_ndk_callback_DataProvider
-
#ifdef __cplusplus
-
extern "C" {
-
#endif
-
-
-
-
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
-
(JNIEnv *, jobject);
-
-
#ifdef __cplusplus
-
}
-
#endif
-
#endif
编写Android.mk文件 : 注意将LogCat日志输出系统动态库加入;
-
LOCAL_PATH := $(call my-dir)
-
-
include $(CLEAR_VARS)
-
-
LOCAL_MODULE := jni
-
LOCAL_SRC_FILES := jni.c
-
#增加log函数对应的log库
-
LOCAL_LDLIBS += -llog
-
-
include $(BUILD_SHARED_LIBRARY)
编写jni的C代码 : 注意加入LogCat相关导入的包;
-
#include "shulaing_han_ndk_callback_DataProvider.h"
-
#include <string.h>
-
#include <android/log.h>
-
#define LOG_TAG "System.out"
-
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-
#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 命令, 即可显示出每个方法的签名;
-
$ javap -s shulaing.han.ndk_callback.DataProvider
-
Compiled from "DataProvider.java"
-
public class shulaing.han.ndk_callback.DataProvider extends java.lang.Object{
-
public shulaing.han.ndk_callback.DataProvider();
-
Signature: ()V
-
public native void callCcode();
-
Signature: ()V
-
public void helloFromJava();
-
Signature: ()V
-
public int Add(int, int);
-
Signature: (II)I
-
public void printString(java.lang.String);
-
Signature: (Ljava/lang/String;)V
-
}
截图 :
方法签名介绍 :
-- 返回值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方法的相关函数 :
-
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
-
-
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
-
jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
-
jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
-
jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
-
jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
-
jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
-
jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
-
jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
-
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
-
void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
-
void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
C语言代码 :
-
#include "shulaing_han_ndk_callback_DataProvider.h"
-
#include <string.h>
-
#include <android/log.h>
-
#define LOG_TAG "System.out"
-
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
-
(JNIEnv * env, jobject obj)
-
{
-
-
-
LOGI("in code");
-
-
char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
-
jclass dpclazz = (*env)->FindClass(env, classname);
-
if(dpclazz == 0)
-
LOGI("class not find !!!");
-
else
-
LOGI("class find !!!");
-
-
-
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
-
if(methodID == 0)
-
LOGI("method not find !!!");
-
else
-
LOGI("method find !!!");
-
-
-
-
-
-
LOGI("before call method");
-
(*env)->CallVoidMethod(env, obj, methodID);
-
LOGI("after call method");
-
-
}
Java代码 :
--XML布局文件代码 :
-
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
xmlns:tools="http://schemas.android.com/tools"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
android:orientation="vertical"
-
android:paddingBottom="@dimen/activity_vertical_margin"
-
android:paddingLeft="@dimen/activity_horizontal_margin"
-
android:paddingRight="@dimen/activity_horizontal_margin"
-
android:paddingTop="@dimen/activity_vertical_margin"
-
tools:context=".MainActivity" >
-
-
<Button
-
android:id="@+id/call_void_method"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:onClick="onClick"
-
android:text="C语言回调Java中的空方法" />
-
-
</LinearLayout>
--
MainActivity代码 :
-
package shulaing.han.ndk_callback;
-
-
import android.app.Activity;
-
import android.os.Bundle;
-
import android.view.View;
-
-
public class MainActivity extends Activity {
-
-
static{
-
System.loadLibrary("jni");
-
}
-
DataProvider dp;
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
dp = new DataProvider();
-
}
-
-
public void onClick(View view) {
-
int id = view.getId();
-
switch (id) {
-
case R.id.call_void_method:
-
dp.callCcode();
-
break;
-
-
default:
-
break;
-
}
-
}
-
-
}
执行结果 :
.
4. C代码回调Java中带String参数的方法
在DataProvider中添加两个native方法 :
-
public native void callCcode();
-
public native void callCcode1();
-
public native void callCcode2();
进入bin/classes目录, 使用 javah -jni shulaing.han.ndk_callback.DataProvider 命令生成头文件 :
-
-
#include <jni.h>
-
-
-
#ifndef _Included_shulaing_han_ndk_callback_DataProvider
-
#define _Included_shulaing_han_ndk_callback_DataProvider
-
#ifdef __cplusplus
-
extern "C" {
-
#endif
-
-
-
-
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
-
(JNIEnv *, jobject);
-
-
-
-
-
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
-
(JNIEnv *, jobject);
-
-
-
-
-
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
-
(JNIEnv *, jobject);
-
-
#ifdef __cplusplus
-
}
-
#endif
-
#endif
jni C语言代码 : 这里只需要修改两处, 方法名, 获取方法id中的参数, 调用方法中最后加上一个Java参数;
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
-
(JNIEnv *env, jobject obj)
-
{
-
-
-
LOGI("in code");
-
-
char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
-
jclass dpclazz = (*env)->FindClass(env, classname);
-
if(dpclazz == 0)
-
LOGI("class not find !!!");
-
else
-
LOGI("class find !!!");
-
-
-
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
-
if(methodID == 0)
-
LOGI("method not find !!!");
-
else
-
LOGI("method find !!!");
-
-
-
-
-
-
LOGI("before call method");
-
(*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
-
LOGI("after call method");
-
}
执行后的结果 :
5. C代码中回调带两个int类型的参数的方法
按照上面的流程, 不同之处就是jni中获取方法 和 方法id , 调用方法的jni函数不同 :
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
-
(JNIEnv *env, jobject obj)
-
{
-
-
-
LOGI("in code");
-
-
char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
-
jclass dpclazz = (*env)->FindClass(env, classname);
-
if(dpclazz == 0)
-
LOGI("class not find !!!");
-
else
-
LOGI("class find !!!");
-
-
-
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
-
if(methodID == 0)
-
LOGI("method not find !!!");
-
else
-
LOGI("method find !!!");
-
-
-
-
-
-
LOGI("before call method");
-
(*env)->CallIntMethod(env, obj, methodID, 3, 5);
-
LOGI("after call method");
-
-
}
Java代码 :
-
case R.id.call_int_parameter_method:
-
dp.callCcode2();
-
break;
执行结果 :
6. 完整源码
Java源码 :
-- DataProvider源码 :
-
package shulaing.han.ndk_callback;
-
-
-
public class DataProvider {
-
-
public native void callCcode();
-
public native void callCcode1();
-
public native void callCcode2();
-
-
-
-
public void helloFromJava(){
-
System.out.println("hello from java");
-
}
-
-
-
public int Add(int x,int y){
-
System.out.println("the add result is : " + (x + y));
-
return x + y;
-
}
-
-
-
public void printString(String s){
-
System.out.println("in java code :" + s);
-
}
-
-
}
-- MainActivity源码 :
-
package shulaing.han.ndk_callback;
-
-
import android.app.Activity;
-
import android.os.Bundle;
-
import android.view.View;
-
-
public class MainActivity extends Activity {
-
-
static{
-
System.loadLibrary("jni");
-
}
-
DataProvider dp;
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
dp = new DataProvider();
-
}
-
-
public void onClick(View view) {
-
int id = view.getId();
-
switch (id) {
-
case R.id.call_void_method:
-
dp.callCcode();
-
break;
-
-
case R.id.call_string_parameter_method:
-
dp.callCcode1();
-
break;
-
-
case R.id.call_int_parameter_method:
-
dp.callCcode2();
-
break;
-
-
default:
-
break;
-
}
-
}
-
-
}
XML布局文件源码 :
-
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
xmlns:tools="http://schemas.android.com/tools"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
android:orientation="vertical"
-
android:paddingBottom="@dimen/activity_vertical_margin"
-
android:paddingLeft="@dimen/activity_horizontal_margin"
-
android:paddingRight="@dimen/activity_horizontal_margin"
-
android:paddingTop="@dimen/activity_vertical_margin"
-
tools:context=".MainActivity" >
-
-
<Button
-
android:id="@+id/call_void_method"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:onClick="onClick"
-
android:text="C语言回调Java中的空方法" />
-
-
<Button
-
android:id="@+id/call_string_parameter_method"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:onClick="onClick"
-
android:text="C语言回调Java中的String参数方法" />
-
-
<Button
-
android:id="@+id/call_int_parameter_method"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:onClick="onClick"
-
android:text="C语言回调Java中的int参数方法" />
-
-
</LinearLayout>
jni源码 :
-- 头文件源码 :
-
-
#include <jni.h>
-
-
-
#ifndef _Included_shulaing_han_ndk_callback_DataProvider
-
#define _Included_shulaing_han_ndk_callback_DataProvider
-
#ifdef __cplusplus
-
extern "C" {
-
#endif
-
-
-
-
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
-
(JNIEnv *, jobject);
-
-
-
-
-
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
-
(JNIEnv *, jobject);
-
-
-
-
-
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
-
(JNIEnv *, jobject);
-
-
#ifdef __cplusplus
-
}
-
#endif
-
#endif
-- Android.mk源码 :
-
LOCAL_PATH := $(call my-dir)
-
-
include $(CLEAR_VARS)
-
-
LOCAL_MODULE := jni
-
LOCAL_SRC_FILES := jni.c
-
#增加log函数对应的log库
-
LOCAL_LDLIBS += -llog
-
-
include $(BUILD_SHARED_LIBRARY)
-- jni主程序源码 :
-
#include "shulaing_han_ndk_callback_DataProvider.h"
-
#include "first.h"
-
#include <string.h>
-
#include <android/log.h>
-
#define LOG_TAG "System.out"
-
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
-
(JNIEnv * env, jobject obj)
-
{
-
-
-
LOGI("in code");
-
-
char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
-
jclass dpclazz = (*env)->FindClass(env, classname);
-
if(dpclazz == 0)
-
LOGI("class not find !!!");
-
else
-
LOGI("class find !!!");
-
-
-
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
-
if(methodID == 0)
-
LOGI("method not find !!!");
-
else
-
LOGI("method find !!!");
-
-
-
-
-
-
LOGI("before call method");
-
(*env)->CallVoidMethod(env, obj, methodID);
-
LOGI("after call method");
-
-
}
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
-
(JNIEnv *env, jobject obj)
-
{
-
-
-
LOGI("in code");
-
-
char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
-
jclass dpclazz = (*env)->FindClass(env, classname);
-
if(dpclazz == 0)
-
LOGI("class not find !!!");
-
else
-
LOGI("class find !!!");
-
-
-
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
-
if(methodID == 0)
-
LOGI("method not find !!!");
-
else
-
LOGI("method find !!!");
-
-
-
-
-
-
LOGI("before call method");
-
(*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
-
LOGI("after call method");
-
}
-
-
-
-
-
-
JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
-
(JNIEnv *env, jobject obj)
-
{
-
-
-
LOGI("in code");
-
-
char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
-
jclass dpclazz = (*env)->FindClass(env, classname);
-
if(dpclazz == 0)
-
LOGI("class not find !!!");
-
else
-
LOGI("class find !!!");
-
-
-
jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
-
if(methodID == 0)
-
LOGI("method not find !!!");
-
else
-
LOGI("method find !!!");
-
-
-
-
-
-
LOGI("before call method");
-
(*env)->CallIntMethod(env, obj, methodID, 3, 5);
-
LOGI("after call method");
-
-
}
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源码 :
-
#ifndef FIRST_H
-
#define FIRST_H
-
-
extern int first(int x, int y);
-
-
#endif /* FIRST_H */
first.c源码 :
-
#include "first.h"
-
-
int first(int x, int y)
-
{
-
return x + y;
-
}
在签名函数中, 直接调用 first()方法即可;
.
七 分析Log日志系统框架的JNI代码
在这里分析日志输出函数 : Log.i(TAG, "log"), 分析该日志系统的JNI层源码结构;
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
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内容 :
-
package android.util;
-
-
import com.android.internal.os.RuntimeInit;
-
-
import java.io.PrintWriter;
-
import java.io.StringWriter;
-
public final class Log {
-
-
... ...
-
-
-
public static int d(String tag, String msg) {
-
return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
-
}
-
-
-
public static int d(String tag, String msg, Throwable tr) {
-
return println_native(LOG_ID_MAIN, DEBUG, tag, msg + ‘\n‘ + getStackTraceString(tr));
-
}
-
-
-
public static int i(String tag, String msg) {
-
return println_native(LOG_ID_MAIN, INFO, tag, msg);
-
}
-
-
... ...
-
-
-
public static native boolean isLoggable(String tag, int level);
-
-
... ...
-
-
public static final int LOG_ID_MAIN = 0;
-
public static final int LOG_ID_RADIO = 1;
-
public static final int LOG_ID_EVENTS = 2;
-
public static final int LOG_ID_SYSTEM = 3;
-
-
-
public static native int println_native(int bufID,
-
int priority, String tag, String msg);
-
}
2. 分析Log系统JNI层源码
JNI层方法: JNI层方法根据一定规则与Java层声明的Native方法进行映射, 然后可以通过JNIEnv指针提供的JNI函数对Java层进行操作;
Log系统的JNI层文件是 : android_util_Log.cpp, 该文件路径 :\frameworks\base\core\jni\android_util_Log.cpp 代码如下 :
-
#define LOG_NAMESPACE "log.tag."
-
#define LOG_TAG "Log_println"
-
-
#include <assert.h>
-
#include <cutils/properties.h>
-
#include <utils/Log.h>
-
#include <utils/String8.h>
-
-
#include "jni.h"
-
#include "utils/misc.h"
-
#include "android_runtime/AndroidRuntime.h"
-
-
... ...
-
-
namespace android {
-
-
struct levels_t {
-
jint verbose;
-
jint debug;
-
jint info;
-
jint warn;
-
jint error;
-
jint assert;
-
};
-
static levels_t levels;
-
-
static int toLevel(const char* value)
-
{
-
switch (value[0]) {
-
case ‘V‘: return levels.verbose;
-
case ‘D‘: return levels.debug;
-
case ‘I‘: return levels.info;
-
case ‘W‘: return levels.warn;
-
case ‘E‘: return levels.error;
-
case ‘A‘: return levels.assert;
-
case ‘S‘: return -1;
-
}
-
return levels.info;
-
}
-
-
-
-
-
-
-
-
-
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
-
{
-
#ifndef HAVE_ANDROID_OS
-
return false;
-
#else /* HAVE_ANDROID_OS */
-
int len;
-
char key[PROPERTY_KEY_MAX];
-
char buf[PROPERTY_VALUE_MAX];
-
-
if (tag == NULL) {
-
return false;
-
}
-
-
jboolean result = false;
-
-
-
const char* chars = env->GetStringUTFChars(tag, NULL);
-
-
if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
-
jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
-
char buf2[200];
-
snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",
-
chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));
-
-
-
env->ReleaseStringUTFChars(tag, chars);
-
-
env->ThrowNew(clazz, buf2);
-
return false;
-
} else {
-
strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
-
strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
-
}
-
-
env->ReleaseStringUTFChars(tag, chars);
-
-
len = property_get(key, buf, "");
-
int logLevel = toLevel(buf);
-
return (logLevel >= 0 && level >= logLevel) ? true : false;
-
#endif /* HAVE_ANDROID_OS */
-
}
-
-
-
-
-
-
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
-
jint bufID, jint priority, jstring tagObj, jstring msgObj)
-
{
-
const char* tag = NULL;
-
const char* msg = NULL;
-
-
if (msgObj == NULL) {
-
jclass npeClazz;
-
-
npeClazz = env->FindClass("java/lang/NullPointerException");
-
assert(npeClazz != NULL);
-
-
env->ThrowNew(npeClazz, "println needs a message");
-
return -1;
-
}
-
-
if (bufID < 0 || bufID >= LOG_ID_MAX) {
-
jclass npeClazz;
-
-
npeClazz = env->FindClass("java/lang/NullPointerException");
-
assert(npeClazz != NULL);
-
-
env->ThrowNew(npeClazz, "bad bufID");
-
return -1;
-
}
-
-
if (tagObj != NULL)
-
tag = env->GetStringUTFChars(tagObj, NULL);
-
msg = env->GetStringUTFChars(msgObj, NULL);
-
-
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
-
-
if (tag != NULL)
-
env->ReleaseStringUTFChars(tagObj, tag);
-
env->ReleaseStringUTFChars(msgObj, msg);
-
-
return res;
-
}
-
-
-
-
-
static JNINativeMethod gMethods[] = {
-
-
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
-
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
-
};
-
-
int register_android_util_Log(JNIEnv* env)
-
{
-
jclass clazz = env->FindClass("android/util/Log");
-
-
if (clazz == NULL) {
-
LOGE("Can‘t find android/util/Log");
-
return -1;
-
}
-
-
levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
-
levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
-
levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
-
levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
-
levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
-
levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
-
-
return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
-
}
-
-
};
3. 声明JNI 与 Native 方法的映射关系
标准JNI规范 : 在标准的JNI规范中, Java中的Native方法 与 JNI层方法 是通过方法名的对应关系进行映射的, 我们通过 javah 工具生成JNI层头文件, 头文件中定义了规范的JNI层方法名, 这个方法名就与Java Native方法对应;
Android自定义规范 : 在 \dalvik\libnativehelper\include\nativehelper\jni.h 中定义了这样的映射关系 :
-
typedef struct {
-
const char* name;
-
const char* signature;
-
void* fnPtr;
-
} JNINativeMethod;
.
JNINativeMethod类型数据 : 在android_util_Log.cpp 中定义了一个该类型的数组 :
-
-
-
-
static JNINativeMethod gMethods[] = {
-
-
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
-
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
-
};
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 中存在这样的方法 :
-
int register_android_util_Log(JNIEnv* env)
-
{
-
jclass clazz = env->FindClass("android/util/Log");
-
-
if (clazz == NULL) {
-
LOGE("Can‘t find android/util/Log");
-
return -1;
-
}
-
-
levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
-
levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
-
levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
-
levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
-
levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
-
levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
-
-
return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
-
}
-
-
};
核心方法 : 该函数调用了 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 ;
-
-
-
-
int AndroidRuntime::registerNativeMethods(JNIEnv* env,
-
const char* className, const JNINativeMethod* gMethods, int numMethods)
-
{
-
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
-
}
registerNativeMethods 方法只是对 jniRegisterNativeMethods 方法的封装, 在JNIHelp.h中找到该方法的声明:
-
-
-
-
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
-
const JNINativeMethod* gMethods, int numMethods);
在JNIHelp.c 中找到该方法的实现 : 最终方法中调用了 JNIEnv 的RegisterNatives 函数, 将gMethods中存放的JNINativeMethod结构体(存放Native方法 与 JNI方法关联信息) 传递到java虚拟机;
-
-
-
-
-
-
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
-
const JNINativeMethod* gMethods, int numMethods)
-
{
-
jclass clazz;
-
-
LOGV("Registering %s natives\n", className);
-
clazz = (*env)->FindClass(env, className);
-
if (clazz == NULL) {
-
LOGE("Native registration unable to find class ‘%s‘\n", className);
-
return -1;
-
}
-
-
int result = 0;
-
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
-
LOGE("RegisterNatives failed for ‘%s‘\n", className);
-
result = -1;
-
}
-
-
(*env)->DeleteLocalRef(env, clazz);
-
return result;
-
}
6. JNI的规范
Android中JNI存在两种规范 : 一种是标准的JNI规范, 多在应用层使用;
另一种是Android中自定义的规范, 多使用在应用框架层;
-- JNI标准规范: 遵守JNI标准规函数命名方式, JNI中方法命名为 Java_包名_类名_方法名 , 可以使用javah生成签名头文件, 靠这种方式实现 Native方法 与 JNI方法之间的映射关系, 即应用直接与框架层进行交互, 这种规范常用与应用开发;
-- 函数注册规范 : 这是Android自定义的一种规范, 应用框架层采用该规范, 即应用框架层 与 框架层 进行交互, 底层源码开发多使用该规范;
NDK开发基本知识
标签:
原文地址:http://blog.csdn.net/u013037007/article/details/50992497