1
2
3
4
5
6
|
//Java与JNI通过JNINativeMethod的结构来建立联系,它在jni.h中被定义,其结构内容如下:
typedef
struct
{
const
char
* name;
//Java中函数的名字。
const
char
* signature;
//描述了函数的参数和返回值
void
* fnPtr;
//函数指针,指向C函数
} JNINativeMethod;
|
静态注册的步骤:
1. 先在Java上声明jni函数。如在MainActivity类中声明public native void helloWorld(),包名是com.example.test。
2. 新建C或Cpp文件,按照规定的命名方式去命名Jni层的函数。Java_包名_类名_函数名()。这个名字可以用javah命令快速生成。要注意:C++要用如下格式,否则会找不到函数
1
2
3
4
5
6
7
|
#ifdef __cplusplus
extern
"C"
{
#endif
//sourc...
#ifdef __cplusplus
}
#endif
|
javah使用方法说明:
-classpath <路径> 用于装入类的路径
-d <目录> 输出目录
-jni 生成 JNI样式的头文件(默认)
在cmd中或者cygwin中,输入javah -class *.class -d 输出目录 -jni 要被生成的包名加类名。
例如 按步骤1的声明,当前在项目目录helloFromC。编译后就会在bin目录下按包名生成class,helloFromC\bin\classes\com\example\test\MainActivity.class。javah就是javah -class bin\classes\ -d .\ -jni com.example.test.MainActivity。但有时会出现,错误: 无法访问android.app.Activity。网上有人说是环境变量问题,但怎么看我的环境变量都没问题。最后用了一个说法,在src目录下直接 javah com.example.test.MainActivity
3. 编写Android.mk,编译
1
2
3
4
5
6
7
8
9
10
11
|
LOCAL_PATH := $(call my-dir)
include
$(CLEAR_VARS)
LOCAL_MODULE := libcoep_jni
LOCAL_SRC_FILES := coep_jni.cpp
LOCAL_SHARED_LIBRARIES := \
liblog \
include
$(BUILD_SHARED_LIBRARY)
|
4. 最后,会编译出在MainActivity中加入如下语句,加载编译后生成的库。如果在Android中用mm来编译会在out目录下生成编译出来的静态库
1
2
3
|
static
{
System.loadLibrary(
"coep_jni"
);
}
|
动态注册的步骤:
其实动态注册也是很简单的
一. 像普通的C编程一样把逻辑函数都用jni形式写好,命名是随意的。
如,普通C函数为:
void funcA(){}
int funcB(int arg){}
Jni的要在参数列表里加上 (JNIEnv *env, jobject obj, ...),上面的会变成,注意返回值和形参:
void funcA(JNIEnv *env, jobject obj,){}
jint funcB(JNIEnv *env, jobject obj, int arg){}
jint funcC(JNIEnv *env, jobject thiz, jstring jName, jstring arg) {}
二. 把这些函数加入到Jni的函数数组中,让Jni函数与Java函数对应
1
2
3
4
5
6
7
8
|
static
JNINativeMethod gMethods[] = {
{
"funcA_native"
,
"()V"
, (
void
*)funcA},
{
"funcB_native"
,
"(I)I"
, (
void
*)funcB},
{
"funcC_native"
,
"(Ljava/lang/String;Ljava/lang/String;)I"
, (
void
*)funcC}
};
|
JNINativeMethod的定义为:
1
2
3
4
5
|
typedef
struct
{
const
char
* name;
//Java中函数的名字
const
char
* signature;
//用字符串描述的函数的参数和返回值
void
* fnPtr;
//指向C函数的函数指针
} JNINativeMethod;
|
其中数组的第二个子元素为signature,signature意为签名,即为函数签名。数据类型与签名关系如下。注意,类的签名后要加分号,如上面的funcC:
字符
V
Z
I
J
D
F
B
C
S
数组则以"["开始,用两个字符表示
[I
[F
[B
[C
[S
[D
[J
[Z
三, 注册上面的Jni函数数组,与指定的类进行绑定
1
2
3
4
|
static
int
registerNatives(JNIEnv *env){
const
char
*kClassName =
"package_name/class_name"
;
} |
四. 加载
在调用System.loadLibrary(“xxx”);时会先调用JNI_OnLoad会自动把Jni函数注册在虚拟机中,并建立Jni与上层函数的联系。如果你的Jni里面没有实现JNI_Onload时会使用默认的JNI_Onload并使用的时默认的Jni版本JNI 1.1。
如果使用动态注册,我们就要实现我们自己的JNI_Onload,并使用最新版本的Jni。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm,
void
*reserved){
JNIEnv *env = NULL;
jint result = -1;
LOGI(
"JNI_OnLoad begin"
);
/*通过虚拟机获取当前的env*/
if
(vm->GetEnv((
void
**)&env, JNI_VERSION_1_4) != JNI_OK){
printf
(
"Error GetEnv\n"
);
return
-1;
}
assert
(env != NULL);
/*调用我们刚才的registerNatives*/
if
(registerNatives(env) < 0){
printf
(
"register_android_test_hello error.\n"
);
return
-1;
}
/*返回要使用的Jni版本,表示onload成功*/
result = JNI_VERSION_1_4;
LOGI(
"JNI_OnLoad finish"
);
return
result;
}
|
Jni的Log系统
在使用jni的时候如果使用printf()。并不会在eclipse里的log框里打印,这样就不方便开发了。如果要在eclipse的log框里显示,就要使用android的log系统。
首先我们要在Android.mk里包含库:LOCAL_SHARED_LIBRARIES := \liblog \
首先我们要在源码里包含头文件:#include <android/log.h>。
android/log.h这个头文件包含了最原始的打印函数,有兴趣的可以去看看。我们用其中的__android_log_print(); 但这还不行,我们还要做一个宏定义
1
2
3
4
5
|
static
const
char
*TAG =
"YoutTag"
;
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
|
__android_log_print的第一个参数就表时了Log的等级。
如果你的Jni在系统中,还可以使用Jni自带的一个Log工具。注意,这些工具有可能在上层App中不能用,在系统里的Jni可以用。
头文件是 #include "utils/Log.h"
主要是"utils/Log.h"中的#include <cutils/log.h>
里面定义了很多
LOGE,LOGW,LOGI,LOGD,LOGV
LOGE_IF,LOGW_IF,LOGI_IF,LOGD_IF,LOGV_IF
只要定义了他们需要的TAG就可直接使用,#define LOG_TAG "XXXX"
注意,使用方法还是和C的printf那样使用。如:LOGI( "xxxx %d", i );
Jni的自身提供的工具
JNI提供了很多快速开发的工具,在JNIHelp.h中定义