JNI-NDK(JNI函数动态注册、JNI线程)

1、静态注册与动态注册介绍

先说静态注册,此方式为传统做法,JNI函数注册比较方便。在运行期调用JNI函数的时候注册。如下:

    /**
     * 静态注册
     */
    native void staticRegist();

/**
 * 此种方式为静态注册
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_hvm_vender_jni_103_MainActivity_staticRegist(JNIEnv *env, jobject thiz) {
    LOGD("native 静态注册")

}

因为静态注册为调用JNI函数的时候才注册,因此性能会稍微比动态注册低,但是对于日常开发影响不大。
下面说动态注册,在安卓系统源码中,JNI源码基本都是使用动态注册,因为对性能要求比较高,例如:android_os_Parcel.cpp的源码。


企业微信截图_16617509172252.png

2、动态注册实现

动态注册实现的思路是在JNI初始化的函数JNI_OnLoad中进行注册。

// 日志输出
#include 

#define TAG "Main_Native"
// __VA_ARGS__ 代表 ...的可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,  __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG,  __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);

#include 
#include 
#include  // 在AS上 pthread不需要额外配置,默认就有

//以下三个函数为动态注册的JNI函数
void dynamicNativeMethod01() {
    LOGD("dynamicNativeMethod01")
}

void dynamicNativeMethod02(JNIEnv *jniEnv, jobject thiz, jint number, jstring str) {
    const char *content = jniEnv->GetStringUTFChars(str, nullptr);
    LOGD("dynamicNativeMethod02 number:%d,str:%s", number, content);
}

int dynamicNativeMethod03(JNIEnv *jniEnv, jobject thiz, jint number, jstring str) {
    const char *content = jniEnv->GetStringUTFChars(str, nullptr);
    LOGD("dynamicNativeMethod03 number:%d,str:%s", number, content);
    return 900;
}
JavaVM *javaVm = nullptr;
const char *className = "com/hvm/vender/jni_03/MainActivity";
//JNINativeMethod数组,注册多个函数
const JNINativeMethod jniNativeMethods[] = {
        {"dynamicRegist1", "()V",                    (void *) (dynamicNativeMethod01)},
        {"dynamicRegist2", "(ILjava/lang/String;)V", (void *) (dynamicNativeMethod02)},
        {"dynamicRegist3", "(ILjava/lang/String;)I", (int *) (dynamicNativeMethod03)}
};

/**
 * JNI_OnLoad 函数,类似JAVA中的构造函数
 * System.loadLibrary的时候会调用该函数,可以进行做初始化操作
 */
extern "C"
JNIEXPORT int JNICALL
JNI_OnLoad(JavaVM *javaVm, void *) {
    ::javaVm = javaVm;
    JNIEnv *jniEnv = nullptr;
    //获取jniEnv
    int result = javaVm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6);
    if (result != JNI_OK) {
        LOGD("jniEnv获取失败")
        return -1;
    }
    //获取jclass
    jclass j_class = jniEnv->FindClass(className);
    //动态注册JNI函数,参数:jclass、函数指针数组(多个函数)、函数数量
    jniEnv->RegisterNatives(j_class, jniNativeMethods,
                            (sizeof(jniNativeMethods)) / (sizeof(JNINativeMethod)));
    LOGE("动态 注册成功");
    return JNI_VERSION_1_6;

}
/**
 * 此种方式为静态注册
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_hvm_vender_jni_103_MainActivity_staticRegist(JNIEnv *env, jobject thiz) {
    LOGD("native 静态注册")

}
package com.hvm.vender.jni_03;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Parcel;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.hvm.vender.jni_03.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName();

    // Used to load the 'jni_03' library on application startup.
    static {
        System.loadLibrary("jni_03");

    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        initListener();


    }

    private void initListener() {
        binding.btnStaticRegist.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //静态注册
                staticRegist();
            }
        });
        binding.btnRegist.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //动态注册三个JNI函数
                dynamicRegist1();
                dynamicRegist2(200, "daxiaa2");
                int result = dynamicRegist3(300, "daxiaa3");
                Log.d(TAG, "dynamicRegist3: "+result);

            }
        });
    }

    /**
     * 静态注册
     */
    native void staticRegist();

    native void dynamicRegist1();

    native void dynamicRegist2(int number, String str);

    native int dynamicRegist3(int number, String str);


}

3、JNI线程

Java代码

    binding.btnThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                threadTest();
            }
        });

Native代码

JavaVM *javaVm = nullptr;
const char *className = "com/hvm/vender/jni_03/MainActivity";
/**
 * JNI_OnLoad 函数,类似JAVA中的构造函数
 * System.loadLibrary的时候会调用该函数,可以进行做初始化操作
 */
extern "C"
JNIEXPORT int JNICALL
JNI_OnLoad(JavaVM *javaVm, void *) {
    ::javaVm = javaVm;
    return JNI_VERSION_1_6;

}
/**
 * 接收jobject
 * @param jobject
 * @return
 */
void *run(void *jobject) {
    JNIEnv *jniEnv = nullptr;
    //给当前线程设置JNIEnv
    int result = javaVm->AttachCurrentThread(&jniEnv, nullptr);
    if (result != JNI_OK) {
        LOGD("给当前线程设置JNIEnv失败")
    }
    jclass j_class = jniEnv->GetObjectClass(static_cast<_jobject *>(jobject));
    jmethodID j_methodId = jniEnv->GetMethodID(j_class, "threadCallBack", "()V");
    jniEnv->CallVoidMethod(static_cast<_jobject *>(jobject), j_methodId);
    //当前线程移除JNIEnv
    javaVm->DetachCurrentThread();
    //线程执行函数,记得写返回值,否则崩溃
    return nullptr;

};
extern "C"
JNIEXPORT void JNICALL
Java_com_hvm_vender_jni_103_MainActivity_threadTest(JNIEnv *env, jobject thiz) {
    //线程ID
    pthread_t pid;
    //全局引用
    jobject jobjectGlobal = env->NewGlobalRef(thiz);
    //将jobject提示为全局引用,传递到其他线程
    pthread_create(&pid, nullptr, run, jobjectGlobal);
    //注意:
    //1、jobject为局部引用不能跨线程使用、不能跨函数使用,需要提示为全局引用>NewGlobalRef(thiz);
    //2、JavaVM是全局的,和进程绑定。
    //4、JNIEnv 不能跨线程,可以跨函数使用。当需要在其他线程使用的时候,
    //可以拿到全局的 JavaVM,调用javaVm->AttachCurrentThread,
    //使用完之后,移除 javaVm->DetachCurrentThread();
    //4、JNIEnv和线程绑定
}

注意

1、jobject为局部引用不能跨线程使用、不能跨函数使用,需要转换为全局引用>NewGlobalRef(thiz);
2、JavaVM是全局的,和进程绑定。
3、JNIEnv 不能跨线程,可以跨函数使用。当需要在其他线程使用的时候,可以拿到全局的 JavaVM,调用
javaVm->AttachCurrentThread(&jniEnv, nullptr);为当前线程绑定JNIEnv
使用完之后,移除JNIEnv ,调用 javaVm->DetachCurrentThread();
4、JNIEnv和线程绑定,JavaVM和进程绑定
5、线程执行函数,记得写返回值,否则崩溃

源码

https://gitee.com/daxiaa/jni-ndk.git

你可能感兴趣的:(JNI-NDK(JNI函数动态注册、JNI线程))