美文网首页
Android JNI

Android JNI

作者: Itachi001 | 来源:发表于2021-09-17 11:58 被阅读0次

CMakeLists.txt

#动态库生成位置:app/build/intermediates/cxx/
add_library( # Sets the name of the library.
        jni
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        native-lib.cpp)

kotlin

private fun callTest(){
    val intArray = IntArray(3)
    for (index in intArray.indices) {
        intArray[index] = index
    }
    val bean = Bean()
    binding.sampleText.text = test(intArray, arrayOf("Hello ", "from ", "Kotlin"), bean)
    Log.i("JNI", intArray.contentToString())
    Log.i("JNI", bean.toString())
    dynamicNative()
    Log.i("JNI", dynamicNative(1))
    testPthread()
}

private external fun test(intArray: IntArray, stringArray: Array<String>, bean: Bean): String
private external fun dynamicNative()
private external fun dynamicNative(i: Int): String
private external fun testPthread()

private fun updateUI() {
    if (Looper.myLooper() == Looper.getMainLooper()) {
        showToast("更新UI")
    } else {
        Handler(mainLooper).post { showToast("更新UI") }
    }
}

private fun showToast(s: String) {
    Toast.makeText(this, s, Toast.LENGTH_SHORT).show()
}

companion object {
    // Used to load the 'jni' library on application startup.
    init {
        System.loadLibrary("jni")
    }
}

C/C++中获取java/kotlin的数组,native-lib.cpp

#include <jni.h>
#include <string>
#include <android/log.h>

using namespace std;
// __VA_ARGS__ 代表... 表示可变参数
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "JNI", __VA_ARGS__)
//c++中需要以c的方式编译
//JNIEXPORT: 表示将该函数导出在外部(动态库外包)可以调用。类Unix系统中这个宏可以省略不加。
//JNIEnv: 由Jvm传入与线程相关的变量。定义了JNI系统操作、java交互等方法。
//jobject:表示当前调用对象,即 this , 如果是静态的native方法,则获得jclass
//JNICALL: 一种函数调用约定。类Unix系统中这个宏可以省略不加。
extern "C"
JNIEXPORT jstring JNICALL
Java_cn_yeby_jni_MainActivity_test(JNIEnv *env, jobject thiz, jintArray int_array, jobjectArray string_array, jobject bean) {
    // 获取并修改Int数组
    // 参数2 isCopy:提供一个boolean(int)指针,用于接收jvm传给我们的字符串是否是拷贝的。
    // true 表示拷贝,false 表示使用java数组(地址)。
    // 通常,我们不关心这个,一般传个nullptr就可以
    jint *int_point = env->GetIntArrayElements(int_array, nullptr);
    jsize int_length = env->GetArrayLength(int_array);
    for (int i = 0; i < int_length; ++i) {
        LOGI("GetIntArrayElements:%d", *(int_point + i));
        *(int_point + i) = 689;
    }
    // 参数3 mode
    // 0:刷新java数组并释放c/c++数组;
    // 1:JNI_COMMIT,即只刷新java数组;
    // 2:JNI_ABORT,只释放c/c++数组
    env->ReleaseIntArrayElements(int_array, int_point, 0);

    // 获取String数组
    jsize string_length = env->GetArrayLength(string_array);
    string return_str;
    for (int i = 0; i < string_length; ++i) {
        auto str = reinterpret_cast<jstring>(env->GetObjectArrayElement(string_array, i));
        const char *s = env->GetStringUTFChars(str, nullptr);
        LOGI("GetStringUTFChars:%s", s);
        return_str += s;
        env->ReleaseStringUTFChars(str, s);
    }

    // 获取对象并操作对象
    jclass clazz = env->GetObjectClass(bean);
    // 调用对象中的成员方法
    jmethodID getId = env->GetMethodID(clazz, "getId", "()I");
    jmethodID getName = env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");
    jint id = env->CallIntMethod(bean, getId);
    auto name = reinterpret_cast<jstring>(env->CallObjectMethod(bean, getName));
    const char *cName = env->GetStringUTFChars(name, nullptr);
    LOGI("id:%d,name:%s", id, cName);
    // Chars、Elements、Critical都需要Release
    env->ReleaseStringUTFChars(name, cName);

    jmethodID setId = env->GetMethodID(clazz, "setId", "(I)V");
    jmethodID setName = env->GetMethodID(clazz, "setName", "(Ljava/lang/String;)V");
    env->CallVoidMethod(bean, setId, 1);
    jstring nameStr = env->NewStringUTF("No.1");
    env->CallVoidMethod(bean, setName, nameStr);
    env->DeleteLocalRef(nameStr);
    // 修改对象中的属性
    jfieldID age = env->GetFieldID(clazz, "age", "I");
    env->SetIntField(bean, age, 89);
    env->DeleteLocalRef(clazz);
    // 创建Java/Kotlin对象
    jclass staticClazz = env->FindClass("cn/yeby/jni/Bean");
    jmethodID constuct = env->GetMethodID(staticClazz, "<init>", "()V");
    jobject bean1 = env->NewObject(staticClazz, constuct);
    // 调用对象中的静态方法
    jmethodID dodo = env->GetStaticMethodID(staticClazz, "dodo", "(Lcn/yeby/jni/Bean;)V");
    env->CallStaticVoidMethod(staticClazz, dodo, bean1);
    // New和Find出来的都需要Delete
    env->DeleteLocalRef(staticClazz);
    env->DeleteLocalRef(bean1);

    return env->NewStringUTF(return_str.c_str());
}

JNI引用

在 JNI 规范中定义了三种引用:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。

局部引用

大多数JNI函数会创建局部引用。NewObject/FindClass/NewStringUTF 等等都是局部引用。
局部引用只有在创建它的本地方法返回前有效,本地方法返回后,局部引用会被自动释放。
因此无法跨线程、跨方法使用。
不能在本地方法中把局部引用存储在静态变量中缓存起来供下一次调用时使用(悬空指针)

全局引用

全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效 。由 NewGlobalRef 函数创建。

    static jstring globalStr;
    if(globalStr == nullptr){
        jstring str = env->NewStringUTF("C++字符串");
        //删除全局引用调用  DeleteGlobalRef
        globalStr = static_cast<jstring>(env->NewGlobalRef(str));
        //可以释放,因为有了一个全局引用使用str,局部str也不会使用了
        env->DeleteLocalRef(str);
    }

弱引用(弱全局引用)

与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象 。
在对Class进行弱引用是非常合适(FindClass),因为Class一般直到程序进程结束才会卸载。
在使用弱引用时,必须先检查缓存过的弱引用是指向活动的对象,还是指向一个已经被GC的对象

static jclass globalClazz = nullptr;
    //对于弱引用 如果引用的对象被回收返回 true,否则为false
    //对于局部和全局引用则判断是否引用java的null对象
    jboolean isEqual = env->IsSameObject(globalClazz, nullptr);
    if (globalClazz == nullptr || isEqual) {
        jclass clazz = env->GetObjectClass(instance);
        //删除使用 DeleteWeakGlobalRef
        globalClazz = static_cast<jclass>(env->NewWeakGlobalRef(clazz));
        env->DeleteLocalRef(clazz);
    }

JNI_OnLoad

调用System.loadLibrary()函数时, 内部就会去查找so中的 JNI_OnLoad 函数,如果存在此函数则调用。
JNI_OnLoad会告诉 VM 此 native 组件使用的 JNI 版本。
对应了Java版本,android中只支持JNI_VERSION_1_2 、JNI_VERSION_1_4、JNI_VERSION_1_6,在JDK1.8有 JNI_VERSION_1_8。

jint JNI_OnLoad(JavaVM* vm, void* reserved){
    // 2、4、6都可以
    return JNI_VERSION_1_4;
}

动态注册

在此之前我们一直在jni中使用的 Java_PACKAGENAME_CLASSNAME_METHODNAME 来进行与java方法的匹配,这种方式我们称之为静态注册。
而动态注册则意味着方法名可以不用这么长了,在android aosp源码中就大量的使用了动态注册的形式。

//C++:
void dynamicNative1(JNIEnv *env, jobject jobj) {
    LOGI("dynamicNative1 动态注册");
}

jstring dynamicNative2(JNIEnv *env, jobject jobj, jint i) {
    return env->NewStringUTF("我是动态注册的dynamicNative2方法");
}

//需要动态注册的方法数组
static const JNINativeMethod mMethods[] = {
        {"dynamicNative", "()V",                   (void *) dynamicNative1},
        {"dynamicNative", "(I)Ljava/lang/String;", (jstring *) dynamicNative2}

};
//需要动态注册native方法的类名
static const char *mClassName = "cn/yeby/jni/MainActivity";

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = nullptr;
    //获得 JniEnv
    int r = vm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (r != JNI_OK) {
        return -1;
    }
    jclass mainActivityCls = env->FindClass(mClassName);
    // 注册 如果小于0则注册失败
    r = env->RegisterNatives(mainActivityCls, mMethods, sizeof(mMethods) / sizeof(JNINativeMethod));
    if (r != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_6;
}

native线程调用Java

native调用java需要使用JNIEnv这个结构体,而JNIEnv是由Jvm传入与线程相关的变量。
但是可以通过JavaVM的AttachCurrentThread方法来获取到当前线程中的JNIEnv指针,最后记得DetachCurrentThread。

JavaVM *vm = nullptr;
jobject instance = nullptr;
jint JNI_OnLoad(JavaVM *_vm, __unused void *reserved) {
    vm = _vm;
    return JNI_VERSION_1_6;
}

void *task(__unused void *args) {
    JNIEnv *env;
    vm->AttachCurrentThread(&env, nullptr);
    jclass mainActivityCls = env->GetObjectClass(instance);
    jmethodID updateUI = env->GetMethodID(mainActivityCls, "updateUI", "()V");
    env->CallVoidMethod(instance, updateUI);
    vm->DetachCurrentThread();
    env->DeleteGlobalRef(instance);
    return nullptr;
}

extern "C"
JNIEXPORT void JNICALL
Java_cn_yeby_jni_MainActivity_testPthread(JNIEnv *env, jobject thiz) {
    instance = env->NewGlobalRef(thiz);
    pthread_t tid;
    pthread_create(&tid, nullptr, task, nullptr);
}

相关文章

网友评论

      本文标题:Android JNI

      本文链接:https://www.haomeiwen.com/subject/spskgltx.html