JNI 学习笔记
JNI是Java Native Interface的缩写,JNI是一种机制,有了它就可以在java程序中调用其他native代码,或者使native代码调用java层的代码。也 就是说,有了JNI我们可以使Android项目中,java层与native层各自发挥所长并相互配合。如下图所示,JNI在Android中所处的位 置。
JNI相对与native层来说是一个接口,java层的程序想访问native层,必须通过JNI,反过来也一样。下面我们来看几个问题。
1,如何告诉VM(虚拟机)java层需要调用native层的哪些libs?
我们知道java程序是运行在VM上的,而Native层的libs则不然。所以为了让java层能访问native层的libs,必须得告诉VM要使用哪些native层的libs。下面看一段代码
public class MediaPlayer
{
... static {
System.loadLibrary("media_jni");
native_init();
} ... private native final void native_init(); ...
}
可以看到上面的代码中,在MediaPlayer类中有一段static块包围起来的代码, 其中System.loadLibrary("media_jni")就是告诉VM去加载libmedia_jni.so这个动态库,那么这个动态库什么 时候被加载呢?因为static语句块的原因,所以在MediaPlayer第一次实例化的时候就会被加载了。这段代码中,我们还看到了一个函数 native_init(),该函数被申明为native型,就是告诉VM该函数由native层来实现。
2,如何做到java层到native层的映射。
所谓Java 层到native层的映射就是说JVM在调用 void native_init() 这个函数时,该怎么去找相应的动态链接库里面的函数。因为void native_init () 函数并没有在java文件中定义。
这里有2种办法,
一种是我们在C 文件中按照一定的命名规则来定义函数。
假设上面的MediaPlayer类在android.media包内。
我们的native层的代码可如下定义
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class MediaPlayer */
#ifndef _Included_MediaPlayer
#define _Included_MediaPlayer
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: MediaPlayer
* Method: init media player
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_android_media_MediaPlayer_native_init
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
其中jobject参数是JVM的MediaPlayer调用native_init时的对象,JNIEnv* 是一个全局环境结构。
这个命名规则,想必很容易看懂,两个修饰符JNIEXPORT JNICALL,应该分别用来兼容so和dll的导出函数的,JNICALL是用来兼容stdcall 等这些不同的函数调用方式的。
函数名字以Java开头,后面跟着的是声明这个函数的类的全路径,包括包名,最后是函数名。
可以发现,这个函数名实在是很丑陋,难以阅读。
第二种方法,我们可以进行动态注册:
我们来看看Android源码里的MediaPlayer的native是怎么写的。
当VM执行到System.loadLibrary()的时候就会去执行native libs中的JNI_OnLoad(JavaVM* vm, void* reserved)函数,因为JNI_OnLoad函数是从java层进入native层第一个调用的方法,所以可以在JNI_OnLoad函数中完成一些native层组件的初始化工作,同时更加重要的是,通常在JNI_jint JNI_OnLoad(JavaVM* vm, void* reserved)函数中会注册java层的native方法。下面看一段代码:
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -;
//判断一下JNI的版本
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL); if (register_android_media_MediaPlayer(env) < ) {
LOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
} if (register_android_media_MediaRecorder(env) < ) {
LOGE("ERROR: MediaRecorder native registration failed\n");
goto bail;
} if (register<span style="font-size:16px;">_android_media_MediaScanner(env) < ) {
LOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}</span> if (register_android_media_MediaMetadataRetriever(env) < ) {
LOGE("ERROR: MediaMetadataRetriever native registration failed\n");
goto bail;
} if (register_android_media_AmrInputStream(env) < ) {
LOGE("ERROR: AmrInputStream native registration failed\n");
goto bail;
} if (register_android_media_ResampleInputStream(env) < ) {
LOGE("ERROR: ResampleInputStream native registration failed\n");
goto bail;
} if (register_android_media_MediaProfiles(env) < ) {
LOGE("ERROR: MediaProfiles native registration failed");
goto bail;
} /* success -- return valid version number */
result = JNI_VERSION_1_4; bail:
return result;
}
上面这段代码的JNI_OnLoad(JavaVM* vm, void* reserved)函数实现与libmedia_jni.so库中。上面的代码中调用了一些形如register_android_media_MediaPlayer(env)的函数,这些函数的作用是注册native method。我们来看看函数register_android_media_MediaPlayer(env)的实现。
// This function only registers the native methods
static int register_android_media_MediaPlayer(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
』 }
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
最终jniRegisterNativeMethods函数完成java标准的native函数的映射工作。下面我们来具体的看看上面这个函数中各个参数的意义。
关于JNIEnv我在google上找到了这些信息:
JNI defines two key data structures, "JavaVM" and "JNIEnv". Both of these are essentiallypointers to pointers to function tables. (In the C++ version, they're classes with apointer to a function table and a member function for each JNI function that indirects throughthe table.) The JavaVM provides the "invocation interface" functions,which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process,but Android only allows one.
The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv asthe first argument.
The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads.If a piece of code has no other way to get its JNIEnv, you should sharethe JavaVM, and useGetEnv
to discover the thread's JNIEnv. (Assuming it has one; see AttachCurrentThread
below.)
这里需要注意一点的是,JNIEnv是一个线程的局部变量,这以为这JNIEnv是存在与多线程环境下的,因为 VM 通常是多执行绪(Multi-threading)的执行环境。每一个执行绪在呼叫JNI_OnLoad()时,所传递进来的 JNIEnv 指标值都是不同的。为了配合这种多执行绪的环境,C/C++组件开发者在撰写本地函数时,可藉由 JNIEnv 指标值之不同而避免执行绪的资料冲突问题,才能确保所写的本地函数能安全地在 Android 的多执行绪 VM 里安全地执行。基于这个理由,当在
呼叫 C/C++ 组件的函数时,都会将 JNIEnv 指标值传递给它。
b,char* className,这个没什么好说的,java空间中类名,其中包含了包名。
c,JNINativeMethod* gMethods,传递进去的是一个JNINativeMethod类型的指针gMethods,gMethods指向一个JNINativeMethod数组,我们先看看JNINativeMethod这个结构体。
typedef struct {
const char* name; /*Java 中函数的名字*/
const char* signature; /*描述了函数的参数和返回值*/
void* fnPtr; /*函数指针,指向 C 函数*/
} JNINativeMethod;
再来看看gMethods数组
static JNINativeMethod gMethods[] = {
{"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource},
。。。
{"setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer_setAuxEffectSendLevel},
{"attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer_attachAuxEffect},
{"getOrganDBIndex", "(II)I", (void *)android_media_MediaPlayer_getOrganDBIndex},
};
在JNINativeMethod的结构体中,有一个描述函数的参数和返回值的签名字段,它是java中对应函数的签名信息,由参数类型和返回值类型共同组成。这个函数签名信息的作用是什么呢?
由于java支持函数重载,也就是说,可以定义同名但不同参数的函数。然而仅仅根据函数名是没法找到具体函数的。为了解决这个问题,JNI技术中就将参数 类型和返回值类型的组合作为一个函数的签名信息,有了签名信息和函数名,就能顺利的找到java中的函数了。
JNI规范定义的函数签名信息格式如下:
(参数1类型标示参数2类型标示......参数n类型标示)返回值类型标示
“()V”
"(II)V"
“(Ljava/lang/String;Ljava/lang/String)V";
实际上这些字符是与函数的参数类型一一对应的。
“()” 中的字符表示参数,后面的则代表返回值。例如”()V” 就表示 void Func();
“(II)V” 表示 void Func(int, int);
值得注意的一点是,当参数类型是引用数据类型时,其格式是“L包名;”其中包名中的“.” 换成“/”,所以在上面的例子中(Ljava/lang/String;Ljava/lang/String;)V
表示 void Func(String,String);
如果 JAVA 函数位于一个嵌入类,则用$作为类名间的分隔符。
例如 “(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z”
下表是对应关系
JAVA类型 | 本地类型 | 对应的描述字符串 | 描述 |
boolean | jboolean | Z | C/C++8位整型 |
byte | jbyte | B | C/C++带符号8bit integer |
char | jchar | C | C/C++ 无符号16位整型 |
short | jshort | S | C/C++ 带符号的16位整型 |
int | jint | I | C/C++ 带符号的32位整型 |
long | jlong | J | C/C++ 带符号的64位整型 |
float | jfloat | F | C/C++ 32位浮点数 |
double | jdouble | D | C/C++ 64位浮点数 |
Object | jobject | 任何Java对象 | |
Class | jclass | Class 对象 | |
String | jstring | 字符串对象 | |
Object[] | jobjectArray | 任何对象的数组 | |
boolean[] | jbooleanArray | [Z | 布尔型数组 |
byte[] | jbyteArray | [B | byte型数组 |
char[] | jcharArray | [C | 短整型数组 |
short[] | jshortArray | [S | 整型数组 |
int[] | jintArray | [I | 长整型数组 |
long[] | jlongArray | [J | 浮点型数组 |
float[] | jfloatArray | [F | 双精度浮点数数组 |
double[] | jdoubleArray | [D | |
void | void | V |
java层和JNI层应该是可以互相交互,我们通过java层中的native函数可以进入到JNI层,那么JNI层的代码能不能操作java层中函数呢?当然可以,通过JNIEnv。
先来看看两个函数原型
jfieldID GetFieldID(jclass clazz,const char *name,const char *sig );
jmethodID GetMethodID(jclass clazz,const char *name,const char *sig);
结合前面的知识来看,JNIEnv是一个与线程相关的代表JNI环境的结构体。JNIEnv实际上提供了一些JNI系统函数。通过这些系统函数可以调用java层中的函数或者操作jobect。下面我看一段函数
class MyMediaScannerClient : public MediaScannerClient
{
public:
MyMediaScannerClient(JNIEnv *env, jobject client)
: mEnv(env),
mClient(env->NewGlobalRef(client)),
mScanFileMethodID(),
mHandleStringTagMethodID(),
mSetMimeTypeMethodID()
{
jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");
if (mediaScannerClientInterface == NULL) {
fprintf(stderr, "android/media/MediaScannerClient not found\n");
}
else {
mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",
"(Ljava/lang/String;JJ)V");
mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",
"(Ljava/lang/String;Ljava/lang/String;)V");
mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",
"(Ljava/lang/String;)V");
mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",
"(Ljava/lang/String;)V");
}
}
... // returns true if it succeeded, false if an exception occured in the Java code
virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
{
jstring pathStr;
if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false; mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize); mEnv->DeleteLocalRef(pathStr);
return (!mEnv->ExceptionCheck());
}
class MyMediaScannerClient : public MediaScannerClient
{
public:
MyMediaScannerClient(JNIEnv *env, jobject client)
: mEnv(env),
mClient(env->NewGlobalRef(client)),
mScanFileMethodID(),
mHandleStringTagMethodID(),
mSetMimeTypeMethodID()
{
jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");
if (mediaScannerClientInterface == NULL) {
fprintf(stderr, "android/media/MediaScannerClient not found\n");
}
else {
mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",
"(Ljava/lang/String;JJ)V");
mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",
"(Ljava/lang/String;Ljava/lang/String;)V");
mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",
"(Ljava/lang/String;)V");
mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",
"(Ljava/lang/String;)V");
}
}
... // returns true if it succeeded, false if an exception occured in the Java code
virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
{
jstring pathStr;
if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false; mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize); mEnv->DeleteLocalRef(pathStr);
return (!mEnv->ExceptionCheck());
}
可以看到上面的代码中,先找到java层中MediaScannerClinet类在JNI层中对应的jclass实例(通过FindClass)。然后拿到MediaScannerclient类中所需要用到函数的函数函数id(通过GetMethodID)。接着通过JNIEnv调用CallXXXMethod函数并且把对应的jobject,jMethodID还有对应的参数传递进去,这样的通过CallXXXMethod就完成了JNI层向java层的调用。这里要注意一点的是这里JNI层中调用的方法实际上是java中对象的成员函数,如果要调用static函数可以使用CallStaticXXXMethod。这种机制有利于native层回调java代码完成相应操作。
上面讲述了如下在JNI层中去调用java层的代码,那么理所当然的应该可以在JNI层中访问或者修改java层中某对象的成员变量的值。我们通过JNIEnv中的GetFieldID()函数来得到java中对象的某个域的id。看下面的具体代码
int register_android_backup_BackupHelperDispatcher(JNIEnv* env)
{
jclass clazz; clazz = env->FindClass("java/io/FileDescriptor");
LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");
s_descriptorField = env->GetFieldID(clazz, "descriptor", "I");
LOG_FATAL_IF(s_descriptorField == NULL,
"Unable to find descriptor field in java.io.FileDescriptor"); clazz = env->FindClass("android/app/backup/BackupHelperDispatcher$Header");
LOG_FATAL_IF(clazz == NULL,
"Unable to find class android.app.backup.BackupHelperDispatcher.Header");
s_chunkSizeField = env->GetFieldID(clazz, "chunkSize", "I");
LOG_FATAL_IF(s_chunkSizeField == NULL,
"Unable to find chunkSize field in android.app.backup.BackupHelperDispatcher.Header");
s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix", "Ljava/lang/String;");
LOG_FATAL_IF(s_keyPrefixField == NULL,
"Unable to find keyPrefix field in android.app.backup.BackupHelperDispatcher.Header"); return AndroidRuntime::registerNativeMethods(env, "android/app/backup/BackupHelperDispatcher",
g_methods, NELEM(g_methods));
}
获得jfieldID之后呢,我们就可以在JNI层之间来访问和操作java层的field的值了,方法如下
NativeType Get<type>Field(JNIEnv *env,jobject object,jfieldID fieldID) void Set<type>Field(JNIEnv *env,jobject object ,jfieldID fieldID,NativeType value)
现在我们看到有了JNIEnv,我们可以很轻松的操作jobject所代表的java层中的实际的对象了。
jstring介绍
之所以要把jstring单独拿出来说正是由于它的特殊性。java中String类型也是一个引用类型,但是JNI中并没有用jobject来与之对 应,JNI中单独创建了一个jstring类型来表示java中的String类型。显然java中的String不能和C++中的String等同起 来,那么怎么操作jstring呢?方法很多下面看几个简单的方法
1,调用JNIEnv的NewStringUTF将根据Native的一个UTF-8字符串得到一个jstring对象。只有这样才能让一个C++中String在JNI中使用。
2,调用JNIEnv的GetStringChars函数(将得到一个Unicode字符串)和GetStringUTFChars函数(将得到一个UTF-8字符串),他们可以将java String对象转换诚本地字符串。下面我们来看段示例代码。
virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
{
jstring pathStr;
//将char*数组字符串转换诚jstring类型
if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false; mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize); mEnv->DeleteLocalRef(pathStr);
return (!mEnv->ExceptionCheck());
} ....
....
while (env->CallBooleanMethod(iter, hasNext)) {
jobject entry = env->CallObjectMethod(iter, next);
jstring key = (jstring) env->CallObjectMethod(entry, getKey);
jstring value = (jstring) env->CallObjectMethod(entry, getValue); const char* keyStr = env->GetStringUTFChars(key, NULL);
...
...
GetStringUTFChars()函数将jstring类型转换成一个UTF-8本地字符串,另外如果代码中调用了上面的几个函数,则在做完相关工 作后,要调用ReleaseStringChars函数或者ReleaseStringUTFChars函数来释放资源。看下面的代码
...
...
jstring key = (jstring) env->CallObjectMethod(entry, getKey);
jstring value = (jstring) env->CallObjectMethod(entry, getValue); const char* keyStr = env->GetStringUTFChars(key, NULL);
if (!keyStr) { // Out of memory
jniThrowException(
env, "java/lang/RuntimeException", "Out of memory");
return;
} const char* valueStr = env->GetStringUTFChars(value, NULL);
if (!valueStr) { // Out of memory
jniThrowException(
env, "java/lang/RuntimeException", "Out of memory");
return;
} headersVector.add(String8(keyStr), String8(valueStr)); env->DeleteLocalRef(entry);
env->ReleaseStringUTFChars(key, keyStr);
env->DeleteLocalRef(key);
...
...
可以看到GetStringUTFChars与下面的ReleaseStringUTFChars对应。
主要是转载自
http://blog.csdn.net/mci2004/article/details/7211678
http://blog.csdn.net/mci2004/article/details/7219140
JNI 学习笔记的更多相关文章
- JNI学习笔记_Java调用C —— 非Android中使用的方法
一.学习笔记 1.java源码中的JNI函数本机方法声明必须使用native修饰. 2.相对反编译 Java 的 class 字节码文件来说,反汇编.so动态库来分析程序的逻辑要复杂得多,为了应用的安 ...
- JNI学习笔记_C调用Java
一.笔记 1.C调用Java中的方法,参考jni.pdf pg97可以参考博文:http://blog.csdn.net/lhzjj/article/details/26470999步骤: a. 创建 ...
- JNI学习笔记_Java调用C —— Android中使用的方法
一.笔记 1.JNI(Java Native Interface),就是如何使用java去访问C/C++编写的那些库.若想深入了解JNI可以看官方文档jni.pdf.优秀博文:Android JNI知 ...
- JNI 学习笔记系列(一)
JNI全称是Java native interface,它是一个中间件,通过JNI可以使Java和C语言之间互相调用,在android开发中,像wifi热点的开启,像极品飞车中重力加速,碰撞效果的模拟 ...
- JNI学习笔记
JNI是什么->一套c和java的互掉规则 为什么使用JNI 1.非常多敏感效率的代码已经用C实现了 2. JNI双向.java调用c,c调用java Java集成本地代码问题 1.代码 ...
- JNI 学习笔记系列(二)
c中没有Boolean类型的值,一般是使用1表示true,0表示false,c中也没有String类型的数据,c中的字符串要通过char数组来表示.c中没有byte类型,一般用char表示byte类型 ...
- android学习笔记----JNI中的c控制java
面向对象的底层实现 java作为面向对象高级语言,可对现实世界进行建模.和面向过程不同的是面向对象软件的编写不是流程的堆积,而是对业务逻辑的多视角分解和分类.其过程大致为: 1).将知识分解 ...
- android cocos2d-x for Android安装和学习笔记(请用adt-bundle21.1或以上导入)
引用:http://weimingtom.iteye.com/blog/1483566 (20121108)注意:这篇文章用cdt编译ndk工程的内容已过时(现在可以用adt-bundle,避免配置繁 ...
- 在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序(老罗学习笔记3)
简单来说,硬件驱动程序一方面分布在Linux内核中,另一方面分布在用户空间的硬件抽象层中.接着,在Ubuntu上为Android系统编写Linux内核驱动程序(老罗学习笔记1)一文中举例子说明了如何在 ...
随机推荐
- http://www.cnblogs.com/
<?php $filename = $_GET['filename']; header("Content-type: application/octet-stream"); ...
- 仿jQuery中undelegate()方法功能的函数
//跨浏览器事件绑定 function addEvent(obj,type,fn){ if(typeof obj.removeEventListener !='undefined'){ /////// ...
- Java多线程之线程池
现在是多核的时代,面向多核的编程很重要,因此基于java的并发和多线程开发非常重要. 线程池是于队列密切相关的,其中队列保存了所有等待执行的任务.工作者线程的任务很简单:从队列中获取一个任务,执行任务 ...
- 在centos中创建nginx启动脚本
1. 建立脚本文件nginxd [root@could]# vi /etc/init.d/nginxd 插入以下内容 #!/bin/bash## chkconfig: - 85 15# descrip ...
- 洛谷P1717 钓鱼
P1717 钓鱼 41通过 116提交 题目提供者该用户不存在 标签贪心 难度提高+/省选- 提交该题 讨论 题解 记录 最新讨论 暂时没有讨论 题目描述 话说发源于小朋友精心设计的游戏被电脑组的童鞋 ...
- jQuery中的DOM操作<思维导图>
DOM是Document Object Model的缩写,意思是文档对象模型.DOM是一种与浏览器.平台.语言无关的接口.使用该接口可以轻松地访问页面中所有的标准组件.简单来说,DOM解决了Netsc ...
- JQuery处理json与ajax返回JSON实例
一.JSON的一些基础知识. JSON中对象通过“{}”来标识,一个“{}”代表一个对象,如{“AreaId”:”123”},对象的值是键值对的形式(key:value). “[]”,标识数组,数组内 ...
- 嵌入值和序列化LOB
Embedded Value 把一个对象映射成另一个对象表中的若干字段. OO系统中会有很多小对象(DataRange,Money).而作为表在DB中毫无意义. 默认想法是把一个对象保存为一个表. 但 ...
- (转)Centos5.5安装MONO2.10.8和Jexus 5.0开启Linux平台.net应用新篇章
注:本文只做本人记录使用,也可供大家参考,有兴趣的可以一起讨论. 安装步骤 1.yum –y update 2.安装Mono源码安装需要的库 yum -y install gcc gcc-c++ bi ...
- js高程笔记1-3章
第1章 js简介 1.js由三部分组成,ECMAScript, DOM, BOM. 第2章 在HTML中使用js 1.把<script>标签放在<body>里面的最后,可以在加 ...