- 什么是JNI
JNI是Java Native Interface 的缩写,意为java本地接口, 使用JNI技术可以使得java语言与其它开发语言(如 C、C++ 和汇编语言)编写的应用程序或库进行相互操作。Android系统中的JNI运行通常是在java语言开发的apk或其它组件中调用C/C++开发的底层 模块。 - JNI的调用
Android系统中应用层的操作都是由java编写的,而这些java编写的类最后都 要通过编译成Dex型式的ByteCode,通过Dalvik虚拟机(VM: Virtual Machine)来执行。java的开发的应用是运行在VM中的,而C,C++语言是运行在另一个平台中的,两者是通过什么来连接进行沟通起来的?是通过 Dalvik虚拟机进行连接,采用JNI技术来进行沟通。Android系统 中JNI调用的情况:
Java要调用Native的方法要通过加载动态库的方法来实现,调用 System.loadLIbrary就可以加载动态库了。如下面的例子:
static{
try{
System.loadLibrary("samJintest");
}catch(UnsatisfiedLinkError e){
Log.w(TAG, "can't load the library:" + LIB_NAME);
}catch(Exception e){
Log.w(TAG, "can't load the library:" + LIB_NAME);
}
}通常做法是在类中的static块内进行加载,当执行System.loadLIbrary时,在android系统中就会去/system/lib目录下去查找相对应的so, 上面的例子就是要加载libsamJintest.so .
- JNI的Native方法的注册
- 示意代码 分布情况:
testJniActivity.java代码如下:
//testJniActivity.java
//。。。。省略代码
publicclass testJniActivity extends Activity {
/** Called when the activity is first created. */
//。。。。省略代码
@Override
publicvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//。。。。省略代码
mButAdd.setOnClickListener(new Button.OnClickListener(){
@Override
publicvoid onClick(View v) {
// TODO Auto-generated method stub
//。。。。省略代码
int add = mHJni.add(first, second);//--调用JniNativeHelp.add
}
});
mButSub.setOnClickListener(new Button.OnClickListener(){
@Override
publicvoid onClick(View v) {
// TODO Auto-generated method stub
//。。。。省略代码
int sub = mHJni.sub(first, second); //--调用JniNativeHelp.sub
}
});
}
}
包含Native方法的JniNativeHelp.java代码:
//文件JniNativeHelp.java
package com.android.sam.test;
import android.util.Log;
publicclass JniNativeHelp{
publicfinalstatic String TAG="JniNativeHelp";
publicfinalstatic String LIB_NAME = "samJintest";
static{
try{
System.loadLibrary("samJintest");
}catch(UnsatisfiedLinkError e){
Log.w(TAG, "can't load the library:" + LIB_NAME);
}catch(Exception e){
Log.w(TAG, "can't load the library:" + LIB_NAME);
}
}
public String getTagValue(){
return TAG;
}
publicnativeint add(int a, int b);
publicnativeint sub(int a,int b);
}
从上面的代码我们知道包括有两个Native方法add加法操作,sub减法操作。其前面都有一个关键修饰词Native. 也就是表明这两个方法是有JNI层实现的。
- 注册Native方法有二种方式:一种是静态注册,另一种是动态注册
以下分别讲述这两种方法
静态注册Native方法
静态方法是根据JNI规定的函数命名规则来找到指定的Native实现函数。
a) testJniActivity.java和JniNativeHelp.java是在同个Android project中的,编译通过后通过java自带的工具javah可生成JNI层的头文件。通过shell(window平台是命令)切换到工程下的bin目录,执行:javah packagename.classname, 本例子是: javah com.android.sam.test.JniNativeHelp
如果成功则无任务返回回信息,如图:
之后在目录下便生成了com.android.sam.test.JniNativeHelp.h//文件 com.android.sam.test.JniNativeHelp.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_android_sam_test_JniNativeHelp */
#ifndef _Included_com_android_sam_test_JniNativeHelp
#define _Included_com_android_sam_test_JniNativeHelp
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_android_sam_test_JniNativeHelp
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_android_sam_test_JniNativeHelp_add
(JNIEnv *, jobject, jint a, jint b);
/*
* Class: com_android_sam_test_JniNativeHelp
* Method: sub
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_android_sam_test_JniNativeHelp_sub
(JNIEnv *, jobject, jint a, jint b);
#ifdef __cplusplus
}
#endif
#endifa) 创建一个对应的实现文件名为com.android.sam.test.JniNativeHelp.cpp 实现具体的Native函数体(代码在附录中)
动态注册Native方法
静态注册的方法的缺点是:1.要通过javah来生成JNI层的头文件。2.Native的函数名称太长,如果packagename的名称长提话就更不得了。不方便阅读。3.第一次调用时,Native要根据函数名称规则来建议联系,效率会有所响应。首先建议一个文件名为:com.android.sam.test.JniNativeHelp.cpp (文件名可以是xxx.cpp)
先看一下最终的代码:
//文件com.android.sam.test.JniNativeHelp.cpp
#define LOG_TAG "JniNativeHelp_lib"
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "jni.h"
#include "JNIHelp.h"
#include "cutils/log.h"
#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif
/*
* Class: com_android_sam_test_JniNativeHelp
* Method: add
* Signature: (II)I
*/
int JniNativeHelp_add(JNIEnv *env, jobject thiz, jint a, jint b)
{
SLOGI("native-----add----");
return a+b;
}
/*
* Class: com_android_sam_test_JniNativeHelp
* Method: sub
* Signature: (II)I
*/
int JniNativeHelp_sub(JNIEnv *env, jobject thiz, jint a, jint b)
{
SLOGI("native-----sub----");
return a-b;
}
//本地实现方法列表,该gMethods变量定义必须放到所有注册的方法之后,否则编译时会提示有些方法没有声明
static JNINativeMethod gMethods[] = {
{"add", "(II)I", (void*)JniNativeHelp_add },
{"sub", "(II)I", (void*)JniNativeHelp_sub },
};
//目标JAVA类路径
staticconstchar *classPathName = "com/android/sam/test/JniNativeHelp";
//为调用的某个JAVA类注册本地JNI函数
int registerNativeMethods(JavaVM* vm)
{
SLOGI("registerNativeMethods");
JNIEnv* env = NULL;
if( vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK ){
SLOGE("JNI_VERSION_1_4 != JNI_OK");
return -1;
}
int numMethods = NELEM(gMethods);
return jniRegisterNativeMethods(env,classPathName, gMethods, numMethods);
}
//为当前虚拟机平台注册本地JNI
jint JNI_OnLoad(JavaVM* vm, void* reserved){
SLOGI("--------JNI_OnLoad-------");
if(registerNativeMethods(vm) < 0 ){
SLOGE("error");
return -1;
}
return JNI_VERSION_1_4; //如果注册成功,返回版本信息
}动态注册通过JNINativeMethod结构来记录Native函数的对应关系。如下所示:
static JNINativeMethod gMethods[] = {
{"add", "(II)I", (void*)JniNativeHelp_add },
{"sub", "(II)I", (void*)JniNativeHelp_sub },
};
该数组有两组元素。每组元素由三个成员构成。
拿第一组元素来说明一下:
{"add", //对应Java Native类中声明 的Native函数名称,在JniNativeHelp.java文件
"(II)I", //表示该方法的参数类型及个数和返回值类型,叫做函数签名信息
(void*)JniNativeHelp_add //JNI Native方法的具体实现的函数名称,在
},
重点说明一下第二元素”(II)I”是函数签名信息,格式为”(参数1类型标识参数2类型标识..参数n类型标识)返回值类型标识”
以下是JNI与java的类型标识对应表:
类型标识 Java类型
Z boolean
B byte
C char
S short
I int
J long
等等…
所以说”(II)I” 表示该函数有二个参数分别为 int , int ,返回值类型为int。
注意地方:该数组static JNINativeMethod gMethods[]=变量的定义必须要在JNI Native函数(数组中现在注册的对应的函数是JniNativeHelp_add, JniNativeHelp_sub)的声明之后进行,要不然编译会出错。
真正注册的操作是必须要实现jint JNI_OnLoad(JavaVM* vm, void* reserved)方法。
注意的地方:
在JNI_OnLoad方法中如果注册成功的话返值为JNI_VERSION_1_4 。
函数jniRegisterNativeMethods的调用是进行注册操作,该方法是Android系统中实现,所以要加上头文件#include "JNIHelp.h"。
我们再看一下Native函数的具体实现,一共有二个:
int JniNativeHelp_add(JNIEnv *env, jobject thiz, jint a, jint b)
{
SLOGI("native-----add----");
return a+b;
}
int JniNativeHelp_sub(JNIEnv *env, jobject thiz, jint a, jint b)
{
SLOGI("native-----sub----");
return a-b;
}每个函数的前两个参数是必须的,具体是什么再找资料。之后的参数就是对应的参数类型,其实也有类型转换关系。如下:
JNI Native类型 Java
Jboolean boolean
Jbyte byte
Jchar char
Jshort short
Jint int
Jlong long
Jfloat float
Jdouble double
等等…
- 示意代码 分布情况:
- Android中Makefile的改写
要把JNI层Native的具体实现编译成动态库,就要用到makefile脚本.Android的整个系统代码是通过一套很有规则的makefile文件脚本进行编译的。只要大概熟悉一下,就可以改写成一个适合自己新加入模块的makefile文件。
我把com.android.sam.test.JniNativeHelp.cpp,如果是静态注册还有文件头文件com.android.sam.test.JniNativeHelp.h放在android_source_code/framework/base/learn_jni_test/jni 目录下,同时也放入自己改写的makefile文件,内容如下:
//文件 Android.mk
View CodeLOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_TAGS := eng
LOCAL_PRELINK_MODULE := false
LOCAL_SRC_FILES:= \
com_android_sam_test_JniNativeHelp.cpp
LOCAL_SHARED_LIBRARIES := \
libnativehelper \
libutils
LOCAL_C_INCLUDES += \
$(PV_INCLUDES) \
$(JNI_H_INCLUDE) \
$(call include-path-for, corecg graphics)
LOCAL_CFLAGS +=
LOCAL_LDLIBS := -lpthread
LOCAL_MODULE:= libsamJintest
include $(BUILD_SHARED_LIBRARY)以上编译是依赖原来编译完的一些库,具体没有去细究。
-
附录所有代码:
android开发中JNI的使用_代码.7z