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中定义