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

原创 2012年04月29日 18:12:59

/********************************************************************************************
 * author:conowen@大钟                                                                                                                          
 * E-mail:conowen@hotmail.com                                                                                                             
 * http://blog.csdn.net/conowen                                                                                                              
 * 注:本文为原创,仅作为学习交流使用,转载请标明作者及出处。      

 ********************************************************************************************/


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

 result = JNI_VERSION_1_4;

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

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为例详细说一下:

//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;
}





//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)
                             
	 */
}



版权声明:本文为博主原创文章,未经博主允许不得转载。

Android开发学习之路--NDK、JNI之初体验

好久没有更新博客了,最近一直在看一个仿微信项目,然后看源码并自己实现下,相信经过这个项目可以让自己了解一个项目中的代码以及种种需要注意的事项。不知不觉中博客已经快要40w访问量,而且排名也即将突破30...
  • eastmoon502136
  • eastmoon502136
  • 2016年02月28日 17:46
  • 13999

Android的NDK开发(2)————利用Android NDK编写一个简单的HelloWorld

/********************************************************************************************  * aut...
  • conowen
  • conowen
  • 2012年04月29日 18:15
  • 25154

Android NDK开发(一) 入门

开始之前 最近学习了一下NDK的开发, 就来分享一下. 对一个新鲜事物, 我们先解决的无非就是三件事情: 是什么?为什么?怎么做?. NDK简介 (英语:native develop...
  • u013144863
  • u013144863
  • 2016年11月06日 18:02
  • 4464

你不知道的Android NDK开发

上篇文章讲解了JNI的有关知识,并且如何在程序中使用“.so”库。那么到底如何生成一个“.so”库?请看下文。 一、概述。       “.so”库是使用C/C++编写生成的,在Android 平台上...
  • zxw136511485
  • zxw136511485
  • 2016年12月02日 14:29
  • 1780

Android NDK开发(二)——从Hello World学起

上篇文章讲述了Android NDK开发的一些基本概念,以及NDK的环境搭建,相信看过的朋友NDK开发环境搭建应该是没有问题了,还没有搭建或者不知道怎么搭建的朋友请点击这里。那么这篇文章,我们跟刚学J...
  • lee_tianya
  • lee_tianya
  • 2014年12月26日 09:32
  • 4185

Android:JNI 与 NDK到底是什么?(含实例教学)

前言 在Android开发中,使用 NDK开发的需求正逐渐增大 但很多人却搞不懂 JNI 与 NDK 到底是怎么回事 今天,我将先介绍JNI 与 NDK & 之间的区别,手把手进行 NDK的使用教学...
  • carson_ho
  • carson_ho
  • 2017年06月14日 17:03
  • 13551

NDK-JNI实战教程(一) 在Android Studio运行第一个NDK程序

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN。因为CSDN也支持MarkDown语法了,牛逼啊!NDK开发,其实是为了项目需要调用底层的一些C/C++的一些东西;另...
  • yanbober
  • yanbober
  • 2015年04月27日 13:23
  • 87259

Android NDK开发,没有你想象的那么难

为什么要用NDK:我们都知道,java是半解释型语言,很容易被反汇编后拿到源代码文件,在开发一些重要协议时,我们为了安全起见,使用C语言来编写这些重要的部分,来增大系统的安全性。还有,在一些接近硬件环...
  • mzm2438975656
  • mzm2438975656
  • 2015年12月26日 14:41
  • 374

Android中的第一个NDK的例子

前几天研究了JNI技术后,想在Android上试一试研究结果,查阅了很多资料后,总结如下步骤: 首先来看一下什么是NDK:      NDK 提供了一系列的工具,帮助开发者快速开发C(或C++)的动态...
  • jiangwei0910410003
  • jiangwei0910410003
  • 2013年12月31日 11:30
  • 45931

Android NDK开发入门实例

Android NDK开发入门实例         写这个,目的就是记录一下我自己的NDK是怎么入门的。便于以后查看,而不会忘了又用搜索引擎一顿乱搜。然后希望能够帮助刚学的人入门。先转一段别人说的话...
  • redoffice
  • redoffice
  • 2011年08月02日 17:53
  • 63223
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android的NDK开发(1)————Android JNI简介与调用流程
举报原因:
原因补充:

(最多只允许输入30个字)