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++中指针引用和常量。喜欢的可以点赞和收藏,感谢!

相关推荐
Shadow(⊙o⊙)2 分钟前
硬核手搓解析!进程-内核分析:命令行参数及环境变量,重构main()
linux·运维·服务器·开发语言·c++·后端·学习
YYYing.4 分钟前
【C++项目之高并发内存池 (五)】一些小细节和性能优化及整体测试
c++·性能优化·高并发·内存池·基数树
2301_789015627 分钟前
Linux:基础指令(二)
linux·运维·服务器·c语言·开发语言·c++·算法
闻缺陷则喜何志丹12 分钟前
【区间合并】P7912 [CSP-J 2021] 小熊的果篮|普及+
c++·算法·洛谷·区间合并
2501_9159090625 分钟前
完整指南:如何将iOS应用上架到App Store
android·ios·小程序·https·uni-app·iphone·webview
赏金术士2 小时前
Retrofit + Kotlin 协程(Android 实战教程)
android·kotlin·retrofit
REDcker9 小时前
C++变量存储与ELF段布局详解 从const全局到rodata与nm_readelf验证实践
java·c++·面试
大炮筒9 小时前
COCOS2DX4.0CPPWIN移植安卓踩坑总结
android
王老师青少年编程11 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串排序】:合并序列
c++·字符串·csp·高频考点·信奥赛·字符串排序·合并序列
qq_4228286211 小时前
android图形学之SurfaceControl和Surface的关系 五
android·开发语言·python