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

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的版本。如

  1. result = JNI_VERSION_1_4; 
 result = JNI_VERSION_1_4;

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

  1. 04-29 13:53:12.184: D/dalvikvm(361): Trying to load lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98 
  2. 04-29 13:53:12.204: D/dalvikvm(361): Added shared lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98 
  3. 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为例详细说一下:

  1. //onLoad.cpp文件 
  2.  
  3. #define TAG "ffmpeg_onLoad" 
  4.  
  5. #include <stdlib.h> 
  6. #include <android/log.h> 
  7. #include "jniUtils.h" 
  8.  
  9. extern "C"
  10.  
  11. extern int register_android_media_FFMpegAVRational(JNIEnv *env); 
  12.  
  13. #ifdef BUILD_WITH_CONVERTOR 
  14. extern int register_android_media_FFMpeg(JNIEnv *env); 
  15. #endif 
  16.  
  17. extern int register_android_media_FFMpegAVFormatContext(JNIEnv *env); 
  18. extern int register_android_media_FFMpegAVInputFormat(JNIEnv *env); 
  19.  
  20.  
  21. extern int register_android_media_FFMpegAVCodecContext(JNIEnv *env); 
  22. extern int register_android_media_FFMpegUtils(JNIEnv *env); 
  23. extern int register_android_media_FFMpegAVFrame(JNIEnv *env); 
  24.  
  25. #ifdef BUILD_WITH_PLAYER 
  26. extern int register_android_media_FFMpegPlayerAndroid(JNIEnv *env); 
  27. #endif 
  28.  
  29. static JavaVM *sVm; 
  30.  
  31. /*
  32. * Throw an exception with the specified class and an optional message.
  33. */ 
  34. int jniThrowException(JNIEnv* env, const char* className, const char* msg) { 
  35.     jclass exceptionClass = env->FindClass(className); 
  36.     if (exceptionClass == NULL) { 
  37.         __android_log_print(ANDROID_LOG_ERROR, 
  38.                 TAG, 
  39.                 "Unable to find exception class %s"
  40.                         className); 
  41.         return -1; 
  42.     } 
  43.  
  44.     if (env->ThrowNew(exceptionClass, msg) != JNI_OK) { 
  45.         __android_log_print(ANDROID_LOG_ERROR, 
  46.                 TAG, 
  47.                 "Failed throwing '%s' '%s'"
  48.                 className, msg); 
  49.     } 
  50.     return 0; 
  51.  
  52. JNIEnv* getJNIEnv() { 
  53.     JNIEnv* env = NULL; 
  54.     if (sVm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 
  55.         __android_log_print(ANDROID_LOG_ERROR, 
  56.                             TAG, 
  57.                             "Failed to obtain JNIEnv"); 
  58.         return NULL; 
  59.     } 
  60.     return env; 
  61.  
  62. /*
  63. * Register native JNI-callable methods.
  64. *
  65. * "className" looks like "java/lang/String".
  66. */ 
  67. int jniRegisterNativeMethods(JNIEnv* env, 
  68.                              const char* className, 
  69.                              const JNINativeMethod* gMethods, 
  70.                              int numMethods) 
  71. /*从com_media_ffmpeg_FFMpegPlayer.cpp文件跳到此,完成最后的注册
  72. * 向 Dalvik虚拟机(即AndroidRuntime)登记传过来的参数gMethods[]所含的本地函数
  73. */ 
  74.     jclass clazz; 
  75.  
  76.     __android_log_print(ANDROID_LOG_INFO, TAG, "Registering %s natives\n", className); 
  77.     clazz = env->FindClass(className); 
  78.     if (clazz == NULL) { 
  79.         __android_log_print(ANDROID_LOG_ERROR, TAG, "Native registration unable to find class '%s'\n", className); 
  80.         return -1; 
  81.     } 
  82.     if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { 
  83.         __android_log_print(ANDROID_LOG_ERROR, TAG, "RegisterNatives failed for '%s'\n", className); 
  84.         return -1; 
  85.     } 
  86.     return 0; 
  87. //Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数 
  88. jint JNI_OnLoad(JavaVM* vm, void* reserved) { 
  89.     JNIEnv* env = NULL;//定义JNI Env 
  90.     jint result = JNI_ERR; 
  91.     sVm = vm; 
  92.     /*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
  93.      * GetEnv()函数返回的  Jni 环境对每个线程来说是不同的,   
  94.      *  由于Dalvik虚拟机通常是Multi-threading的。每一个线程调用JNI_OnLoad()时,
  95.      *  所用的JNI Env是不同的,因此我们必须在每次进入函数时都要通过vm->GetEnv重新获取
  96.      * 
  97.      */ 
  98.     //得到JNI Env 
  99.     if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 
  100.         __android_log_print(ANDROID_LOG_ERROR, TAG, "GetEnv failed!"); 
  101.         return result; 
  102.     } 
  103.  
  104.     __android_log_print(ANDROID_LOG_INFO, TAG, "loading . . ."); 
  105.  
  106. /*开始注册
  107. * 传入参数是JNI env
  108. * 由于下面有很多class,只以register_android_media_FFMpegPlayerAndroid(env)为例子
  109. */ 
  110.      
  111. #ifdef BUILD_WITH_CONVERTOR 
  112.     if(register_android_media_FFMpeg(env) != JNI_OK) { 
  113.         __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpeg"); 
  114.         goto end; 
  115.     } 
  116. #endif 
  117.  
  118.     if(register_android_media_FFMpegAVFormatContext(env) != JNI_OK) { 
  119.         __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVFormatContext"); 
  120.         goto end; 
  121.     } 
  122.  
  123.     if(register_android_media_FFMpegAVCodecContext(env) != JNI_OK) { 
  124.         __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVCodecContext"); 
  125.         goto end; 
  126.     } 
  127.  
  128.     if(register_android_media_FFMpegAVRational(env) != JNI_OK) { 
  129.         __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVRational"); 
  130.         goto end; 
  131.     } 
  132.      
  133.     if(register_android_media_FFMpegAVInputFormat(env) != JNI_OK) { 
  134.         __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVInputFormat"); 
  135.         goto end; 
  136.     } 
  137.      
  138.     if(register_android_media_FFMpegUtils(env) != JNI_OK) { 
  139.         __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegUtils"); 
  140.         goto end; 
  141.     } 
  142.  
  143.     if(register_android_media_FFMpegAVFrame(env) != JNI_OK) { 
  144.         __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegAVFrame"); 
  145.         goto end; 
  146.     } 
  147.  
  148. #ifdef BUILD_WITH_PLAYER 
  149.     if(register_android_media_FFMpegPlayerAndroid(env) != JNI_OK) {//跳到----》com_media_ffmpeg_FFMpegPlayer.cpp文件 
  150.         __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load android_media_FFMpegPlayerAndroid"); 
  151.         goto end; 
  152.     } 
  153. #endif 
  154.  
  155.     __android_log_print(ANDROID_LOG_INFO, TAG, "loaded"); 
  156.  
  157.     result = JNI_VERSION_1_4; 
  158.  
  159. end: 
  160.     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;
}





  1. //com_media_ffmpeg_FFMpegPlayer.cpp文件 
  2. /*
  3. *
  4. * 由于代码量较大,com_media_ffmpeg_FFMpegPlayer.cpp开始的一部分省略,只是贴出注册函数的相关部分。
  5. */ 
  6. static const char* const kClassPathName = "com/media/ffmpeg/FFMpegPlayer"
  7. /*
  8. * 由于gMethods[]是一个<名称,函数指针>对照表,在程序执行时,
  9. * 可多次调用registerNativeMethods()函数来更换本地函数的指针,
  10. * 从而达到弹性调用本地函数的目的。
  11. */ 
  12. static JNINativeMethod gMethods[] = { 
  13.     {"setDataSource",       "(Ljava/lang/String;)V",            (void *)com_media_ffmpeg_FFMpegPlayer_setDataSource}, 
  14.     {"_setVideoSurface",    "(Landroid/view/Surface;)V",        (void *)com_media_ffmpeg_FFMpegPlayer_setVideoSurface}, 
  15.     {"prepare",             "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_prepare}, 
  16.     {"_start",              "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_start}, 
  17.     {"_stop",               "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_stop}, 
  18.     {"getVideoWidth",       "()I",                              (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth}, 
  19.     {"getVideoHeight",      "()I",                              (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight}, 
  20.     {"seekTo",              "(I)V",                             (void *)com_media_ffmpeg_FFMpegPlayer_seekTo}, 
  21.     {"_pause",              "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_pause}, 
  22.     {"isPlaying",           "()Z",                              (void *)com_media_ffmpeg_FFMpegPlayer_isPlaying}, 
  23.     {"getCurrentPosition""()I",                              (void *)com_media_ffmpeg_FFMpegPlayer_getCurrentPosition}, 
  24.     {"getDuration",         "()I",                              (void *)com_media_ffmpeg_FFMpegPlayer_getDuration}, 
  25.     {"_release",            "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_release}, 
  26.     {"_reset",              "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_reset}, 
  27.     {"setAudioStreamType""(I)V",                             (void *)com_media_ffmpeg_FFMpegPlayer_setAudioStreamType}, 
  28.     {"native_init",         "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_native_init}, 
  29.     {"native_setup",        "(Ljava/lang/Object;)V",            (void *)com_media_ffmpeg_FFMpegPlayer_native_setup}, 
  30.     {"native_finalize",     "()V",                              (void *)com_media_ffmpeg_FFMpegPlayer_native_finalize}, 
  31.     {"native_suspend_resume", "(Z)I",                           (void *)com_media_ffmpeg_FFMpegPlayer_native_suspend_resume}, 
  32. }; 
  33.  
  34. int register_android_media_FFMpegPlayerAndroid(JNIEnv *env) { 
  35.     return jniRegisterNativeMethods(env, kClassPathName, gMethods, sizeof(gMethods) / sizeof(gMethods[0])); 
  36.     /*跳到OnLoad.cpp文件中的
  37.      * jint jniRegisterNativeMethods(JNIEnv* env,
  38.                              const char* className,
  39.                              const JNINativeMethod* gMethods,
  40.                              int numMethods)
  41.                             
  42.      */ 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值