JNI 编程上手指南之 JNI 调用性能优化-灵析社区

IT大鲨鱼

为什么要做性能优化


  • Java 程序中,调用一个 Native 方法相比调用一个 Java 方法要耗时很多,我们应该减少 JNI 方法的调用,同时一次 JNI 调用尽量完成更多的事情。对于过于耗时的 JNI 调用,应该放到后台线程调用。
  • Native 程序要访问 Java 对象的字段或调用它们的方法时,本机代码必须调用 FindClass()、GetFieldID()、GetStaticFieldID、GetMethodID() 和 GetStaticMethodID() 等方法,返回的 ID 不会在 JVM 进程的生存期内发生变化。但是,获取字段或方法的调用有时会需要在 JVM 中完成大量工作,因为字段和方法可能是从超类中继承而来的,这会让 JVM 向上遍历类层次结构来找到它们。为了提高性能,我们可以把这些 ID 缓存起来,用内存换性能。


使用时缓存


Java 层:

public class TestJavaClass {

    //......
    private void myMethod() {
        Log.i("JNI", "this is java myMethod");
    }
    //......
}

public native void cacheTest();


Natice 层

extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_cacheTest(JNIEnv *env, jobject thiz) {

    jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass");
    if (clazz == NULL) {
        return;
    }

    static jmethodID java_construct_method_id = NULL;
    static jmethodID java_method_id = NULL;

    //实现缓存的目的,下次调用不用再获取 methodid 了
    if (java_construct_method_id == NULL) {
        //构造函数 id
        java_construct_method_id = env->GetMethodID(clazz, "<init>", "()V");
        if (java_construct_method_id == NULL) {
            return;
        }
    }

    //调用构造函数,创建一个对象
    jobject object_test = env->NewObject(clazz, java_construct_method_id);
    if (object_test == NULL) {
        return;
    }
    //相同的手法,缓存 methodid
    if (java_method_id == NULL) {
        java_method_id = env->GetMethodID(clazz, "myMethod", "()V");
        if (java_method_id == NULL) {
            return;
        }
    }

    //调用 myMethod 方法
    env->CallVoidMethod(object_test, java_method_id);

    env->DeleteLocalRef(clazz);
    env->DeleteLocalRef(object_test);
}


手法还是比较简单的,主要是通过一个全局变量保存 methodid,这样只有第一次调用 native 函数时,才会调用 GetMethodID 去获取,后面的调用都使用缓存起来的值了。这样就避免了不必要的调用,提升了性能。


静态初始化缓存


Java 层:



static {
    System.loadLibrary("myjnidemo");
    initIDs();
}

public static native void initIDs();

C++ 层:

//定义用于缓存的全局变量
static jmethodID java_construct_method_id2 = NULL;
static jmethodID java_method_id2 = NULL;

extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_initIDs(JNIEnv *env, jclass clazz) {

    jclass clazz2 = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass");

    if (clazz == NULL) {
        return;
    }

    //实现缓存的目的,下次调用不用再获取 methodid 了
    if (java_construct_method_id2 == NULL) {
        //构造函数 id
        java_construct_method_id2 = env->GetMethodID(clazz2, "<init>", "()V");
        if (java_construct_method_id2 == NULL) {
            return;
        }
    }

    if (java_method_id2 == NULL) {
        java_method_id2 = env->GetMethodID(clazz2, "myMethod", "()V");
        if (java_method_id2 == NULL) {
            return;
        }
    }
}

手法和使用时缓存是一样的,只是缓存的时机变了。如果是动态注册的 JNI 还可以在 Onload 函数中来执行缓存操作。


阅读量:2029

点赞量:0

收藏量:0