Day08_JNI、Linux 常用命令

一、JNI_OnLoad

通过 System.loadLibrary() 去加载 NDK 编译生成的动态库时,内部就会先去找动态库中是否有 JNI_OnLoad 这个函数,找到了就会调用这个函数

JNI_OnLoad 函数会告诉 JVM,native 组件使用的 JNI 版本

  • NDK 提供的 JNI 版本
    查看 NDK 版本,如下图,当前 NDK 版本为 19
    AndroidStudio 通过 SDK Tools 查看 NDK 版本

如下图所示,根据方法提示可知,当前版本 NDK 提供 JNI_VERSION_1_1、JNI_VERSION_1_2、JNI_VERSION_1_4、JNI_VERSION_1_6,只能用 JNI_VERSION_1_2、JNI_VERSION_1_4、JNI_VERSION_1_6,用 JNI_VERSION_1_1 报错
NDK 提供 JNI_VERSION 各版本

  • JDK 提供的 JNI 版本
    JDK 1.8 提供 JNI_VERSION_1_1、JNI_VERSION_1_2、JNI_VERSION_1_4、JNI_VERSION_1_6、JNI_VERSION_1_8

jni.h 文件查看 JNI_VERSION 定义的各版本

JNI_OnLoad()

  • 调用 System.loadLibrary() 时,内部会去查找动态库中的 JNI_OnLoad 函数,若存在则调用
  • JNI_OnLoad 会返回 JNI 版本信息
// 参数一:JNIEnv,JVM 虚拟机
// 参数二:void*,系统传过来该值为 NULL,我们可以不用管第二个参数
// 返回值 jint:当前使用的 JNI 版本(Android 使用 SDK 提供的类,JNI 在 SDK 中,NDK 使用了 JNI)
jint JNI_OnLoad(JNIEnv *env, void* r) {
    
}

静态注册

在 .java 文件中声明 native 方法

public native String stringFromJNI();

在 .cpp 文件中实现 native 方法

extern "C" JNIEXPORT jstring JNICALL
Java_com_lql_dn_ndk_day07_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

动态注册

可以在 JNI_OnLoad 函数中进行动态注册

  1. native 方法声明
public native void dynamicJavaTest();
public native int dynamicJavaTest2(int i);
  1. native 方法实现
  2. 定义一个 JNINativeMethod 结构体数组
#include <android/log.h>

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "JNI_TAG", __VA_ARGS__);

// native 方法实现必须写在结构体数组前面,否则当结构体数组会报红,找不到 native 方法实现
void dynamicTest() {

}

// 注意:如果在 Java 代码中调用 dynamicJavaTest2(88),则 88 对应 native 方法 dynamicTest2() 的第三个参数,前两个参数有参数占位即可
jint dynamicTest2(jstring i1, jboolean i2, jint i) {
    LOGE("dynamicTest2: %d", i);
    return 0;
}

// JNINativeMethod 结构体数组
static const JNINativeMethod method[] = {
        {"dynamicJavaTest", "()V", (void*)dynamicTest},
        {"dynamicJavaTest2", "(I)I", (int*)dynamicTest2}
};

static const char *mClassName = "com/lql/dn/ndk/day07/MainActivity";

jint JNI_OnLoad(JavaVM *vm, void *r) {
    LOGE("第一个调用的函数");

    JNIEnv *env = nullptr;
    int rc = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
    if (rc != JNI_OK) {
        return -1;
    }
    jclass clazz = (env)->FindClass(mClassName);
    (env)->RegisterNatives(clazz, method, sizeof(method) / sizeof(JNINativeMethod));
    
    return JNI_VERSION_1_6;
}

二、native 线程调用 Java

在 .java 文件中声明两个方法,一个是 native 方法,另一个是 java 方法(供 native 方法调用)

public native void nativeThread();

public void updateUI() {
    Log.e("TAG", "updateUIupdateUIupdateUI");
    if (Looper.myLooper() == Looper.getMainLooper()) {
        Toast.makeText(this, "更新UI", Toast.LENGTH_SHORT).show();
    } else {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, "子线程更新UI", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

在 .cpp 文件中实现 native 方法,开启 native 线程,调用 java 方法

#include <pthread.h>

// 借助 JavaVM 对象可以创建 JNIEnv 对象
JavaVM *_vm;

jint JNI_OnLoad(JavaVM *vm, void *r) {
    _vm = vm;
    LOGE("第一个调用的函数");
    
    return JNI_VERSION_1_6;
}

// pthread 的执行函数
void* threadTask(void* args) {
    JNIEnv *env;
    // 关键二:当前是 native 线程,附加到 Java 虚拟机,获取当前线程的 JNIEnv
    int rc = _vm->AttachCurrentThread(&env, 0); // 指针的指针可以修改指针的值(指向)。直接传指针不能修改,传参时调用了复制构造函数
    if (rc != JNI_OK) {
        return reinterpret_cast<void *>(-1);
    }
    
    TContext *tContext = static_cast<TContext *>(args);
    jclass cls = env->GetObjectClass(tContext->instance); // 获取 MainActivity 的 Class
    jmethodID jmid = env->GetMethodID(cls, "updateUI", "()V"); // 对应 Java 层的 upateUI() 方法
    env->CallVoidMethod(tContext->instance, jmid); // 调用 Java 层的 upateUI() 方法
    
    delete(tContext);
    tContext = 0;
    
    // 关键三:必须调用 DetachCurrentThread 方法,从 Java 虚拟机分离
    _vm->DetachCurrentThread();
    
    return 0;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_lql_dn_ndk_day07_MainActivity_nativeThread(JNIEnv *env, jobject instance) {
    TContext *tContext = new TContext;
    // 关键一:需要在外面使用,必须创建全局引用
    tContext->instance = env->NewGlobalRef(instance);
    
    // 创建线程
    pthread_t pid;
    // 启动线程
    pthread_create(&pid, 0, threadTask, 0); // 第三个参数是函数指针,第四个参数会传给第三个参数
}

结构体和类的区别
在 C/C++ 中,struct 结构体(属性、继承)默认行为是 public,class 类(属性、继承)默认行为是 private

三、Linux 常用命令(略)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值