您的位置:首页 > 移动开发 > Android开发

Android的NDK开发(1)————Android JNI简介与调用流程

2015-04-08 15:06 471 查看
1、JNI简介

JNI全称为Java Native Interface(JAVA本地调用)。从Java1.1开始,JNI成为java平台的一部分,它允许Java代码和其他语言写的代码(如C&C++)进行交互。并非从Android发布才引入JNI的概念的。

2、JNI与NDK

        简单来说,Android的NDK提供了一些交叉编译工具链和Android自带的库,这些Android的库可以让开发者在编写本地语言的程序时调用。而NDK提供的交叉编译工具链就对已经编写好的C&C++代码进行编译,生成库。

        当然了,你也可以自己搭建交叉编译环境,而不用NDK的工具和库。然后生成库,只要规范操作,一样可以生成能让JAVA层成功调用的库文件的。

        

      利用NDK进行编译本地语言可以参考这篇博文:http://blog.csdn.net/conowen/article/details/7522667

      

3、JNI  调用流程

         众所周知,Android的应用层的类都是以Java写的,这些Java类编译为Dex文件之后,必须靠Dalvik虚拟机( Virtual Machine)来执行。假如在执行java程序时,需要载入C&C++函数时,Dalvik虚拟机就会去加载C&C++的库,(System.loadLibrary("libName");)让java层能顺利地调用这些本地函数。需要清楚一点,这些C&C++的函数并不是在Dalvik虚拟机中运行的,所以效率和速度要比在Dalvik虚拟机中运行得快很多。

       Dalvik虚拟机成功加载库之后,就会自动地寻找库里面的JNI_OnLoad函数,这个函数用途如下:

(1)告诉Dalvik虚拟机此C库使用哪一个JNI版本。如果你的库里面没有写明JNI_OnLoad()函数,VM会默认该库使用最老的JNI 1.1版本。但是新版的JNI做了很多的扩充,也优化了一些内容,如果需要使用JNI的新版功能,就必须在JNI_OnLoad()函数声明JNI的版本。如

[java]
view plaincopyprint?





result = JNI_VERSION_1_4; 

result = JNI_VERSION_1_4;


当没有JNI_OnLoad()函数时,Android调试信息会做出如下提示(No JNI_OnLoad found)

[java]
view plaincopyprint?





04-29
13:53:12.184: D/dalvikvm(361): Trying to load lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so
0x44edea98 
04-29
13:53:12.204: D/dalvikvm(361): Added shared lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so
0x44edea98 
04-29
13:53:12.204: D/dalvikvm(361): No JNI_OnLoad found in /data/data/com.conowen.helloworld/lib/libHelloWorld.so
0x44edea98, skipping init 

04-29 13:53:12.184: D/dalvikvm(361): Trying to load lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98
04-29 13:53:12.204: D/dalvikvm(361): Added shared lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98
04-29 13:53:12.204: D/dalvikvm(361): No JNI_OnLoad found in /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98, skipping init


(2)因为Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数,所以我们可以在JNI_OnLoad()里面进行一些初始化工作,如注册JNI函数等等。注册本地函数,可以加快java层调用本地函数的效率。

另外:与JNI_OnLoad()函数相对应的有JNI_OnUnload()函数,当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行善后清除动作。

4、例子(关于jni里面的数据类型转换与常用jni方法下一篇博文介绍)

下面以havlenapetr的FFmpeg工程里面的onLoad.cpp为例详细说一下:

[cpp]
view plaincopyprint?





//onLoad.cpp文件 
 
#define TAG "ffmpeg_onLoad" 

 
#include <stdlib.h> 

#include <android/log.h> 

#include "jniUtils.h" 

 
extern "C" { 

 
extern int register_android_media_FFMpegAVRational(JNIEnv *env); 

 
#ifdef BUILD_WITH_CONVERTOR 

extern int register_android_media_FFMpeg(JNIEnv *env); 

#endif 
 
extern int register_android_media_FFMpegAVFormatContext(JNIEnv *env); 

extern int register_android_media_FFMpegAVInputFormat(JNIEnv *env); 

 

 
extern int register_android_media_FFMpegAVCodecContext(JNIEnv *env); 

extern int register_android_media_FFMpegUtils(JNIEnv *env); 

extern int register_android_media_FFMpegAVFrame(JNIEnv *env); 

 
#ifdef BUILD_WITH_PLAYER 

extern int register_android_media_FFMpegPlayerAndroid(JNIEnv *env); 

#endif 
 
static JavaVM *sVm; 
 
/*
* Throw an exception with the specified class and an optional message.

*/ 
int jniThrowException(JNIEnv* env,
const char* className,
const char* msg) { 

    jclass exceptionClass = env->FindClass(className); 
    if (exceptionClass == NULL) { 

        __android_log_print(ANDROID_LOG_ERROR, 
                TAG, 
                "Unable to find exception class %s", 

                        className); 
        return -1; 
    } 
 
    if (env->ThrowNew(exceptionClass, msg) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, 
                TAG, 
                "Failed throwing '%s' '%s'", 

                className, msg); 
    } 
    return 0; 

 
JNIEnv* getJNIEnv() { 
    JNIEnv* env = NULL; 
    if (sVm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, 
                            TAG, 
                            "Failed to obtain JNIEnv"); 

        return NULL; 
    } 
    return env; 

 
/*
* Register native JNI-callable methods.

*
* "className" looks like "java/lang/String".

*/ 
int jniRegisterNativeMethods(JNIEnv* env, 

                             const
char* className, 
                             const JNINativeMethod* gMethods, 

                             int numMethods) 

/*从com_media_ffmpeg_FFMpegPlayer.cpp文件跳到此,完成最后的注册

* 向 Dalvik虚拟机(即AndroidRuntime)登记传过来的参数gMethods[]所含的本地函数

*/ 

    jclass clazz; 
 
    __android_log_print(ANDROID_LOG_INFO, TAG, "Registering %s natives\n", className); 

    clazz = env->FindClass(className); 
    if (clazz == NULL) { 
        __android_log_print(ANDROID_LOG_ERROR, TAG,
"Native registration unable to find class '%s'\n", className); 

        return -1; 
    } 
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"RegisterNatives failed for '%s'\n", className); 
        return -1; 
    } 
    return 0; 

//Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数 

jint JNI_OnLoad(JavaVM* vm, void* reserved) { 

    JNIEnv* env = NULL;//定义JNI Env 

    jint result = JNI_ERR; 
    sVm = vm; 
    /*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);

     * GetEnv()函数返回的  Jni 环境对每个线程来说是不同的,   

     *  由于Dalvik虚拟机通常是Multi-threading的。每一个线程调用JNI_OnLoad()时,

     *  所用的JNI Env是不同的,因此我们必须在每次进入函数时都要通过vm->GetEnv重新获取

     * 
     */ 
    //得到JNI Env 

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"GetEnv failed!"); 
        return result; 
    } 
 
    __android_log_print(ANDROID_LOG_INFO, TAG,
"loading . . ."); 
 
/*开始注册
* 传入参数是JNI env
* 由于下面有很多class,只以register_android_media_FFMpegPlayerAndroid(env)为例子

*/ 
     
#ifdef BUILD_WITH_CONVERTOR 

    if(register_android_media_FFMpeg(env) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"can't load android_media_FFMpeg"); 
        goto end; 

    } 
#endif 
 
    if(register_android_media_FFMpegAVFormatContext(env) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"can't load android_media_FFMpegAVFormatContext"); 
        goto end; 

    } 
 
    if(register_android_media_FFMpegAVCodecContext(env) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"can't load android_media_FFMpegAVCodecContext"); 
        goto end; 
    } 
 
    if(register_android_media_FFMpegAVRational(env) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"can't load android_media_FFMpegAVRational"); 
        goto end; 

    } 
     
    if(register_android_media_FFMpegAVInputFormat(env) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"can't load android_media_FFMpegAVInputFormat"); 
        goto end; 
    } 
     
    if(register_android_media_FFMpegUtils(env) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"can't load android_media_FFMpegUtils"); 
        goto end; 

    } 
 
    if(register_android_media_FFMpegAVFrame(env) != JNI_OK) { 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"can't load android_media_FFMpegAVFrame"); 
        goto end; 
    } 
 
#ifdef BUILD_WITH_PLAYER 

    if(register_android_media_FFMpegPlayerAndroid(env) != JNI_OK) {//跳到----》com_media_ffmpeg_FFMpegPlayer.cpp文件 

        __android_log_print(ANDROID_LOG_ERROR, TAG,
"can't load android_media_FFMpegPlayerAndroid"); 
        goto end; 
    } 
#endif 
 
    __android_log_print(ANDROID_LOG_INFO, TAG, "loaded"); 

 
   result = JNI_VERSION_1_4; 
 
end: 
    return result; 



//onLoad.cpp文件

#define TAG "ffmpeg_onLoad"

#include <stdlib.h>
#include <android/log.h>
#include "jniUtils.h"

extern "C" {

extern int register_android_media_FFMpegAVRational(JNIEnv *env);

#ifdef BUILD_WITH_CONVERTOR
extern int register_android_media_FFMpeg(JNIEnv *env);
#endif

extern int register_android_media_FFMpegAVFormatContext(JNIEnv *env);
extern int register_android_media_FFMpegAVInputFormat(JNIEnv *env);

}

extern int register_android_media_FFMpegAVCodecContext(JNIEnv *env);
extern int register_android_media_FFMpegUtils(JNIEnv *env);
extern int register_android_media_FFMpegAVFrame(JNIEnv *env);

#ifdef BUILD_WITH_PLAYER
extern int register_android_media_FFMpegPlayerAndroid(JNIEnv *env);
#endif

static JavaVM *sVm;

/*
* Throw an exception with the specified class and an optional message.
*/
int jniThrowException(JNIEnv* env, const char* className, const char* msg) {
jclass exceptionClass = env->FindClass(className);
if (exceptionClass == NULL) {
__android_log_print(ANDROID_LOG_ERROR,
TAG,
"Unable to find exception class %s",
className);
return -1;
}

if (env->ThrowNew(exceptionClass, msg) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR,
TAG,
"Failed throwing '%s' '%s'",
className, msg);
}
return 0;
}

JNIEnv* getJNIEnv() {
JNIEnv* env = NULL;
if (sVm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR,
TAG,
"Failed to obtain JNIEnv");
return NULL;
}
return env;
}

/*
* Register native JNI-callable methods.
*
* "className" looks like "java/lang/String".
*/
int jniRegisterNativeMethods(JNIEnv* env,
const char* className,
const JNINativeMethod* gMethods,
int numMethods)
/*从com_media_ffmpeg_FFMpegPlayer.cpp文件跳到此,完成最后的注册
* 向 Dalvik虚拟机(即AndroidRuntime)登记传过来的参数gMethods[]所含的本地函数
*/
{
jclass clazz;

__android_log_print(ANDROID_LOG_INFO, TAG, "Registering %s natives\n", className);
clazz = env->FindClass(className);
if (clazz == NULL) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "Native registration unable to find class '%s'\n", className);
return -1;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "RegisterNatives failed for '%s'\n", className);
return -1;
}
return 0;
}
//Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;//定义JNI Env
jint result = JNI_ERR;
sVm = vm;
/*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
* GetEnv()函数返回的  Jni 环境对每个线程来说是不同的,
*  由于Dalvik虚拟机通常是Multi-threading的。每一个线程调用JNI_OnLoad()时,
*  所用的JNI Env是不同的,因此我们必须在每次进入函数时都要通过vm->GetEnv重新获取
*
*/
//得到JNI Env
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "GetEnv failed!");
return result;
}

__android_log_print(ANDROID_LOG_INFO, TAG, "loading . . .");

/*开始注册
* 传入参数是JNI env
* 由于下面有很多class,只以register_android_media_FFMpegPlayerAndroid(env)为例子
*/

#ifdef BUILD_WITH_CONVERTOR
if(register_android_media_FFMpeg(env) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpeg");
goto end;
}
#endif

if(register_android_media_FFMpegAVFormatContext(env) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVFormatContext");
goto end;
}

if(register_android_media_FFMpegAVCodecContext(env) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVCodecContext");
goto end;
}

if(register_android_media_FFMpegAVRational(env) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVRational");
goto end;
}

if(register_android_media_FFMpegAVInputFormat(env) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVInputFormat");
goto end;
}

if(register_android_media_FFMpegUtils(env) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegUtils");
goto end;
}

if(register_android_media_FFMpegAVFrame(env) != JNI_OK) {
__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVFrame");
goto end;
}

#ifdef BUILD_WITH_PLAYER
if(register_android_media_FFMpegPlayerAndroid(env) != JNI_OK) {//跳到----》com_media_ffmpeg_FFMpegPlayer.cpp文件
__android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegPlayerAndroid");
goto end;
}
#endif

__android_log_print(ANDROID_LOG_INFO, TAG, "loaded");

result = JNI_VERSION_1_4;

end:
return result;
}


[cpp]
view plaincopyprint?





//com_media_ffmpeg_FFMpegPlayer.cpp文件 

/*
*
* 由于代码量较大,com_media_ffmpeg_FFMpegPlayer.cpp开始的一部分省略,只是贴出注册函数的相关部分。

*/ 
static const
char* const kClassPathName =
"com/media/ffmpeg/FFMpegPlayer"; 

/*
* 由于gMethods[]是一个<名称,函数指针>对照表,在程序执行时,

* 可多次调用registerNativeMethods()函数来更换本地函数的指针,

* 从而达到弹性调用本地函数的目的。
*/ 
static JNINativeMethod gMethods[] = { 

    {"setDataSource",      
"(Ljava/lang/String;)V",            (void *)com_media_ffmpeg_FFMpegPlayer_setDataSource}, 

    {"_setVideoSurface",    "(Landroid/view/Surface;)V",        (void *)com_media_ffmpeg_FFMpegPlayer_setVideoSurface}, 

    {"prepare",            
"()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_prepare}, 

    {"_start",              "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_start}, 

    {"_stop",              
"()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_stop}, 

    {"getVideoWidth",       "()I",                              (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth}, 

    {"getVideoHeight",     
"()I",                              (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight}, 

    {"seekTo",              "(I)V",                             (void *)com_media_ffmpeg_FFMpegPlayer_seekTo}, 

    {"_pause",             
"()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_pause}, 

    {"isPlaying",           "()Z",                              (void *)com_media_ffmpeg_FFMpegPlayer_isPlaying}, 

    {"getCurrentPosition", 
"()I",                              (void *)com_media_ffmpeg_FFMpegPlayer_getCurrentPosition}, 

    {"getDuration",         "()I",                              (void *)com_media_ffmpeg_FFMpegPlayer_getDuration}, 

    {"_release",           
"()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_release}, 

    {"_reset",              "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_reset}, 

    {"setAudioStreamType", 
"(I)V",                             (void *)com_media_ffmpeg_FFMpegPlayer_setAudioStreamType}, 

    {"native_init",         "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_native_init}, 

    {"native_setup",       
"(Ljava/lang/Object;)V",            (void *)com_media_ffmpeg_FFMpegPlayer_native_setup}, 

    {"native_finalize",     "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_native_finalize}, 

    {"native_suspend_resume",
"(Z)I",                           (void *)com_media_ffmpeg_FFMpegPlayer_native_suspend_resume}, 

}; 
 
int register_android_media_FFMpegPlayerAndroid(JNIEnv *env) { 

    return jniRegisterNativeMethods(env, kClassPathName, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])); 

    /*跳到OnLoad.cpp文件中的
     * jint jniRegisterNativeMethods(JNIEnv* env,

                             const char* className,

                             const JNINativeMethod* gMethods,

                             int numMethods)

                            
     */ 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: