jni-04、静态注册、动态注册、JavaVM与JNIEnv与jobject的地址问题

动态注册性能优于静态注册

// 默认情况下,就是静态注册,静态注册是最简单的方式,NDK开发过程中,基本上使用静态注册
// Android 系统的C++源码:基本上都是动态注册(麻烦)

// 静态注册: 优点:开发简单
// 缺点
// 1.JNI函数名非常长
// 2.捆绑 上层 包名 + 类名
// 3.运行期 才会去 匹配JNI函数,性能上 低于 动态注册

// 静态注册:
// new Student.方法
// new Student.方法

// 动态注册:
// s = new Student(); 所以的初始化已经做了
// s.方法
// s.方法

动态注册 在JNI_OnLoad里面注册函数

  • Java native
    public native void dynamicJavaMethod01(); // 动态注册1
    public native int dynamicJavaMethod02(String valueStr); // 动态注册2
  • cpp
// 日志输出
#include 

#define TAG "JNISTUDY"
// __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__);

JavaVM *jVm = nullptr; // 0x003545 系统乱值,C++11后,取代NULL,作用是可以初始化指针赋值
const char *mainActivityClassName = "com/derry/as_jni_project/MainActivity";

// native 真正的函数
// void dynamicMethod01(JNIEnv *env, jobject thiz) { // OK的
void dynamicMethod01() { // 也OK  如果你用不到  JNIEnv jobject ,可以不用写
    LOGD("我是动态注册的函数 dynamicMethod01...");
}

int dynamicMethod02(JNIEnv *env, jobject thiz, jstring valueStr) { // 也OK
    const char *text = env->GetStringUTFChars(valueStr, nullptr);
    LOGD("我是动态注册的函数 dynamicMethod02... %s", text);
    env->ReleaseStringUTFChars(valueStr, text);
    return 200;
}

/*
     typedef struct {
        const char* name;       // 函数名
        const char* signature; // 函数的签名
        void*       fnPtr;     // 函数指针
     } JNINativeMethod;
     */
static const JNINativeMethod jniNativeMethod[] = {
        {"dynamicJavaMethod01", "()V",                   (void *) (dynamicMethod01)},
        {"dynamicJavaMethod02", "(Ljava/lang/String;)I", (int *) (dynamicMethod02)},
};


// Java:像 Java的构造函数,如果你不写构造函数,默认就有构造函数,如果你写构造函数 覆写默认的构造函数
// JNI JNI_OnLoad函数,如果你不写JNI_OnLoad,默认就有JNI_OnLoad,如果你写JNI_OnLoad函数 覆写默认的JNI_OnLoad函数
extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM *javaVm, void *) {
    // this.javaVm = javaVm;
    ::jVm = javaVm;

    // 做动态注册 全部做完

    JNIEnv *jniEnv = nullptr;
    int result = javaVm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6);

    // result 等于0  就是成功    【C库 FFmpeg 成功就是0】
    if (result != JNI_OK) {
        return -1; // 会奔溃,故意奔溃
    }

    LOGE("System.loadLibrary ---》 JNI Load init");

    jclass mainActivityClass = jniEnv->FindClass(mainActivityClassName);

    // jint RegisterNatives(Class, 我们的数组==jniNativeMethod, 注册的数量 = 2)
    jniEnv->RegisterNatives(mainActivityClass,
                            jniNativeMethod,
                            sizeof(jniNativeMethod) / sizeof(JNINativeMethod));

    LOGE("动态 注册没有毛病");

    return JNI_VERSION_1_6; //  // AS的JDK在JNI默认最高1.6      存Java的JDKJNI 1.8
}

JNIEnv、jobject 都不能跨进程,会崩溃

  • Java 层代码
public native void naitveThread(); // Java层 调用 Native层 的函数,完成JNI线程

/**
 * TODO 下面是 被native代码调用的 Java方法
 * 第二部分 JNI线程
 */
public void updateActivityUI() {
    if (Looper.getMainLooper() == Looper.myLooper()) { // TODO C++ 用主线程调用到此函数 ---->  主线程
        new AlertDialog.Builder(MainActivity.this)
                .setTitle("UI")
                .setMessage("updateActivityUI Activity UI ...")
                .setPositiveButton("老夫知道了", null)
                .show();
    } else {  // TODO  C++ 用异步线程调用到此函数 ---->  异步线程
        Log.d(TAG, "updateActivityUI 所属于子线程,只能打印日志了..");

        runOnUiThread(new Runnable() { // 哪怕是异步线程  UI操作 正常下去 runOnUiThread
            @Override
            public void run() {

                // 可以在子线程里面 操作UI
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("updateActivityUI")
                        .setMessage("所属于子线程,只能打印日志了..")
                        .setPositiveButton("老夫知道了", null)
                        .show();
            }
        });
    }
}
  • cpp
class MyContext {
public:
    JNIEnv *jniEnv = nullptr;  // 不能跨线程 ,会奔溃
    jobject instance = nullptr; // 不能跨线程 ,会奔溃
};

void *myThreadTaskAction(void *pVoid) { // 当前是异步线程
    LOGE("myThreadTaskAction run");

    // 需求:有这样的场景,例如:下载完成 ,下载失败,等等,必须告诉Activity UI端,所以需要在子线程调用UI端

    // 这两个是必须要的
    // JNIEnv *env
    // jobject thiz   OK

    MyContext * myContext = static_cast(pVoid);

    // jclass mainActivityClass = myContext->jniEnv->FindClass(mainActivityClassName); // 不能跨线程 ,会奔溃
    // mainActivityClass = myContext->jniEnv->GetObjectClass(myContext->instance); // 不能跨线程 ,会奔溃

    // TODO 解决方式 (安卓进程只有一个 JavaVM,是全局的,是可以跨越线程的)
    JNIEnv * jniEnv = nullptr; // 全新的JNIEnv  异步线程里面操作
    jint attachResult = ::jVm->AttachCurrentThread(&jniEnv, nullptr); // 附加当前异步线程后,会得到一个全新的 env,此env相当于是子线程专用env
    if (attachResult != JNI_OK) {
        return 0; // 附加失败,返回了
    }

    // 1.拿到class
    jclass mainActivityClass = jniEnv->GetObjectClass(myContext->instance);

    // 2.拿到方法
    jmethodID updateActivityUI = jniEnv->GetMethodID(mainActivityClass, "updateActivityUI", "()V");

    // 3.调用
    jniEnv->CallVoidMethod(myContext->instance, updateActivityUI);

    ::jVm->DetachCurrentThread(); // 必须解除附加,否则报错

    LOGE("C++ 异步线程OK")

    return nullptr;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity_naitveThread(JNIEnv *env, jobject job) { // 当前是主线程
    /*pthread_t pid;
    pthread_create(&pid, nullptr, myThreadTaskAction, nullptr);
    pthread_join(pid, nullptr);*/

    MyContext * myContext = new MyContext;
    myContext->jniEnv = env;
    // myContext->instance = job; // 默认是局部引用,会奔溃
    myContext->instance = env->NewGlobalRef(job); // 提升全局引用

    pthread_t pid;
    pthread_create(&pid, nullptr, myThreadTaskAction, myContext);
    pthread_join(pid, nullptr);
}

JavaVM、JNIEnv、jobject地址问题

  • Java
public class MainActivity extends AppCompatActivity {

    public native void nativeFun1();
    public native void nativeFun2(); // 2
    public static native void staticFun3(); // 3
    public static native void staticFun4();
    
    public void clickMethod4(View view) {
        nativeFun1(); // main线程调用的
        nativeFun2(); // main线程调用的
        staticFun3(); // main线程调用的
        // 第四个  new Thread 调用  ThreadClass == clasz 当前函数clazz地址
        new Thread() {
            @Override
            public void run() {
                super.run();
                staticFun4(); // Java的子线程调用
            }
        }.start();
    }
    
     public void clickMethod5(View view) {
        startActivity(new Intent(this, MainActivity2.class));
    }
    
}

public class MainActivity2 extends AppCompatActivity {

    public native void nativeFun5();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        nativeFun5(); // main线程调用的
    }
}
  • cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity_nativeFun1(JNIEnv *env, jobject job) {
    JavaVM * javaVm = nullptr;
    env->GetJavaVM(&javaVm);

    // 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址,  JNI_OnLoad的jvm地址
    LOGE("nativeFun1 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::jVm);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity_nativeFun2(JNIEnv *env, jobject job) {
    JavaVM * javaVm = nullptr;
    env->GetJavaVM(&javaVm);

    // 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址,  JNI_OnLoad的jvm地址
    LOGE("nativeFun2 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::jVm);
}

void * run(void *) { // native的子线程 env地址  和  Java的子线程env地址,一样吗  不一样的
    JNIEnv * newEnv = nullptr;
    ::jVm->AttachCurrentThread(&newEnv, nullptr);
    // 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址,  JNI_OnLoad的jvm地址

    LOGE("run jvm地址:%p,  当前run函数的newEnv地址:%p \n", ::jVm, newEnv);

    ::jVm->DetachCurrentThread();
    return nullptr;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity_staticFun3(JNIEnv *env, jclass clazz) {
    JavaVM * javaVm = nullptr;
    env->GetJavaVM(&javaVm);

    // 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址,  JNI_OnLoad的jvm地址
    LOGE("nativeFun3 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, clazz, ::jVm);

    // 调用run
    pthread_t pid;
    pthread_create(&pid, nullptr, run, nullptr);
}

// Java子线程调用的
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity_staticFun4(JNIEnv *env, jclass clazz) {
    JavaVM * javaVm = nullptr;
    env->GetJavaVM(&javaVm);

    // 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址,  JNI_OnLoad的jvm地址
    LOGE("nativeFun4 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, clazz, ::jVm);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_derry_as_1jni_1project_MainActivity2_nativeFun5(JNIEnv *env, jobject job) {
    JavaVM * javaVm = nullptr;
    env->GetJavaVM(&javaVm);

    // 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址,  JNI_OnLoad的jvm地址
    LOGE("nativeFun5 当前函数env地址%p,  当前函数jvm地址:%p,  当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::jVm);
}
  • 打印的log
nativeFun1 当前函数env地址0x754b4c5460,  当前函数jvm地址:0x754b4bd6c0,  当前函数job地址:0x7fc4fa9d64, JNI_OnLoad的jvm地址:0x754b4bd6c0
nativeFun2 当前函数env地址0x754b4c5460,  当前函数jvm地址:0x754b4bd6c0,  当前函数job地址:0x7fc4fa9d64, JNI_OnLoad的jvm地址:0x754b4bd6c0
nativeFun3 当前函数env地址0x754b4c5460,  当前函数jvm地址:0x754b4bd6c0,  当前函数clazz地址:0x7fc4fa9db4, JNI_OnLoad的jvm地址:0x754b4bd6c0
run jvm地址:0x754b4bd6c0,  当前run函数的newEnv地址:0x754b4c81e0
nativeFun4 当前函数env地址0x7542e22840,  当前函数jvm地址:0x754b4bd6c0,  当前函数clazz地址:0x7518bfe984, JNI_OnLoad的jvm地址:0x754b4bd6c0
nativeFun5 当前函数env地址0x754b4c5460,  当前函数jvm地址:0x754b4bd6c0,  当前函数job地址:0x7fc4fa9cb4, JNI_OnLoad的jvm地址:0x754b4bd6c0

结论

  1. JavaVM全局,绑定当前进程, 只有一个地址
  2. JNIEnv线程绑定, 绑定主线程,绑定子线程
  3. jobject 谁调用JNI函数,谁的实例会给jobject
  • JNIEnv *env 不能跨越线程,否则奔溃, 他可以跨越函数 【解决方式:使用全局的JavaVM附加当前异步线程 得到权限env操作】
  • jobject thiz 不能跨越线程,否则奔溃,不能跨越函数,否则奔溃 【解决方式:默认是局部引用,提升全局引用,可解决此问题】
  • JavaVM 能够跨越线程,能够跨越函数

你可能感兴趣的:(jni-04、静态注册、动态注册、JavaVM与JNIEnv与jobject的地址问题)