注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

民主与科学

独立之人格,自由之思想

 
 
 

日志

 
 

JNI系统程序开发  

2011-06-18 15:58:34|  分类: 开发专题 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
JNI是Java Native Interface的缩写。从Java 1.1开始,JNI标准成为java平台的一部分,它允许Java和其他语言进行交互。
    JNI一开始为C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。 使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,它可以直接与硬件、操作系统进行交互,也能提高程序的性能。
    Android中JNI系统程序和应用程序的开发有些细微的差别。本文章主要是针对Android中JNI系统程序的开发,如果你想进步了解应用程序请阅读《JNI应用程序开发
 Jni程序开发的一般操作步骤如下:
(1) 编写带有native声明的方法的java类
(2) 编译java类成class文件,使用javah -jni java类名生成扩展名为h的头文件
(3)使用C++ 实现本地方法
(4)编辑Android.mk
(5)编译JNI的C/C++源码生成.so文件
(6) 在Java中把.so文件载入到java.library.path
(7)把JNI函数注册到系统
(8) 在java中调用native方法.
一 Java中声明JNI函数
在Java声明一个JNI函数很简单,就像在接口中声明函数一样,只是在前面多加个前缀Native.
比如:native String getStringFromJni();
:对于Java中声明JNI函数,JNI系统程序和应用程序开发都一样
二 编译java类成class文件,使用javah -jni java类名生成扩展名为h的头文件
如果你需要不同的JNI的C\C++实现文件的函数能互相调用,那么你就需要为你的JNI的C\C++端编写扩展名为h的头文件。
你可以手写头文件,也可以使用javah命令自动生成
关于javah命令的详细内容请参考《javah命令详解
对于JNI应用程序,一般不需要不同的JNI的C\C++实现文件的函数能互相调用。
但是对JNI系统程序,最好还是让不同的JNI的C\C++实现文件的函数能互相调用。
如果我们是通过registerNativeMethods()手动把的JNI本地和相应java函数的对应关系注册到系统的,那么本地方法的命名似乎并没有严格的规定,否则本地方法的命名就必须严格按照规范来,必须为java_java类的包名_java类名_Java的函数名的形式,这里java类的包名用"_"代替"."。JNI系统程序一般都是采用前面一种方式,JNI应用程序则通常不用把JNI的C/C++函数和相应java函数的对应关系注册到系统。
比如Hello.java文件
package com.robin;
public class Hello{
void sayHello()
{
System.out.println(getHelloStringFromJni());
}
native String getHelloStringFromJni();
}
JNI的本地方法就应该为:
JNIEXPORT jstring JNICALL Java_com_robin_Hello_getHelloStringFromJni(JNIEnv *, jobject);
三 使用C/C++ 实现本地方法
接着可以开始在.c或.cpp文件中实现头文件声明的JNI函数。
JNI的所有的本地方法的第一个参数都是指向JNIEnv结构的。这个结构是用来调用JNI函数的。第二个参数jclass/jobject的意义,要看方法是不是静态的(static)或者实例(Instance)的。前者,jclass代表一个类对象的引用,而后者是被调用的方法所属对象的引用。 从第三个参数开始的才是java函数本身传递的参数。函数返回值和传入参数的类型都是根据约定映射java类型到本地C/C++类型。我们可以在JNI的C\C++本地方法对传入的java参数进行操作处理,处理完成可以返回空,也可以返回一个映射到C/C++的java类型。
关于JNI中参数的传递与操作的更多内容请参考《JNI中参数的传递与操作
:在C/C++中,如果一个函数没有进行声明,那么在定义它时,就同时进行声明了。
四 编辑Android.mk
示例1 
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)。
关于Android.mk的详细内容请参照《Android.mk
五 编译JNI的C\C++源码生成.so文件
对于JNI应用程序,编译JNI的C\C++源码是使用NDK来进行编译,具体请参照《JNI应用程序开发
对于JNI系统程序,你只要提供了Android.mk文件,在编译Android系统的时候,该Anroid.mk就会被执行,当然就进行编译并生成.so文件。
一个.so文件就相当于一个JNI组件。
另外注意,Android.mk中通过“LOCAL_MODULE:= hello-jni”指定包名,此处包名为"hello-jni",生成的.so文件名为lib+包名+.so形成,这里将生成libhello-jni.so文件
六 在Java中把.so文件载入到java.library.path
在java代码中可以通过 System.loadLibrary方法把.so文件载入java.library.path,以便我们使用。
比如
static {
         System.loadLibrary("hello-jni");
   }
   注意:System.loadLibrary的参数是包名hello-jni,而不文件名libhello-jni.so
JNI_OnLoad()JNI_OnUnload()函数
    当Android的VM(Virtual Machine)执行到System.loadLibrary()函数时,首先会去执行JNI组件(*.so)里的JNI_OnLoad()函数。
它的用途有二
    1)告诉VM此C组件使用那一个JNI版本。如果你的在*.so没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的JNI 1.1版本。
    由于新版的JNI做了许多扩充,如果你需要使用JNI的新版功能(例如JNI 1.4的java.nio.ByteBuffer),那么JNI_OnLoad()函数返回JNI_VERSION_1_4或以上。如果你返回JNI_VERSION_1_4的话,就是告知VM你使用的是JNI_VERSION_1_4版本的JNI。
    2)由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),    所以C组件的开发者可以藉由JNI_OnLoad()来进行组件内的初期值之设定(Initialization) 。
    例如,在Android的/system/lib/libmedia_jni.so档案里,就提供了JNI_OnLoad()函数,其程式码片段为:
示例1
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaPlayer-JNI"
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
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) < 0) {
LOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
}
 if (register_android_media_MediaRecorder(env) < 0) {
LOGE("ERROR: MediaRecorder native registration failed\n");
goto bail;
}
 if (register_android_media_MediaScanner(env) < 0) {
LOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
 if (register_android_media_MediaMetadataRetriever(env) < 0) {
LOGE("ERROR: MediaMetadataRetriever native registration failed\n");
goto bail;
}
 /* success -- return valid version number */
result = JNI_VERSION_1_4;
bail:
return result;
}
此函数回传JNI_VERSION_1_4值给VM,于是VM知道了其所使用的JNI版本了。此外,它也做了一些初期的动作(该函数可呼叫任何本地函数),例如指令:
if (register_android_media_MediaPlayer(env) < 0) {
LOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
}
这里register_android_media_MediaPlayer(env)将此组件提供的各个本地函数(Native Function)登记到VM里,以便能加快后续呼叫本地函数的效率。关于JNI本地函数的登记的详细内容请参考后文。
当VM释放JNI组件时,则会调用JNI_OnUnload()函数,我们可以在此添加一些善后清除的代码。
当VM呼叫JNI_OnLoad()JNI_Unload()函数时,都会将VM的指针(Pointer)传递给它们,其参数如下:
jint JNI_OnLoad(JavaVM* vm, void* reserved) { }
jint JNI_OnUnload(JavaVM* vm, void* reserved){ }
在JNI_OnLoad()函数里,就必须透过VM来取得JNIEnv,并存入env变量里,如下述指令:
 jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed\n");
goto bail;
}
}
由于VM通常是多线程(Multi-threading)的执行环境。每一个线程在呼叫JNI_OnLoad()时,所传递进来的JNIEnv指标值都是不同的。
为了配合这种多执行绪的环境,JNI组件开发者在撰写本地函数时,也就可因JNIEnv不同而避免线程冲突问题,
这样就能确保所写的本地函数能安全地在Android的多线程的VM里安全地执行。基于这个理由,当在呼叫JNI组件的本地函数时,都会将JNIEnv指标值传递给它。如示例1,调用register_android_media_MediaPlayer(env)函数时,就将env指标值传递过去。如此,在register_android_media_MediaPlayer()函数就能根据JNIEnv的不同而避免线程的冲突。
可以撰写下述指令:
if ((*env)->MonitorEnter(env, obj) == JNI_OK) {
//代码1
}else {
//代码2
}
该段代码用于查看是否已经有JNIEnv进入此obj,如果没有,此JNIEnv就进入该obj并返回JNI_OK,然后执行代码1;否则执行代码2.
还有,也可撰写下述指令:
if ((*env)->MonitorExit(env, obj) == JNI_OK) {
//代码1
 }else
{
//代码2
}
该段代码用于查看该JNIEnv是否正在此obj,如果是该JNIEnv就离开此obj并返回JNI_OK,执行代码1,否则执行代码2
另外注意对于本步骤,JNI应用程序开发和JNI系统程序开发没有什么不同。
七 把JNI函数注册到系统
    应用层级的Java类别通过VM而调用到本地函数,VM默认是按照JNI的java函数和C\C++本地函数的命名规范,去寻找*.so里的本地函数。查找的过程还是有点耗时
    如果调用次数很多,而每次都寻找一遍的话,会花掉许多时间。
如果你想节约VM查找JNI本地函数的时间,你可以通过registerNativeMethods()函数把JNI本地函数向VM进行登记,以节约VM查找JNI本地函数的时间。
如果你的JNI本地函数的命名没严格遵守java函数和C\C++本地函数对应的命名规范,你必须通过registerNativeMethods()函数把JNI本地函数向VM进行登记,以便VM能找到你的JNI本地函数
例如,在Android的/system/lib/libmedia_jni.so档案里的代码段如下:
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaPlayer-JNI"
static JNINativeMethod gMethods[] = {
{"setDataSource", "(Ljava/lang/String;)V",
(void *)android_media_MediaPlayer_setDataSource},
{"setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
(void *)android_media_MediaPlayer_setDataSourceFD},
{"prepare", "()V", (void *)android_media_MediaPlayer_prepare},
{"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync},
{"_start", "()V", (void *)android_media_MediaPlayer_start},
{"_stop", "()V", (void *)android_media_MediaPlayer_stop},
{"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth},
{"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight},
{"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo},
{"_pause", "()V", (void *)android_media_MediaPlayer_pause},
{"isPlaying", "()Z", (void *)android_media_MediaPlayer_isPlaying},
{"getCurrentPosition", "()I", (void *)android_media_MediaPlayer_getCurrentPosition},
{"getDuration", "()I", (void *)android_media_MediaPlayer_getDuration},
{"_release", "()V", (void *)android_media_MediaPlayer_release},
{"_reset", "()V", (void *)android_media_MediaPlayer_reset},
{"setAudioStreamType","(I)V", (void *)android_media_MediaPlayer_setAudioStreamType},
{"setLooping", "(Z)V", (void *)android_media_MediaPlayer_setLooping},
{"setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume},
{"getFrameAt", "(I)Landroid/graphics/Bitmap;",
(void *)android_media_MediaPlayer_getFrameAt},
{"native_setup", "(Ljava/lang/Object;)V",
(void *)android_media_MediaPlayer_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize},
};
 static int register_android_media_MediaPlayer(JNIEnv *env){
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
 jint JNI_OnLoad(JavaVM* vm, void* reserved){
//省略
if (register_android_media_MediaPlayer(env) < 0) {
LOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
}
//省略
}
   当VM载入libmedia_jni.so档案时,就调用JNI_OnLoad()函数。接着,在JNI_OnLoad()中调用register_android_media_MediaPlayer()函数然后,在register_android_media_MediaPlayer()函数中调用AndroidRuntime::registerNativeMethods()函数,
向VM(即AndroidRuntime)登记gMethods[]数组所含的本地函数
简而言之,registerNativeMethods()函数的用途有二
1) 更有效率去找到函数
2) 可在执行期间进行抽换。由于gMethods[]是一个关于JNI的<java函数,本地函数指针>对照表,在程序执行时,可多次呼叫registerNativeMethods()函数来更换本地函数之指针,而达到弹性抽换本地函数之目的。
Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数。其中很重要的区别是Andorid使用了一种Java 和 C 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组的类型是JNINativeMethod,定义如下:
typedef struct {
const char* name; /*Java中函数的名字*/
const char* signature; /*用于描述函数的参数和返回值的字符*/
void* fnPtr; /*本地函数指针,指向C函数*/
} JNINativeMethod;
其中比较难以理解的是第二个参数,例如 "()V" ,"(II)V", "(Ljava/lang/String;Ljava/lang/String;)V". 实际上这些字符是与函数的参数类型一一对应的。"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func(); "(II)V" 表示 void Func(int, int);
关于此的详细内容请参照《JNI中java类型的简写
另外还有需要指出的是对于本步骤,JNI应用程序开发和JNI系统程序开发没有什么不同。
 八、在java中调用native方法.
 无论是对于JNI应用程序开发,还是JNI系统程序开发,你可以在java中像调用普遍的java方法一样调用native方法
  评论这张
 
阅读(1032)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017