现在的位置: 首页 > 移动开发 > 正文

Android 开发之JNI

2019年09月04日 移动开发 ⁄ 共 7350字 ⁄ 字号 评论关闭

       我们知道Android中的apk开发一般都是用Java语言编写的,但是用到的系统服务和一些操作系统相关的都是用C/C++写的。而Java和C/C++之间的互相访问我们一般都是通过JNI来打通通道的。Java和C/C++之间的调用主要分两种:Java域访问C域,这也是最常见的。和C域访问Java域,例如Android启动过程中Zygote的启动。

 Java域调用C域的函数

      1)在Java中某个类中,在要调用的jni方法前面加上native修饰符

      2)在Java中使用该方法前,首先用 System.loadLibrary()装载库。

      3)在JNI层相应的.cpp文件中定义JNI_OnLoad(JavaVM* vm, void* reserved)函数,通过AndroidRuntime::registerNativeMethods()将我们的函数注册起来,和Java层调用的JNI方法联系到一起。这样在Java中调用方法时能自动找到JNI的函数。

如:Android的MediaPlayer在Java中的代码为:

      frameworks/base/media/java/android/media/MediaPlayer.java文件:

    class MediaPlayer {

          ...............

       static {

          System.loadLibrary("media_jni");

          native_init();    }

    private static native final void native_init();

}

 

这里在使用MediaPlayer这个类之前Dalvik虚拟机会去装载libmedia_jni.so库,调用Jni层的JNI_OnLoad函数,这个函数负责将native函数等级到VM里面。

 

      frameworks/base/media/jni/android_media_MediaPlayer.cpp文件:

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
   JNIEnv* env = NULL;
    vm->GetEnv((void**)&env, JNI_VERSION_1_4);
    register_android_media_MediaPlayer(env);
    return JNI_VERSION_1_4;
}

static int register_android_media_MediaPlayer(JNIEnv* env)
{
  return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods));
}

其中gMethods就是一个全局数组,里面包含JAVA函数和JNI函数是怎么一一对应的。

typedef struct {

const char* name;       // Java中函数的名字

const char* signature; // 对应的签名信息,描述函数的参数和返回值

void* fnPtr;           // 函数指针,指向JNI函数

}JNINativeMethod;

static JNINativeMethod gMethods[] = {
    {"setDataSource",       "(Ljava/lang/String;)V",            (void *)android_media_MediaPlayer_setDataSource},
    ......
};

这里表面:在Java中我们调用的void setDataSource(String path) 对应着JNI中的void android_media_MediaPlayer_setDataSource函数。这里我们看看函数原型为:

void android_media_MediaPlayer_setDataSource(JNIEnv *env, jobject thiz, jstring path)函数

 我们发现:在Jni中的实现函数签名中返回值和Java中声明函数函数的原型是一致的,如在Java里面的int类型对应着JNI层的jint,Java里面的String类型对应着JNI里面的jstring等。

Java类型      JNI类型        本地类型          字节(bit)  
 boolean      jboolean       unsigned char     8, unsigned  
 byte         jbyte          signed char      8  
 char         jchar          unsigned short    16, unsigned  
 short        jshort         short            16  
 int          jint           long             32  
 long         jlong          __int64          64  
 float        jfloat         float             32  
 double       jdouble        double            64   

 

但是参数列表里却多了两个。

JNIEnv *env: 是虚一个与线程相关的代表虚拟机运行的环境的结构体,可以用于调用Java函数,操作jobject对象等。通过调用JavaVM中的AttachCurrentThread函数,可以得到这个线程的JNIEnv结构体。

 

jobject thiz:
调用改函数的对应Java中的对象,这样我们在JNI层就能区分是哪个Java类调用的这个函数

 

C域调用Java域的变量

在JNI中用jfieldID 和 jMethodID可以表示Java类的成员变量和成员函数,用JNIEnv提供的函数可以获取Java中的成员变量和成员函数。

jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)

jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);

参数:jclass表示对应的Java类,name表示成员函数或成员变量名,sIg为这个成员函数或成员变量的签名信息。

既然我们能够获取到Java域的成员变量和成员函数,那我们肯定就能访问它。

例如Android中的Camera:

在文件frameworks/base/core/jni/AndroidRuntime.cpp:

void AndroidRuntime::start(const char* className, const char* options)

{

 .............

 JNIEnv *env;

 startVm(&mJavaVM, &env); 启动虚拟机

startReg(env); 开始注册JNI函数

}

frameworks/base/core/jni/android_hardware_Camera.cpp:

int register_android_hardware_Camera(JNIEnv *env)
{
    field fields_to_find[] = {
        { "android/hardware/Camera", "mNativeContext",   "I", &fields.context },
        { "android/view/Surface",    ANDROID_VIEW_SURFACE_JNI_ID, "I", &fields.surface },
        { "android/graphics/SurfaceTexture",
          ANDROID_GRAPHICS_SURFACETEXTURE_JNI_ID, "I", &fields.surfaceTexture },
        { "android/hardware/Camera$CameraInfo", "facing",   "I", &fields.facing },
        { "android/hardware/Camera$CameraInfo", "orientation",   "I", &fields.orientation },
        { "android/hardware/Camera$Face", "rect", "Landroid/graphics/Rect;", &fields.face_rect },
        { "android/hardware/Camera$Face", "score", "I", &fields.face_score },
        { "android/graphics/Rect", "left", "I", &fields.rect_left },
        { "android/graphics/Rect", "top", "I", &fields.rect_top },
        { "android/graphics/Rect", "right", "I", &fields.rect_right },
        { "android/graphics/Rect", "bottom", "I", &fields.rect_bottom },
    };

    if (find_fields(env, fields_to_find, NELEM(fields_to_find)) < 0)
        return -1;

    jclass clazz = env->FindClass("android/hardware/Camera");
    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        LOGE("Can't find android/hardware/Camera.postEventFromNative");
        return -1;
    }

    // Register native functions
    return AndroidRuntime::registerNativeMethods(env, "android/hardware/Camera",
                                              camMethods, NELEM(camMethods));
}

其中fields_to_find是一个field类型的结构体:

struct field{

   const char* class_name;        // 类的名字

   const char* field_name;        // Java中成员变量的名字

   const char* field_type;          // Java中成员变量的类型

    jfieldID  *jfield;                     // 对应的JNI域中变量

};
这样我们将Java中对应类的各个变量ID保存到JNI结构体中:

struct fields_t {
    jfieldID    context;
    jfieldID    surface;
    jfieldID    surfaceTexture;
    jfieldID    facing;
    jfieldID    orientation;
    jfieldID    face_rect;
    jfieldID    face_score;
    jfieldID    rect_left;
    jfieldID    rect_top;
    jfieldID    rect_right;
    jfieldID    rect_bottom;
    jmethodID   post_event;
    jmethodID   rect_constructor;
    jmethodID   face_constructor;
};

所以现在我们可以在JNI域直接修改设置Java域的这些变量了:如当我们Java层需要获得CameraInfo信息时,我们会调用android.hardware.Camera::getCameraInfo()函数,会调用到JNI的android_hardware_Camera_getCameraInfo();

// info_obj是Java中传进来的CameraInfo对象,标志着是Java中的哪个对象调用的当前函数

static void android_hardware_Camera_getCameraInfo(JNIEnv* env, jobject thiz, jint cameraId, jobject info_obj)

{

      CameraInfo cameraInfo;

       Camera::getCameraInfo(cameraId, &cameraInfo);

       ....

       env->SetIntField(info_obj, fields.facing, cameraInfo.facing);

       env->SetIntField(info_obj, fields.orientation, cameraInfo.orientation);

}

通过CPP获取到cameraInfo信息,然后调用env->SetIntField()函数将cameraInfo.facing信息设置到info_obj.facing中去。

 

总结:

      1) 通过JNIEnv::FindClass()找到Java域中类对应的jclass

              jclass clazz = env->FindClass("android/hardware/Camera$CameraInfo"); 其中$表面是嵌套类

      2)通过JNIEnv::GetFieldID()找到类中属性变量的jfieldID

             jfieldID fieldId = env->GetFIeldID(clazz, "facing", "I");

      3)通过JNIEnv::GetTYPEField()/SetTYPEField() 获取/设置Java对象的属性

            env->SetIntField(info_obj, fieldId, camerainfo.facing);

 

 C域调用Java域的函数

      在Android中C域访问Java域的方法和访问属性相类似。如android.hardware.Camera中的postEventFromNative是如何被JNI调用的。

我们注意到在前面:register_android_hardware_Camera(JNIEnv *env)中,在我们正式注册native函数之前我们已经将Java域中的方法postEventFromNative的jmethodID获取了。

1)获得methodID

jclass clazz = env->FindClass("android/hardware/Camera");

fields.post_event = env->GetStaticMethod(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V");

这样Java域中android.hardware.Camera类的postEventFromNative函数的methodID就被保存下来了。

2)调用方法

 JNIEnv *env = AndroidRuntime::getJNIEnv();

 env->CallStaticVoidMethod(mCameraJClass, fields.post_event, mCameraJObjectWeak, msgType, ext1, ext2, NULL);

 

 C域生成的对象的保存与使用  

       在C域中某次被调用生成的对象,在其他函数调用时是不可见的,虽然可以设置为全局变量但不是最好的解决方式。在Android中通常是在Java域中定义一个int型变量,在c域生成对象的地方,与这个Java域的变量关联。然后在别的使用到的地方,再从这个变量中获取。

如JNICameraContext:

 

  在正式注册native函数之前我们已经将Java域中android.hardware.Camera类的变量mNativeContext保存到本地了。

jclass clazz = env->FindClass("android/hardware/Camera");

jfieldID field = env->GetFieldID(clazz, "mNativeContext", "I");

 

 在生成cpp对象时,通过JNIEnv::SetIntField()设置为Java对象的属性

static void android_hardware_Camera_native_setup(JNIEnv* env, jobject thiz, jobject weak_this, jint cameraId)

{

       ......

      sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);

      .......

      env->SetIntField(thiz, fields.context, (int)context.get());

}

 

当我们要使用时,通过JNIEnv::GetIntField()获取Java对象的属性,并转化为JNICameraContext类型:

JNICameraContext* context = reinterpret_cast<JNICameraContext*>(env->GetIntField(thiz, fields.context));

 

总结:

 1) 通过JNIEnv::FindClass()找到对应的jclass

 2) 通过JNIEnv::GetFieldID()找到类中属性的jfieldID

 3)生成cpp对象时,通过JNIEnv::SetIntField()设置为Java对象的属性

 4)使用的时候,通过JNIEnv::GetIntField()获取Java对象的属性,再转化为真实对象类型

 

声明:本文是在网上另外一篇文章修改而来,加入了自己的一些理解。原文地址:http://blog.csdn.net/thl789/article/details/7212822

 

 

 

 

抱歉!评论已关闭.