NDK开发技巧:Java层使用long型变量保存C++对象指针

在 Android JNI 开发中,Java层使用一个long型变量来保存JNI层的C++对象指针是一个经典的常用技巧。在Android Framework层源码中也被广泛使用,这似乎成了一种默认的套路准则。

Framework中的相关源码示例详细可参考Bitmap.javaMessageQueue.java以及其对应JNI层实现:

  • http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp
  • http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/jni/android_os_MessageQueue.cpp

下面简单总结一下源码中是如何做的:

第一步:java层定义long型成员变量存储native层对应的指针:

public final class Bitmap implements Parcelable {
    private final long mNativePtr;
}

第二步:native层在初始化时,创建C++对象指针,通过 reinterpret_cast 转成long型设置给java层:

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    ...
    return reinterpret_cast<jlong>(nativeMessageQueue);
}
jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
        int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, int density) {
    ...
    BitmapWrapper* bitmapWrapper = new BitmapWrapper(bitmap);
    jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
            reinterpret_cast<jlong>(bitmapWrapper), bitmap->width(), bitmap->height(), density,
            isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets);
   ...
    return obj;
}

第三步:具体使用的时候,Java层调用JNI方法时将long型指针句柄传给native层,native层再次通过reinterpret_castlong型指针句柄强转成对应的C++对象指针进行方法调用:

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

我们看两眼Bitmap的源码:

NDK开发技巧:Java层使用long型变量保存C++对象指针_第1张图片
NDK开发技巧:Java层使用long型变量保存C++对象指针_第2张图片
NDK开发技巧:Java层使用long型变量保存C++对象指针_第3张图片
NDK开发技巧:Java层使用long型变量保存C++对象指针_第4张图片

注意:Bitmap的构造函数是由JNI层主动调用的,开发者没有办法调用,它的注释上也写着 “called from JNI”。

对于这个用法,本人深有体会,在Android开发的远古时代(估计很多人都没有经历过),那个时候手机还都是32位的,因此开发当中都是使用int类型来存储C对象指针的,随着技术飞速发展,到了后来手机芯片也提升到了64位的,对象指针变为使用64位来存储,就需要做32位到64位的适配工作,记得那个时候为了适配64位对象指针还特意满工程的到处搜索查找int型的指针,然后将其替换为long型的。

了解了这个套路,如果我们在开发中遇到要将C层对象保存到Java层的问题,就可以依葫芦画瓢自己动手写一个类似的来使用了。下面是在另一篇文章中看到的代码例子,可以直接拿来用:

新建一个java类:

public class Teacher {

   public long mNativePtr = 0;

   /**
    * 初始化一个学生,并将指针地址保存在mNativePtr变量中
    */
   public native void initStudent();

   /**
    * 根据学生的指针地址发起个性化教育
    * @param studentPtr
    */
   public native void educationStudent(long studentPtr);

   /**
    * 释放学生对象,很关键的一步,否则就是可怕的内存泄漏
    * @param studentPtr
    */
   public native void releaseStudent(long studentPtr);
}

新建C++对象类:

Student.h

#ifndef GROWING_STUDENT_H
#define GROWING_STUDENT_H

class Student {

public:
    char *name = nullptr;
    int age = 0;

    Student();
    ~Student();
};
#endif //GROWING_STUDENT_H

Student.cpp

#include "Student.h"

Student::Student() {

}

Student::~Student() {

}

native-lib.cpp

extern "C"
JNIEXPORT void JNICALL
Java_com_flyer_bspatchupdate_jni_Teacher_initStudent(JNIEnv *env, jobject thiz) {
    Student *student = new Student;
    student->age = 18;
    student->name = "我是一名学生,我叫小明";
    // JNI 设置Java变量
    jclass jc = env->GetObjectClass(thiz);
    jfieldID jf = env->GetFieldID(jc, "mNativePtr", "J");
    env->SetLongField(thiz, jf, (jlong)student);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_flyer_bspatchupdate_jni_Teacher_educationStudent(JNIEnv *env, jobject thiz, jlong student_ptr) {
    if (0 != student_ptr) {
        Student *student = (Student*)student_ptr;
        student->age = student->age + 1;
        student->name;
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_com_flyer_bspatchupdate_jni_Teacher_releaseStudent(JNIEnv *env, jobject thiz, jlong student_ptr) {
    if (0 != student_ptr) {
        Student *student = (Student*)student_ptr;
        delete student;
    }
}

测试 Activity 类:

public class JNIActivity extends AppCompatActivity {

    private Teacher teacher;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_j_n_i);

        teacher = new Teacher();
        teacher.initStudent();
        teacher.educationStudent(teacher.mNativePtr);
    }

    @Override
    protected void onDestroy() {
        if(null != teacher){
            teacher.releaseStudent(teacher.mNativePtr);
            teacher = null;
        }
        super.onDestroy();
    }
}

你可能感兴趣的:(JNI/NDK,android,JNI,NDK)