Android JNI详解
目录
1 JNI基础知识 2
1.1 概述 2
1.2 数据类型 2
1.2.1 常用数据类型 2
1.2.2 使用数组 4
1.2.3 使用对象 4
2 Android JNI使用方法示例 6
2.1 Java部分文件 6
2.2 JNI部分文件 6
2.3 JNI参数传递 9
2.3.1 基本数据类型 9
2.3.2 Java对象数据类型 9
2.3.3 Java对象数组 10
2.4 JNI实现回调 12
3 以上内容部分参考自互联网资料 14
1 JNI基础知识
1.1 概述
JNI,Java Native Interface(JNI) 。其标准从Java1.1开始成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。JNI标准至少保证本地代码能工作在任何Java 虚拟机环境下。
图1是我在网上找到一份对JNI进行总结的知识图。
1.2 数据类型
1.2.1 常用数据类型
在Java存在两种数据类型: 基本类型和引用类型 。在JNI的世界里也存在类似的数据类型,与Java比较起来,其范围更具严格性,如下:
1、primitive types —-基本数据类型,如:int、 float 、char等基本类型
2、reference types—-引用类型,如:类、实例、数组。
特别需要注意:数组 —— 不管是对象数组还是基本类型数组,都作为reference types存在。
表A JNI常见类型映射表
Java类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
另外,关于引用类型的一个继承关系如下,我们可以对具有父子关系的类型进行转换:
1.2.2 使用数组
JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。
因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见表B),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
表B JNI数组存取函数
函数 Java数组类型 本地类型
GetBooleanArrayElements jbooleanArray jboolean
GetByteArrayElements jbyteArray jbyte
GetCharArrayElements jcharArray jchar
GetShortArrayElements jshortArray jshort
GetIntArrayElements jintArray jint
GetLongArrayElements jlongArray jlong
GetFloatArrayElements jfloatArray jfloat
GetDoubleArrayElements jdoubleArray jdouble
当你对数组的存取完成后,要确保调用相应的Relea***XXArrayElements函数,参数是对应Java数组和 GetXXXArrayElements返回的指针。如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相 关的资源。
为了使用java对象的数组,你必须使用GetObjectArrayElement函数和SetObjectArrayElement函数,分别去get,set数组的元素。GetArrayLength函数会返回数组的长度。
1.2.3 使用对象
JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或 方法的ID是任何处理域和方法的函数的必须参数。
表C列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
表C 域和方法的函数
函数 描述
GetFieldID 得到一个实例的域的ID
GetStaticFieldID 得到一个静态的域的ID
GetMethodID 得到一个实例的方法的ID
GetStaticMethodID 得到一个静态方法的ID
如果你有了一个类的实例,它就可以通过方法GetObjectClass得到,或者如果你没有这个类的实例,可以通过FindClass得到。符号是从域的类型或者方法的参数,返回值得到字符串,如表D所示。
表D 确定域和方法的符号
Java 类型 符号
boolean Z
byte B
char C
short S
int I
long L
float F
double D
void V
objects对象 Lfully-qualified-class-name;L类名
Arrays数组 [array-type [数组类型
methods方法 (argument-types)return-type(参数类型)返回类型
例如:在java代码中的java.lang.String类的类描述符就是java/lang/String。
其实,在实践中,我发现可以直接用该类型的域描述符取代,也是可以成功的。
例如: jclass intArrCls = env->FindClass(“java/lang/String”)
等同于 jclass intArrCls = env->FindClass(“Ljava/lang/String;”)
数组类型描述符则为: [ + 其类型的域描述符 (后文说明)
例如:
int [ ] 其描述符为[I
float [ ] 其描述符为[F
String [ ] 其描述符为[Ljava/lang/String;
方法描述符将参数类型的域描述符按照申明顺序放入一对括号中后跟返回值类型的域描述符,规则如下: (参数的域描述符的叠加)返回 类型描述符。
对于,没有返回值的,用V(表示void型)表示。
举例如下:
Java层方法 JNI函数签名
String test ()Ljava/lang/String;
int f (int i, Object object) (ILjava/lang/Object;)I
void set (byte[ ] bytes) ([B)V
一旦你有了类和方法或者域的ID,你就能把它保存下来以后使用,而没有必要重复去获取。
有几个分别访问域和方法的函数。实例的域可以使用对应域的GetXXXField的变体函数访问。GetStaticXXXField函数用于静态类型。设置域的值,用SetXXXField 和SetStaticXXXField函数。
2 Android JNI使用方法示例
2.1 Java部分文件
- so动态库放置于工程的libs/armabi(根据系统构架不同可能会稍有差异)目录下。
- MyJniClass.java源码示例如下
package com.example.demo
public class MyJniClass{
//load jni library
static {
System.loadLibrary(“myjni”);
}
public int myFunction(){
//call native jni function
return my_function();
}
//declare native jni function
native private int my_function ();
}
2.2 JNI部分文件
JNI部分文件在工程的jni目录下,包括Android.mk及JNI文件两部分。Android.mk文件是编译脚本文件,JNI文件是生成so库的源码文件
1. Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
//library name
LOCAL_MODULE := libmyjni
LOCAL_C_INCLUDES += $(LOCAL_PATH)/inc \
frameworks/native/include \
frameworks/base/core/jni/android/graphics
# Also needed the JNI headers.
LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) \
$(LOCAL_PATH) \
$(LOCAL_PATH)/mylibcode /inc\
LOCAL_LDFLAGS += -Wl,--no-warn-shared-textrel
LOCAL_SDK_VERSION := 9
LOCAL_ARM_MODE := arm
CFLAGS = -march=armv5 -msoft-float -mthumb-interwork -O2 -fno-exceptions -fno-rtti -fpic
LOCAL_CFLAGS += -ffast-math -O3 -funroll-loops
LOCAL_CPPFLAGS += $(JNI_CFLAGS)
LOCAL_LDLIBS :=-L$(LOCAL_PATH)/lib -llog \
-lsupc++ \
-ldl \
-ljnigraphics
LOCAL_CPP_EXTENSION := .cpp
LOCAL_SRC_FILES += \
native.cpp \
LOCAL_SHARED_LIBRARIES += \
libutils \
libcutils \
libjnigraphics \
include $(BUILD_SHARED_LIBRARY)
- JNI文件,假设为native.cpp
native.cpp为android.mk中声明的JNI入口函数文件。当Java端加载JNI动态链接库时,首先会调用JNI_OnLoad接口,然后在改接口中调用JNI接口注册函数registerNativeMethods。
#define LOG_TAG "JNI_LIBMYJNI"
#include <sys/types.h>
#include "jni.h"
#include <errno.h>
#include <android/bitmap.h>
//cpp class head files
#include "myheadfiles.h"
using namespace android;
static int function( JNIEnv *env, jobject thiz ) {
int ret = 0;
//implement code
return ret;
}
static const char *classPathName = "com/android/yulong/facerecognize/FaceProcEngine";
static JNINativeMethod methods[] = {
{"initFaceProcEngine", "()I", (void*) function },
};
/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* Register native methods for all classes we know about.
*
* returns JNI_TRUE on success.
*/
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, classPathName,
methods, sizeof(methods) / sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* This is called by the VM when the shared library is first loaded.
*/
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
LOGI("JNI_OnLoad");
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed");
goto bail;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
LOGE("ERROR: registerNatives failed");
goto bail;
}
result = JNI_VERSION_1_4;
bail:
return result;
}
2.3 JNI参数传递
2.3.1 基本数据类型
对于基本数据类型,java和c是相互对应的,所以可以直接使用。它们的对应关系如表1所示。
2.3.2 Java对象数据类型
1. 从Java传递对象到C
对于java传递进来的java对象类型,c要加载java类的原型,根据创建相应的c对象,获取java对象的方法的id,然后调用java对象的方法。
举例说明:比如有个java类customer对象作为jni参数传递到c程序,customer有方法String getName()。
void getCustomer(JNIEnv *env, jobject, jobject customer){
//获得customer对象的句柄
jclass cls_objClass=env->GetObjectClass(customer);
//获得customer对象中特定方法getName的id
jmethodID methodId=env->GetMethodID(cls_objClass,"getName","()Ljava/lang/String;");
//调用customer对象的特定方法getName
jstring js_name=(jstring)env->CallObjectMethod(customer,methodId,NULL);
......
}
- 从C传递对象到Java
在c程序中首先要创建要返回的java对象,得到每个属性的id,然后给每个属性赋值,最后返回。举例说明:同样是customer对象,有name等属性值,需要在c程序中给每个属性赋值后返回。
jobject getCustomer(JNIEnv *env, jobject, jobject customer){
......
//发现java Customer类,如果失败,程序返回
jclass clazz = env->FindClass("com/example/demo /Customer");
if(clazz == 0)
return 0;
//为新的java类对象obj分配内存
jobject obj = env->AllocObject(clazz);
//发现类中的属性,如果失败,程序返回
jfieldID fid_id = env->GetFieldID(clazz,"customerID","I");
if (fid_id == 0)
return 0;
jfieldID fid_name = env->GetFieldID(clazz,"name","Ljava/lang/String;");
if (fid_name == 0)
return 0;
......
env->SetIntField(obj, fid_id, 100);
env->SetObjectField(obj, fid_name, jname);
......
return obj;
}
2.3.3 Java对象数组
1. java向C传递一个含有java对象的数组
对于这种情况,先得到数组的大小,接下来取出数组中的对象,取得对象的属性值或者调用对象的方法,将获得值存到本地数组中,然后可以灵活使用这些数据了。
举例说明:java向c传递一个含有多个customer对象的数组,在c中将这个数组的分解出来,存到本地的临时数组中去。
void CustomerRequest_2(JNIEnv *env, jobject, jobjectArray oa){
......
//声明customerrequest对象
jobject o_customer;
jmethodID methodId;
jint size=env->GetArrayLength(oa);
_tmp_bind[0]= (char *)malloc(size*sizeof(int));
_tmp_bind[1]= (char *)malloc(size*sizeof(char)*( 20 + 1));
//将输入数组的数据拷贝到临时数组中去
for(int i=0; i<size; i++){
//从数组中获得customerrequest对象
o_request=env->GetObjectArrayElement(oa,i);
//获得customerrequest对象的句柄
jclass cls_objClass=env->GetObjectClass(o_request);
//获得customerrequest对象的特定方法getCustomerID的id
methodId=env->GetMethodID(cls_objClass,"getCustomerID","()I");
//调用customerrequest对象的特定方法getCustomerID
int_customerID=env->CallIntMethod(o_request,methodId,NULL);
//获得customerrequest对象中特定方法getTelNum的id
methodId=env->GetMethodID(cls_objClass,"getTelNum","()Ljava/lang/String;");
//调用customerrequest对象的特定方法getTelNum
str_telNum=(jstring)env->CallObjectMethod(o_request,methodId,NULL);
//将用户id拷贝到临时数组
memcpy(_tmp_bind[0]+i*sizeof(int),&int_customerID,sizeof(int));
//将电话号码拷贝到临时数组,如果电话号码字符串超长,报错返回
if(sizeof(char)*strlen(chr_tel)<=sizeof(char)*( 20 + 1)){
memcpy(_tmp_bind[1]+i*sizeof(char)*( 20+1 ),chr_tel,strlen(chr_tel)+1);
}else{
printf("%s too long!\n",chr_tel);
return;
}
}
}
- C向java返回一个数组
先创建数组,然后加载java对象,给每个java对象的属性赋值,添加到数组中,最后返回数组。
如下例:
jobjectArray getCustomerRequest (JNIEnv *env, jobject, jint customerid){
......
int count = 10;
//发现java Customerrequest类,如果失败,程序返回
jclass clazz = env->FindClass("com/ example/demo /CustomerRequest");
if(clazz == 0)
return 0;
//声明存放查询结果的objectarray
jobjectArray jo_array = env->NewObjectArray(count, clazz , 0);
jobject obj;
for(int i=0; i<count; i++){
obj = env->AllocObject(clazz);
jfieldID fid_customerID = env->GetFieldID(clazz,"customerID","I");
if (fid_customerID == 0)
return 0;
jfieldID fid_priority = env->GetFieldID(clazz,"priority","I");
if (fid_priority == 0)
return 0;
...
env->SetIntField(obj, fid_customerID, col_customerID);
env->SetIntField(obj, fid_priority, col_priority);
......
//将对象obj添加到object array中
env->SetObjectArrayElement(jo_array, i, obj);
}
return jo_array;
}
2.4 JNI实现回调
通过JNI在Native层调用JAVA层的方法,来实现Native层向JAVA层传递消息。
JNICallback.java
public class JNICallback {
static {
System.loadLibrary("callback");
}
public void callback(int notify_id, int param)
{
Log.e("JNICallback","notify_id="+notify_id+";param="+param);
}
public native void setCallBack();
}
JNICallback.cpp
void setCallBack (JNIEnv *env, jobject obj)
{
LOGE("jni callback (0)");
jclass cls = env->GetObjectClass(obj);
jmethodID callback = env->GetMethodID(cls,"callback","(II)V");
env->CallVoidMethod(obj,callback,5,10);
LOGE("jni callback (1)");
}
3 references
- http://blog.csdn.net/qinjuning/article/details/7599796
- http://blog.chinaunix.net/uid-23023613-id-2566756.html
- http://www.cnblogs.com/mandroid/archive/2011/06/15/2081093.html
- http://blog.csdn.net/dncts/article/details/12715897
- http://blog.chinaunix.net/uid-26524139-id-3181305.html
- http://blog.sina.com.cn/s/blog_4cd5d2bb0101fkm1.html