最近研究了一下JNI,JNI作为Java/Kotlin 同C/C++相互调用的桥梁,在安卓native开发中我感觉还是比较重要。
本文简要介绍一下他们之间互相调用的流程和注意事项:
借由Android Studio来开发比较简单: 可以在模板上选择native C++生成安卓工程
如果哪里说得不对,想看到的真大佬能够指出,感激不尽!
从晚上十点敲到晚上十一点半,也不容易~
作为C++开发,在Java的代码规范上,和具体名词或者原理上存在知识上的不足,无视无视
package com.nativer.myapplication;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NativeHelper nativeHelper = new NativeHelper();
Log.v(TAG, "" + nativeHelper.getStringSizeNow("fuck you"));
}
private final String TAG = "MainActivity";
}
然后创建一个用于和native层交互的类,里面需要在静态块中加载这个项目生成中CMakeLists.txt指定的的动态库(demo 就按默认生成的来)
package com.nativer.myapplication;
import android.util.Log;
public class NativeHelper {
// Used to load the 'myapplication' library on application startup.
static {
System.loadLibrary("myapplication");
}
public NativeHelper() {
}
/**
* A native method that is implemented by the 'myapplication' native library,
* which is packaged with this application.
*/
public native int getStringSizeNow(String msg);
private final String TAG = "NativeHelper";
}
生成C++的C语言式全局函数:比如我的是下面这样,我实现了这个函数:
extern "C"
JNIEXPORT jint JNICALL
Java_com_nativer_myapplication_NativeHelper_getStringSizeNow(JNIEnv *env, jobject thiz,
jstring msg) {
// TODO: implement getStringSizeNow()
return env->GetStringLength(msg);
}
JNIEnv
对象提供了哪些函数,这是一个比较需要研究和学习的,可以通过头文件查看,或者上网查阅资料,在这里我就用了他提供的函数返回Java字符串对象的长度。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NativeHelper nativeHelper = new NativeHelper();
//Log.v(TAG, "size:" + nativeHelper.getStringSizeNow("fuck you"));
nativeHelper.getStringSizeWithCallBack("Hello");
}
private final String TAG = "MainActivity";
}
然后在NativeHelper
中添加两个方法,一个是native方法,一个是用于C++回调的方法
public native void getStringSizeWithCallBack(String msg);
public void onGetSize(int size) {
Log.v(TAG, "size:" + size);
}
生成C++的C语言式全局函数:
extern "C"
JNIEXPORT void JNICALL
Java_com_nativer_myapplication_NativeHelper_getStringSizeWithCallBack(JNIEnv *env, jobject thiz,
jstring msg) {
// TODO: implement giveMeSize()
jclass clz = env->GetObjectClass(thiz);
jmethodID method = env->GetMethodID(clz, "onGetSize", "(I)V");
if (clz and method) {
auto size = env->GetStringLength(msg);
env->CallVoidMethod(thiz, method, static_cast<jint>(size));
}
}
需要强调的是获取方法ID的那个表达式:通过jclass字节码对象和函数名,以及入参类型得到函数签名,前两个参数都好懂,但是参数类型是一个字符串,遇到不同的参数类型,究竟该填什么字符串呢?下面介绍一个方法,不必去记忆具体的字符串:
跳转到当前类编译后的字节码文件.class
所在目录,在命令行中输入javap -s -p 类名.class
,比如我输入javap -s -p NativeHelper.class
就会自动打印出诸如
public void onGetSize(int);
descriptor: (I)V
public native void getStringSizeWithCallBack(java.lang.String);
descriptor: (Ljava/lang/String;)V
public native int getStringSizeNow(java.lang.String);
descriptor: (Ljava/lang/String;)I
将需要回调的Java方法的descriptor
后面部分作为字符串填入C++函数中即可。
我在native层 封装了一个C++类,该类模拟多线程环境下回调Java方法。
首先我自己封装了一个线程类,想了解可以查看我以前的博客。链接
然后对Java,我在Native层封账了C++类:
#pragma once
#include
#include
#include
#include "utils_thread.h"
class NativeProcessor final {
public:
static NativeProcessor &GetInstance();
~NativeProcessor() = default;
private:
class TaskObject : public Task {
public:
TaskObject(NativeProcessor *const observer)
: Task(true), m_observer{observer} {}
~TaskObject() override { stop(); }
bool ThreadProcess() override;
private:
NativeProcessor *m_observer;
};
class MessageQueue final {
public:
MessageQueue() = default;
~MessageQueue() = default;
void Pop(std::string &);
void Push(std::string &);
private:
MessageQueue(MessageQueue &&) = delete;
MessageQueue(const MessageQueue &&) = delete;
MessageQueue &operator=(MessageQueue &&) = delete;
MessageQueue &operator=(const MessageQueue &) = delete;
private:
std::mutex m_procLock;
std::condition_variable m_procCond;
std::queue<std::string> m_queue;
};
private:
NativeProcessor() : m_task{new TaskObject(this)} {}
NativeProcessor(NativeProcessor &&) = delete;
NativeProcessor(const NativeProcessor &&) = delete;
NativeProcessor &operator=(NativeProcessor &&) = delete;
NativeProcessor &operator=(const NativeProcessor &) = delete;
void CallBackToJava(std::string const &);
public:
int Init(JNIEnv *, jobject);
int Recycle() const;
void Push(std::string &);
private:
MessageQueue m_msgQueue; // 消息队列
std::unique_ptr<Task> m_task; // 任务对象
JavaVM *m_jvm; // Java虚拟机对象
jobject m_jobj; // Java类对象
jclass m_jclz; // 字节码对象
jmethodID m_onCallBack;
std::string_view m_onCallBackMethod = "onCallBack";
};
不必关注里面多线程是怎样实现的,以及消息队列怎么实现的,甚至用到了哪几种设计模式。这里只讨论Java和C++之间的互相调用链路是怎么实现的。
#include
#include "native_process.h"
#include "utils_log.h"
// 此函数位于Java线程
NativeProcessor &NativeProcessor::GetInstance() {
static NativeProcessor instance;
return instance;
}
// 此函数位于Java的MainActivity线程
int NativeProcessor::Init(JNIEnv *env, jobject object) {
if (nullptr != env and nullptr != object) {
// 初始化JavaVM对象,全局唯一
env->GetJavaVM(std::addressof(m_jvm));
m_jobj = env->NewGlobalRef(object);
m_jclz = env->GetObjectClass(m_jobj);
m_onCallBack = env->GetMethodID(m_jclz, m_onCallBackMethod.data(),
"([ILjava/lang/String;)V");
// 启动C++线程
m_task->start();
return 0;
}
return -1;
}
// 此函数位于C++创建的线程
void NativeProcessor::CallBackToJava(std::string const &msg) {
// Java虚拟机对象不允许线程共享,当前线程获取,通过反射回调Java方法
JNIEnv *env = nullptr;
m_jvm->AttachCurrentThread(std::addressof(env), nullptr);
jstring string = env->NewStringUTF(msg.data());
jintArray array = env->NewIntArray(10);
int array_[9]{0, 1, 2, 3, 4, 5, 6, 7, 8};
env->SetIntArrayRegion(array, 0, 10, array_);
// 回调方法
env->CallVoidMethod(m_jobj, m_onCallBack, array, string);
// 在当前线程释放虚拟机对象
m_jvm->DetachCurrentThread();
}
// 此函数位于Java线程
int NativeProcessor::Recycle() const {
m_task->stop();
return 0;
}
// 此函数位于Java线程
void NativeProcessor::Push(std::string &msg) {
m_msgQueue.Push(msg);
}
// C++线程处理函数
bool NativeProcessor::TaskObject::ThreadProcess() {
// 取消息
std::string msg{};
m_observer->m_msgQueue.Pop(msg);
// 处理消息
msg += " !";
// 回调
m_observer->CallBackToJava(msg);
return true;
}
void NativeProcessor::MessageQueue::Pop(std::string &msg) {
std::unique_lock<std::mutex> lock(m_procLock);
m_procCond.wait(lock, [this] { return !m_queue.empty(); });
msg = std::move(m_queue.front());
m_queue.pop();
}
void NativeProcessor::MessageQueue::Push(std::string &msg) {
std::unique_lock<std::mutex> lock(m_procLock);
if (m_queue.size() > 20) {
lock.unlock();
m_procCond.notify_all();
return;
}
m_queue.push(msg);
m_procCond.notify_one();
}
然后在Java类中新增以下方法:
public void onCallBack(int[] array, String string) {
for (int j : array) {
Log.v(TAG, "" + j);
}
Log.v(TAG, string);
}
public native void initNative();
public native void reset();
public native void pushStringToCpp(String msg);
我想C++ 回调Java方法的时候,传入多个参数,分别为整型数组和字符串
对应生成native方法:
#include "native_process.h"
extern "C"
JNIEXPORT void JNICALL
Java_com_nativer_myapplication_NativeHelper_pushStringToCpp(JNIEnv *env, jobject,
jstring msg) {
std::string msgData = JavaStringToCppString(env, msg);
NativeProcessor::GetInstance().Push(msgData);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_nativer_myapplication_NativeHelper_initNative(JNIEnv *env, jobject thiz) {
NativeProcessor::GetInstance().Init(env, thiz);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_nativer_myapplication_NativeHelper_reset(JNIEnv *, jobject) {
NativeProcessor::GetInstance().Recycle();
}
其中用到了我C++类,于是添加上对应头文件
然后我在Java最上层业务的类中改一下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NativeHelper nativeHelper = new NativeHelper();
//Log.v(TAG, "Get:" + nativeHelper.getStringSizeNow("fuck you"));
// nativeHelper.getStringSizeWithCallBack("Hello");
nativeHelper.initNative();
Thread th = new Thread(() -> {
for (int i = 0; i < 10; i++) {
nativeHelper.pushStringToCpp("fuck you");
}
});
th.start();
}
private final String TAG = "MainActivity";
}
新创建一个去模拟不断给native层生产消息的过程,native起了一个线程去,消息队列会通知该线程去取数据来处理,之后native C++会给Java回调
尤其要注意我的C++中注释,以及结合第三个例子开头罗列出来的条款,才能实现在多线程条件下,对Java类方法进行正常回调。