这篇文章依旧是为进行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数据类型和方法签名:
Java | JNI |
---|---|
char | jchar |
byte | jbyte |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
boolean | jboolean |
为什么会有方法签名这种东西呢?这是因为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 类型 | 类型签名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
void | V |
float | F |
double | D |
类 | 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++ 类统一封装进行调用的方式
//
// 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
//
// 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() {
}
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.Wang原创文章,转载无需和我联系,但请注明来自lokie博客http://lokie.wang