Android ndk-jni语法—— 6

一.C++中子线程操作JNIEnv环境指针

先声明一个native方法去启动线程:

csharp 复制代码
public native void executePthread();

在C++中进行实现:

arduino 复制代码
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_MainActivity_executePthread(JNIEnv *env, jobject thiz) {
    // 线程
    pthread_t thread;
    // 创建线程
    // 参数1 指向线程标识符的指针
    // 参数2 用来设置线程属性
    // 参数3 线程运行函数的起始地址
    // 参数4 运行函数的参数
    pthread_create(&thread, NULL, get_min, NULL);
}

我们创建get_min运行函数:

arduino 复制代码
void* get_min(void* arg) {
    for (int i = 0; i < 5; i ++) {
        __android_log_print(ANDROID_LOG_INFO, "carey====get_min", "%d", i);
    }
    JNIEnv *env = NULL;
    // 正确调用 AttachCurrentThread
    int result = jvm->AttachCurrentThread(&env, NULL);
    if (result!= 0) {
        // 处理 AttachCurrentThread 失败的情况
        __android_log_print(ANDROID_LOG_ERROR, "carey====", "Failed to attach current thread.");
        return NULL;
    }
    __android_log_print(ANDROID_LOG_INFO, "carey====", "%s", "my name is carey.");
    // 分离线程,解除关联JVM虚拟机
    jvm->DetachCurrentThread();
    return NULL;
}

里面我们有一个循环打印,完了通过jvm的AttachCurrentThread方法获取环境变量JNIEnv指针,这里的jvm是JavaVM,JavaVM可以通过重写JNI_OnLoad方法得到:

javascript 复制代码
JavaVM* jvm;
// 当我们应用程序加载完毕之后,虚拟机立马调用该方法
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    // 通过JavaVM获取环境指针
    jvm = vm;
    return JNI_VERSION_1_6;
}

通过上面方法获取到JavaVM指针,并返回当前JNI的版本号。

当Java虚拟机卸载包含本地代码(通常是用C或C++编写的动态库)的库时,会调用JNI_OnUnload方法:

arduino 复制代码
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);

最后Java中调用executePthread方法,查看日志打印:

二. C++中的常量

新建一个java类,并声明一个native方法:

csharp 复制代码
public class NDKCppInterface {
    // C++中常量
    public native void executeCppConst();
}

在Terminal中执行命令,生成相应的头部.h文件:

如果javah命令找不到,可以在环境变量中添加jdk/bin目录。这时在当前java类同级目录下生成了.h文件:

arduino 复制代码
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_carey_myndk_NDKCppInterface */

#ifndef _Included_com_carey_myndk_NDKCppInterface
#define _Included_com_carey_myndk_NDKCppInterface
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_carey_myndk_NDKCppInterface
 * Method:    executeCppConst
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_carey_myndk_NDKCppInterface_executeCppConst
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

我们把这个.h文件放到cpp/目录下:

同时,我们在cpp/目录下创建对应的.cpp文件com_carey_myndk_NDKCppInterface.cpp:

arduino 复制代码
#include <jni.h>
#include <string.h>
#include <stdlib.h>
#include "com_carey_myndk_NDKCppInterface.h"
#include <android/log.h>
/*
 * Class:     com_carey_myndk_NDKCppInterface
 * Method:    executeCppConst
 * Signature: ()V
 */
extern "C"
JNIEXPORT void JNICALL Java_com_carey_myndk_NDKCppInterface_executeCppConst
  (JNIEnv *, jobject) {
    const int a = 100; // 常量
    int *p = (int*)&a; // 指针指向a的地址
    *p = 200; // 改为200
    __android_log_print(ANDROID_LOG_INFO, "carey====", "C语言: %d", a);
}

同时更改CMakeLists.txt文件中添加新增加的cpp文件:

在Java中调用该方法,查看日志:

ini 复制代码
NDKCppInterface ndkCppInterface = new NDKCppInterface();
ndkCppInterface.executeCppConst();

这里我们看到输出的a值还是100,就是说明这个常量值是不能修改的。

三.指针的引用

我们新增一个方法:

csharp 复制代码
public class NDKCppInterface {
    // C++中常量
    public native void executeCppConst();
    // 指针的引用
    public native void executeCppPointer();
}

在.h文件和.cpp文件中分别添加该方法是声明和实现:

arduino 复制代码
JNIEXPORT void JNICALL
Java_com_carey_myndk_NDKCppInterface_executeCppPointer(JNIEnv *env, jobject thiz);
ini 复制代码
// 定义一个结构体
struct User {
    char *name;
    int age;
};

// 更新user
void update_user(User **u) { // 二级指针,`u` 是一个指向 `User*` 类型指针的指针。要修改 `u` 所指向的指针(即 `*u`),需要通过解引用 `u` 来实现。
    User *user = (User*) malloc(sizeof(User));
    user->name = "Carey";
    user->age = 100;
    *u = user;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_NDKCppInterface_executeCppPointer(JNIEnv *env, jobject thiz) {
    User *user = nullptr;
    update_user(&user);
    __android_log_print(ANDROID_LOG_INFO, "carey====", "名字: %s, 年龄:%d", user->name, user->age);
    
    delete user;
    user = nullptr;
}

我们先用二级指针的方式去更改结构体对象User的数据,查看打印:

下面通过指针的引用去更改User信息,我们改下update_user方法代码:

arduino 复制代码
    // 指针引用
    void update_user(User* &u) { // `u` 是一个对 `User*` 类型指针的引用。可以直接修改 `u` 所指向的地址。
        // 使用new分配内存并初始化
        u = new User(); // 调用默认构造函数
        strcpy(u->name, "Carey2"); // 使用strcpy复制字符串
        u->age = 28;
    }



    extern "C"
    JNIEXPORT void JNICALL
    Java_com_carey_myndk_NDKCppInterface_executeCppPointer(JNIEnv *env, jobject thiz) {
        User *user = nullptr;
        update_user(user); // 传入指针
        __android_log_print(ANDROID_LOG_INFO, "carey====", "名字: %s, 年龄:%d", user->name, user->age);
        
        delete user; // 释放内存
        user = nullptr;
    }

这样我们查看打印:

四.总结

今天学习了C++中创建线程、获取JavaVM指针,通过JavaVM指针的AttachCurrentThread方法得到JNIEnv指针;还学习了C++中指针引用和常量。喜欢的可以点赞和收藏,感谢!

相关推荐
结衣结衣.25 分钟前
LeetCode热题100(滑动窗口篇)
java·c++·python·算法·leetcode·职场和发展
刘争Stanley37 分钟前
Android系统开发(八):从麦克风到扬声器,音频HAL框架的奇妙之旅
android·c语言·framework·音视频·框架·c·hal
轻口味1 小时前
【HarmonyOS NAPI 深度探索6】使用 N-API 创建第一个 Hello World 原生模块
c++·华为·harmonyos·napi·harmonyos-next
2401_897908311 小时前
2019-Android-高级面试题总结-从java语言到AIDL使用与原理
android·java·开发语言
fancc椰1 小时前
STL—stack与queue
开发语言·c++
不是编程家2 小时前
C++第十五讲:异常
jvm·c++
知星小度S3 小时前
今天你学C++了吗?——C++中的STL
开发语言·c++
三月的船长4 小时前
Android Studio常用操作备忘录
android·ide·android studio
轻口味5 小时前
【HarmonyOS NAPI 深度探索9】发布到 npm 并管理版本
c++·华为·npm·harmonyos·napi·harmonyos-next
像污秽一样6 小时前
AI刷题-小R的随机播放顺序、不同整数的计数问题
开发语言·c++·算法