Android NDK 调用C++调用Java

这篇文章依旧是为进行NDK下视频和硬盘开发的准备,主要用于记录NDK开发中关于在C++ Native如何调用层面如何调用Java代码的方法。在NDK开发中调用是Java是常见的场景,特别是写结果到UI是必须。

C++ 和Java相互操作遵循JNI规范,分为主线程和子线程两种不同的方式。

主线程下调用

主线程比较简单,代码如下

    extern "C"
JNIEXPORT void JNICALL
Java_com_example_pthread_MainActivity_callFromMainThreaad(JNIEnv *env, jobject thiz) {
    jclass clz = env->GetObjectClass(thiz);
    jmethodID jmid = env->GetMethodID(clz,"CallFromJni", "(Ljava/lang/String;)V");
    jstring string = env->NewStringUTF("my jni method");
    env->CallVoidMethod(thiz,jmid,string);
    env->DeleteLocalRef(string);
}

主要是通过当前的jobject欧去jclass,在jclass就可以通过签名来获取java的定义在MainActivity内的方法,如上述代码对应的就是MainActivy.kt下的方法,CallFromJni

fun CallFromJni(jniText:String) {
        this.runOnUiThread() {
            binding.sampleText.text = jniText
        }
}

其中关于JNI数据类型和方法签名:

1. 数据类型

JavaJNI
charjchar
bytejbyte
shortjshort
intjint
longjlong
floatjfloat
doublejdouble
booleanjboolean

2. JNI方法签名

为什么会有方法签名这种东西呢?这是因为Java这边支持函数重载,即虽然参数不一样,但是方法名一样,那么在JNI层它们的方法名都会是一样的,那JNI也会犯迷糊了,得找哪个呢?
不过也正是因为其参数类型是不一样的,所以就出现了方法签名,利用方法签名和方法名来唯一确定一个JNI函数的调用。
既然方法签名是基于参数类型的不同而形成的,首先要知道Java各数据类型对应的签名是什么,也就是所谓的类型签名,
在jni.h文件中就已经定义了这样一套规则,如下:

typedef union jvalue {
     jboolean    z;
     jbyte       b;
     jchar       c;
     jshort      s;
     jint        i;
     jlong       j;
     jfloat      f;
     jdouble     d;
     jobject     l;
} jvalue;

对应于Java端的数据类型如下:

Java 类型类型签名
booleanZ
byteB
charC
shortS
intI
longL
voidV
floatF
doubleD
L全限定名;,比如String, 其签名为Ljava/lang/util/String;
数组[类型签名, 比如 [B

如 (Ljava/lang/String;)V" 表示 void methodName(String)

当按照以上规则,获取方法后,可以采用env->CallVoidMethod调用void返回值的方法。

子线程调用

在pthread开的线程中无法获取当前的jobject对象,因为jobject线程的安全性等问题,也不能将jobject对象通过指针传递给子线程。正确的方法是获取jvm的实列后去做

JavaVM* jvm;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void* reserved)
{
    JNIEnv *env;
    jvm = vm;
    if(vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
    {
        return -1;
    }

    return JNI_VERSION_1_6;
}

    extern "C"
JNIEXPORT void JNICALL
Java_com_example_pthread_MainActivity_callbackFromChildThread(JNIEnv *env, jobject thiz) {
    globalJObj = env->NewGlobalRef(thiz);
    pthread_create(&childThread,NULL,childThreadCallback,NULL);
}

这里展现一种采用c++ 类统一封装进行调用的方式

1.头文件

//
// Created by Lokie on 2021/12/3.
//

#ifndef PTHREAD_WRITEBACKTOUSERINTERFACE_H
#define PTHREAD_WRITEBACKTOUSERINTERFACE_H

#include <jni.h>

class WriteBackToUserInterface {

    private:
        JavaVM *jvm;
        _JNIEnv *jenv;
        jobject jobj;
        jmethodID jmid;

    public:
        WriteBackToUserInterface(JNIEnv *env,JavaVM *javaVm,jobject obj);
        ~WriteBackToUserInterface();
        void WriteMessageToUserInterface(const char* message,int threadType);
};


#endif //PTHREAD_WRITEBACKTOUSERINTERFACE_H

2. 实现

//
// Created by Lokie on 2021/12/3.
//

#include "WriteBackToUserInterface.h"


void WriteBackToUserInterface::WriteMessageToUserInterface(const char *message, int threadType) {
    if(threadType == 0)
    {
        JNIEnv *env;
        jvm->AttachCurrentThread(&env, 0);
        jstring jmsg = env->NewStringUTF(message);
        env->CallVoidMethod(jobj, jmid, jmsg);
        env->DeleteLocalRef(jmsg);
        jvm->DetachCurrentThread();
    }
    else if(threadType == 1)
    {
        jstring jmsg = jenv->NewStringUTF(message);
        jenv->CallVoidMethod(jobj, jmid, jmsg);
        jenv->DeleteLocalRef(jmsg);
    }
}

WriteBackToUserInterface::WriteBackToUserInterface(JNIEnv *jniEnv,JavaVM *javaVm,jobject obj) {
    jenv = jniEnv;
    jvm = javaVm;
    jobj = obj;

    jclass clz = jniEnv->GetObjectClass(obj);
    if(!clz)
    {
        return;
    }


    jmid = jniEnv->GetMethodID(clz,"SetLisView", "(Ljava/lang/String;)V");
    if(!jmid)
        return;
}

WriteBackToUserInterface::~WriteBackToUserInterface() {

}

3. 调用

pthread_t testThread
extern "C"
JNIEXPORT void JNICALL
Java_com_example_pthread_MainActivity_nativeThread(JNIEnv *env, jobject thiz) {
    // 初始化
    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&cond,NULL);
    for(int i=0;i<10;i++) {
        queue.push(i);
    }

    writeBackToUserInterface =new WriteBackToUserInterface(env,jvm,env->NewGlobalRef(thiz));

    pthread_create(&testThread,NULL,test,writeBackToUserInterface);
}

void *test(void * args) {
    WriteBackToUserInterface* writeBackToUserInterface1 = (WriteBackToUserInterface*)args;
    while (1) {
        // 加锁。因为queue公用
        pthread_mutex_lock(&mutex);
        // 队列里又消息,则消费
        if(queue.size() > 0) {
            int val = queue.front();
            LOGI("now consume %d",val);
            char* str = new char[200];
            sprintf(str,"%s %d","now consume" ,val);
            writeBackToUserInterface1->WriteMessageToUserInterface(str,0);
            queue.pop();
        } else {
            // 队列空了,等条件并放开互斥
            pthread_cond_wait(&cond,&mutex);
        }
        // 放开互斥锁
        pthread_mutex_unlock(&mutex);
    }
    return 0;
}

Lokie博客
请先登录后发表评论
  • 最新评论
  • 总共0条评论
  • 本博客使用免费开源的 laravel-bjyblog v5.5.1.1 搭建 © 2014-2018 lokie.wang 版权所有 ICP证:沪ICP备18016993号
  • 联系邮箱:kitche1985@hotmail.com