diff --git a/modules/juce_core/native/juce_JNIHelpers_android.cpp b/modules/juce_core/native/juce_JNIHelpers_android.cpp index c7f09dbee3..b929a07345 100644 --- a/modules/juce_core/native/juce_JNIHelpers_android.cpp +++ b/modules/juce_core/native/juce_JNIHelpers_android.cpp @@ -248,7 +248,7 @@ struct SystemJavaClassComparator //============================================================================== JNIClassBase::JNIClassBase (const char* cp, int classMinSDK, const uint8* bc, size_t n) - : classPath (cp), byteCode (bc), byteCodeSize (n), minSDK (classMinSDK), classRef (nullptr) + : classPath (cp), byteCode (bc), byteCodeSize (n), minSDK (classMinSDK) { SystemJavaClassComparator comparator; @@ -375,7 +375,7 @@ void JNIClassBase::initialise (JNIEnv* env, jobject context) } if (classRef == nullptr) - classRef = (jclass) env->NewGlobalRef (LocalRef (env->FindClass (classPath))); + classRef = GlobalRefImpl { LocalRef { env->FindClass (classPath) } }; jassert (classRef != nullptr); initialiseFields (env); @@ -388,23 +388,22 @@ void JNIClassBase::tryLoadingClassWithClassLoader (JNIEnv* env, jobject classLoa // Android SDK <= 19 has a bug where the class loader might throw an exception but still return // a non-nullptr. So don't assign the result of this call to a jobject just yet... - auto classObj = env->CallObjectMethod (classLoader, JavaClassLoader.loadClass, classNameAndPackage.get(), (jboolean) true); + LocalRef classObj { (jclass) env->CallObjectMethod (classLoader, JavaClassLoader.loadClass, classNameAndPackage.get(), (jboolean) true) }; if (jthrowable exception = env->ExceptionOccurred()) { env->ExceptionClear(); - classObj = nullptr; + classObj = {}; } // later versions of Android don't throw at all, so re-check the object if (classObj != nullptr) - classRef = (jclass) env->NewGlobalRef (LocalRef (classObj)); + classRef = GlobalRefImpl { classObj }; } void JNIClassBase::release (JNIEnv* env) { - if (classRef != nullptr) - env->DeleteGlobalRef (classRef); + classRef.clear (env); } void JNIClassBase::initialiseAllClasses (JNIEnv* env, jobject context) diff --git a/modules/juce_core/native/juce_JNIHelpers_android.h b/modules/juce_core/native/juce_JNIHelpers_android.h index e4ab1591d3..09f36cfb0e 100644 --- a/modules/juce_core/native/juce_JNIHelpers_android.h +++ b/modules/juce_core/native/juce_JNIHelpers_android.h @@ -270,7 +270,7 @@ private: size_t byteCodeSize; int minSDK; - jclass classRef = nullptr; + GlobalRefImpl classRef; static Array& getClasses(); void initialise (JNIEnv*, jobject context); diff --git a/modules/juce_core/native/juce_Threads_android.cpp b/modules/juce_core/native/juce_Threads_android.cpp index 9d9aaf58dd..370e5eb003 100644 --- a/modules/juce_core/native/juce_Threads_android.cpp +++ b/modules/juce_core/native/juce_Threads_android.cpp @@ -48,7 +48,7 @@ DECLARE_JNI_CLASS (AndroidResolveInfo, "android/content/pm/ResolveInfo") //============================================================================== JavaVM* androidJNIJavaVM = nullptr; -jobject androidApkContext = nullptr; +GlobalRef androidApkContext; //============================================================================== JNIEnv* getEnv() noexcept @@ -105,6 +105,56 @@ extern "C" jint JNIEXPORT JNI_OnLoad (JavaVM* vm, void*) return JNI_VERSION_1_2; } +//============================================================================== +class WeakGlobalRef +{ +public: + WeakGlobalRef() = default; + explicit WeakGlobalRef (jobject o) : WeakGlobalRef (o, getEnv()) {} + WeakGlobalRef (jobject o, JNIEnv* env) : obj (retain (o, env)) {} + + WeakGlobalRef (const WeakGlobalRef& o) : obj (retain (o.obj)) {} + WeakGlobalRef (WeakGlobalRef&& o) noexcept : obj (std::exchange (o.obj, nullptr)) {} + + WeakGlobalRef& operator= (const WeakGlobalRef& o) + { + WeakGlobalRef { o }.swap (*this); + return *this; + } + + WeakGlobalRef& operator= (WeakGlobalRef&& o) noexcept + { + WeakGlobalRef { std::move (o) }.swap (*this); + return *this; + } + + ~WeakGlobalRef() noexcept { clear(); } + + void clear (JNIEnv* env = getEnv()) + { + if (obj != nullptr) + env->DeleteWeakGlobalRef (obj); + } + + auto lock (JNIEnv* env = getEnv()) const + { + return LocalRef { env->NewLocalRef (obj) }; + } + +private: + void swap (WeakGlobalRef& other) noexcept + { + std::swap (other.obj, obj); + } + + static jweak retain (jweak o, JNIEnv* env = getEnv()) + { + return env->NewWeakGlobalRef (o); + } + + jweak obj = nullptr; +}; + //============================================================================== class JuceActivityWatcher final : public ActivityLifecycleCallbacks { @@ -122,21 +172,16 @@ public: ScopedLock lock (currentActivityLock); - if (currentActivity != nullptr) + if (auto locked = currentActivity.lock (env)) { - // see Clarification June 2001 in JNI reference for why this is - // necessary - LocalRef localStorage (env->NewLocalRef (currentActivity)); - - if (env->IsSameObject (localStorage.get(), activity) != 0) + if (env->IsSameObject (locked, activity) != 0) return; - env->DeleteWeakGlobalRef (currentActivity); - currentActivity = nullptr; + currentActivity = {}; } if (activity != nullptr) - currentActivity = env->NewWeakGlobalRef (activity); + currentActivity = WeakGlobalRef { activity }; } void onActivityStopped (jobject activity) override @@ -145,16 +190,15 @@ public: ScopedLock lock (currentActivityLock); - if (currentActivity != nullptr) + if (auto locked = currentActivity.lock (env)) { // important that the comparison happens in this order // to avoid race condition where the weak reference becomes null // just after the first check - if (env->IsSameObject (currentActivity, activity) != 0 - || env->IsSameObject (currentActivity, nullptr) != 0) + if (env->IsSameObject (locked, activity) != 0 + || env->IsSameObject (locked, nullptr) != 0) { - env->DeleteWeakGlobalRef (currentActivity); - currentActivity = nullptr; + currentActivity = {}; } } } @@ -162,13 +206,13 @@ public: LocalRef getCurrent() { ScopedLock lock (currentActivityLock); - return LocalRef (getEnv()->NewLocalRef (currentActivity)); + return currentActivity.lock(); } LocalRef getMain() { ScopedLock lock (currentActivityLock); - return LocalRef (getEnv()->NewLocalRef (mainActivity)); + return mainActivity.lock(); } static JuceActivityWatcher& getInstance() @@ -184,16 +228,13 @@ private: ScopedLock lock (currentActivityLock); - if (mainActivity != nullptr) + if (auto locked = mainActivity.lock (env)) { - if (env->IsSameObject (mainActivity, nullptr) != 0) - { - env->DeleteWeakGlobalRef (mainActivity); - mainActivity = nullptr; - } + if (env->IsSameObject (locked, nullptr) != 0) + mainActivity = {}; } - if (mainActivity == nullptr) + if (mainActivity.lock() == nullptr) { auto appContext = getAppContext(); auto mainActivityPath = getMainActivityClassPath(); @@ -206,7 +247,7 @@ private: // This may be problematic for apps which use several activities with the same type. We just // assume that the very first activity of this type is the main one if (activityPath == mainActivityPath) - mainActivity = env->NewWeakGlobalRef (context); + mainActivity = WeakGlobalRef { context }; } } } @@ -250,8 +291,7 @@ private: } CriticalSection currentActivityLock; - jweak currentActivity = nullptr; - jweak mainActivity = nullptr; + WeakGlobalRef currentActivity, mainActivity; ActivityLifecycleCallbackForwarder forwarder { GlobalRef { getAppContext() }, this }; }; @@ -287,7 +327,7 @@ void Thread::initialiseJUCE (void* jniEnv, void* context) firstCall = false; // if we ever support unloading then this should probably be a weak reference - androidApkContext = env->NewGlobalRef (static_cast (context)); + androidApkContext = GlobalRef { LocalRef { (jobject) context } }; JuceActivityWatcher::getInstance(); #if JUCE_MODULE_AVAILABLE_juce_events && JUCE_ANDROID