Android_JNI浅析(1)

        JNI是Java Native Interface的缩写,JNI是一种机制,有了它就可以在java程序中调用其他native代码,或者使native代码调用java层的代码。也就是说,有了JNI我们可以使Android项目中,java层与native层各自发挥所长并相互配合。如下图所示,JNI在Android中所处的位置。

  

                                                          Android_JNI浅析(1)_第1张图片                

                                                                好吧谁不知道JNI应该在JAVA和Native的中间呢?


        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_setup(Object mediaplayer_this);  
        
      ...  
  }


可以看到上面的代码中,在MediaPlayer类中有一段static块包围起来的代码,其中System.loadLibrary("media_jni")就是告诉VM去加载libmedia_jni.so这个动态库,那么这个动态库什么时候被加载呢?因为static语句块的原因,所以在MediaPlayer第一次实例化的时候就会被加载了。这段代码中,我们还看到了一个函数native_init(),该函数被申明为native型,就是告诉VM该函数由native层来实现。


2,如何做到java层到native层的映射。

        事实上我想表达的意思是,如何完成java层的代码到native层代码的映射,例如上面的代码中有一个native函数native_init(),那么如何使这个函数映射到一个由C/C++(或者其他语言)实现的具体函数呢?PS:本菜鸟,表达能力欠缺,不知道大家有没有看明白。

             当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 = -1;
    //判断一下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) < 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;
    }

    if (register_android_media_AmrInputStream(env) < 0) {
        LOGE("ERROR: AmrInputStream native registration failed\n");
        goto bail;
    }

    if (register_android_media_ResampleInputStream(env) < 0) {
        LOGE("ERROR: ResampleInputStream native registration failed\n");
        goto bail;
    }

    if (register_android_media_MediaProfiles(env) < 0) {
        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函数的映射工作。下面我们来具体的看看上面这个函数中各个参数的意义。

a,JNIEnv* env,关于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类型的指针gMethodsgMethods指向一个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},
};
d, int numMethods,不解释。


这样一来就完成了java native函数到到JNI层函数的映射。当然具体功能实现还是由JNI层函数来调用C/C++相应的功能函数


-------------------------------------------至此只讲解了JNI大致流程,下次会具体讲解一下JNI的细节,本菜鸟能力有限,blog中若有错误的地方还请给为指正,谢谢


你可能感兴趣的:(java,android,function,jni,interface,Pointers)