Android 拯救JNIEnv的最佳方式是什么*
我和JNI有一个Android项目。在实现侦听器类的CPP文件中,有一个回调x()。调用x()函数时,我想调用java类中的另一个函数。然而,为了调用该java函数,我需要访问JNIEnv* 我知道在回调的同一个cpp文件中,有一个函数:Android 拯救JNIEnv的最佳方式是什么*,android,android-ndk,java-native-interface,jnienv,Android,Android Ndk,Java Native Interface,Jnienv,我和JNI有一个Android项目。在实现侦听器类的CPP文件中,有一个回调x()。调用x()函数时,我想调用java类中的另一个函数。然而,为了调用该java函数,我需要访问JNIEnv* 我知道在回调的同一个cpp文件中,有一个函数: static jboolean init (JNIEnv* env, jobject obj) {...} 调用init(..)时,是否应将JNIEnv*作为成员变量保存在cpp文件中?然后在回调发生时使用它 对不起,我是JNI的初学者 缓存JNIEnv*不
static jboolean init (JNIEnv* env, jobject obj) {...}
调用init(..)
时,是否应将JNIEnv*作为成员变量保存在cpp文件中?然后在回调发生时使用它
对不起,我是JNI的初学者 缓存
JNIEnv*
不是一个特别好的主意,因为您不能跨多个线程使用相同的JNIEnv*
,甚至可能无法将其用于同一线程上的多个本机调用(请参阅)
编写一个函数来获取JNIEnv*
,并在必要时将当前线程附加到VM,这并不太困难:
bool GetJniEnv(JavaVM *vm, JNIEnv **env) {
bool did_attach_thread = false;
*env = nullptr;
// Check if the current thread is attached to the VM
auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (vm->AttachCurrentThread(env, NULL) == JNI_OK) {
did_attach_thread = true;
} else {
// Failed to attach thread. Throw an exception if you want to.
}
} else if (get_env_result == JNI_EVERSION) {
// Unsupported JNI version. Throw an exception if you want to.
}
return did_attach_thread;
}
您使用它的方式是:
JNIEnv *env;
bool did_attach = GetJniEnv(vm, &env);
// Use env...
// ...
if (did_attach) {
vm->DetachCurrentThread();
}
您可以将其封装在一个类中,该类在构建时附加,在破坏时分离,RAII样式:
class ScopedEnv {
public:
ScopedEnv() : attached_to_vm_(false) {
attached_to_vm_ = GetJniEnv(g_vm, &env_); // g_vm is a global
}
ScopedEnv(const ScopedEnv&) = delete;
ScopedEnv& operator=(const ScopedEnv&) = delete;
virtual ~ScopedEnv() {
if (attached_to_vm_) {
g_vm->DetachCurrentThread();
attached_to_vm_ = false;
}
}
JNIEnv *GetEnv() const { return env_; }
private:
bool attached_to_env_;
JNIEnv *env_;
};
// Usage:
{
ScopedEnv scoped_env;
scoped_env.GetEnv()->SomeJniFunction();
}
// scoped_env falls out of scope, the thread is automatically detached if necessary
Edit:有时您可能有一个运行时间很长的本机线程,在多种情况下需要一个
JNIEnv*
。在这种情况下,您可能希望避免不断地将线程连接到JVM或从JVM中分离线程,但仍然需要确保在线程销毁时分离线程
您可以通过只附加一次线程,然后将其保持附加状态,并通过使用pthread\u key\u create
和pthread\u setspecific
设置线程销毁回调来完成此操作,该回调将负责调用DetachCurrentThread
/**
* Get a JNIEnv* valid for this thread, regardless of whether
* we're on a native thread or a Java thread.
* If the calling thread is not currently attached to the JVM
* it will be attached, and then automatically detached when the
* thread is destroyed.
*/
JNIEnv *GetJniEnv() {
JNIEnv *env = nullptr;
// We still call GetEnv first to detect if the thread already
// is attached. This is done to avoid setting up a DetachCurrentThread
// call on a Java thread.
// g_vm is a global.
auto get_env_result = g_vm->GetEnv((void**)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (g_vm->AttachCurrentThread(&env, NULL) == JNI_OK) {
DeferThreadDetach(env);
} else {
// Failed to attach thread. Throw an exception if you want to.
}
} else if (get_env_result == JNI_EVERSION) {
// Unsupported JNI version. Throw an exception if you want to.
}
return env;
}
void DeferThreadDetach(JNIEnv *env) {
static pthread_key_t thread_key;
// Set up a Thread Specific Data key, and a callback that
// will be executed when a thread is destroyed.
// This is only done once, across all threads, and the value
// associated with the key for any given thread will initially
// be NULL.
static auto run_once = [] {
const auto err = pthread_key_create(&thread_key, [] (void *ts_env) {
if (ts_env) {
g_vm->DetachCurrentThread();
}
});
if (err) {
// Failed to create TSD key. Throw an exception if you want to.
}
return 0;
}();
// For the callback to actually be executed when a thread exits
// we need to associate a non-NULL value with the key on that thread.
// We can use the JNIEnv* as that value.
const auto ts_env = pthread_getspecific(thread_key);
if (!ts_env) {
if (pthread_setspecific(thread_key, env)) {
// Failed to set thread-specific value for key. Throw an exception if you want to.
}
}
}
如果
\uuucxa\u thread\u atexit
对您可用,您可能可以使用在其析构函数中调用DetachCurrentThread
的某个thread\u local
对象来完成相同的任务 @Michael很好地概述了如何通过缓存JVM来检索JNI。
对于那些不想使用pTror(或者因为Windows系统上的“不”),而你使用C++ 11或更高的,那么THEADLIX本地存储就是这样。
下面是一个粗略的示例,介绍如何实现一个包装器方法,该方法正确地连接到线程,并在线程退出时自动清理
JNIEnv* JNIThreadHelper::GetJniEnv() {
// This method might have been called from a different thread than the one that created
// this handler. Check to make sure that the JNI is attached and if not attach it to the
// new thread.
// double check it's all ok
int nEnvStat = m_pJvm->GetEnv(reinterpret_cast<void**>(&m_pJniEnv), JNI_VERSION_1_6);
if (nEnvStat == JNI_EDETACHED) {
std::cout << "GetEnv: not attached. Attempting to attach" << std::endl;
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6; // choose your JNI version
args.name = NULL; // you might want to give the java thread a name
args.group = NULL; // you might want to assign the java thread to a ThreadGroup
if (m_pJvm->AttachCurrentThread(&m_pJniEnv, &args) != 0) {
std::cout << "Failed to attach" << std::endl;
return nullptr;
}
thread_local struct DetachJniOnExit {
~DetachJniOnExit() {
m_pJvm->DetachCurrentThread();
}
};
m_bIsAttachedOnAThread = true;
}
else if (nEnvStat == JNI_OK) {
//
}
else if (nEnvStat == JNI_EVERSION) {
std::cout << "GetEnv: version not supported" << std::endl;
return nullptr;
}
return m_pJniEnv;
}
JNIEnv*jnithreadheloper::GetJniEnv(){
//此方法可能是从与创建此方法的线程不同的线程调用的
//检查以确保JNI已连接,如果未连接,则将其连接到
//新线程。
//再检查一遍,一切正常
int nEnvStat=m_pJvm->GetEnv(重新解释cast(&m_pJniEnv),JNI_版本1_6);
if(nEnvStat==JNI_e附加){
std::cout Hi@Michael,感谢您使用TLS在退出时自动分离线程的简洁方法-分离代码不拥有的线程(例如,一些框架线程)也很有用。>缓存JNIEnv*不是一个特别好的主意,[…]甚至可能无法将其用于同一线程上的多个本机调用-我看不到链接文章建议JNI接口指针可能会为给定线程更改“当VM从同一Java线程多次调用本机方法时,它保证将同一接口指针传递给本机方法”。我认为调用API没有什么不同。@DmitryMofeev:“我看不出链接的文章建议JNI接口指针可能会为给定线程更改。这是指谷歌的措辞“如果下一个本机调用发生在同一线程上,它们可能有效”,即它们选择“可能是”而不是“将是”。明白了,谢谢!如果它们的实现不符合规范,那就太奇怪了。无论如何,只使用JavaVM#GetEnv
(如果代码希望附加线程)肯定更可靠或者JavaVM#AttachCurrentThread
来获取有效指针。实际上,当我考虑它时,我无法理解这个短语-如果JNIEnv是通过AttachCurrentThread
获取的(JNIEnv是它的输出参数),则调用者唯一合理的期望是,只要它保持连接状态,调用者就可以在同一线程中使用它(即,直到该线程调用DetachCurrentThread
)。请注意,此问题已有一个可接受的答案。请确保您的答案比此问题中已有的其他答案有所改进。