From 86e28d1e426945f8a561172e71a982b0786b28e3 Mon Sep 17 00:00:00 2001 From: hogliux Date: Wed, 12 Apr 2017 18:13:16 +0100 Subject: [PATCH] Restructured android threading code to re-use threading code in juce_posix_Shared.h --- .../native/java/JuceAppActivity.java | 28 --- .../native/juce_android_JNIHelpers.h | 15 -- .../native/juce_android_SystemStats.cpp | 91 ++++++- .../juce_core/native/juce_android_Threads.cpp | 231 ------------------ .../juce_core/native/juce_posix_SharedCode.h | 29 ++- 5 files changed, 102 insertions(+), 292 deletions(-) diff --git a/modules/juce_core/native/java/JuceAppActivity.java b/modules/juce_core/native/java/JuceAppActivity.java index 92450400d0..902d019241 100644 --- a/modules/juce_core/native/java/JuceAppActivity.java +++ b/modules/juce_core/native/java/JuceAppActivity.java @@ -1211,36 +1211,8 @@ public class JuceAppActivity extends Activity return null; } - public final int setCurrentThreadPriority (int priority) - { - android.os.Process.setThreadPriority (android.os.Process.myTid(), priority); - return android.os.Process.getThreadPriority (android.os.Process.myTid()); - } - public final boolean hasSystemFeature (String property) { return getPackageManager().hasSystemFeature (property); } - - private static class JuceThread extends Thread - { - public JuceThread (long host, String threadName, long threadStackSize) - { - super (null, null, threadName, threadStackSize); - _this = host; - } - - public void run() - { - runThread(_this); - } - - private native void runThread (long host); - private long _this; - } - - public final Thread createNewThread(long host, String threadName, long threadStackSize) - { - return new JuceThread(host, threadName, threadStackSize); - } } diff --git a/modules/juce_core/native/juce_android_JNIHelpers.h b/modules/juce_core/native/juce_android_JNIHelpers.h index dc36557667..6ae8471a77 100644 --- a/modules/juce_core/native/juce_android_JNIHelpers.h +++ b/modules/juce_core/native/juce_android_JNIHelpers.h @@ -293,9 +293,7 @@ extern AndroidSystem android; METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ METHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ - METHOD (setCurrentThreadPriority, "setCurrentThreadPriority", "(I)I") \ METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ - METHOD (createNewThread, "createNewThread", "(JLjava/lang/String;J)Ljava/lang/Thread;") \ METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ @@ -329,19 +327,6 @@ DECLARE_JNI_CLASS (Paint, "android/graphics/Paint"); DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); #undef JNI_CLASS_MEMBERS -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (start, "start", "()V") \ - METHOD (stop, "stop", "()V") \ - METHOD (setName, "setName", "(Ljava/lang/String;)V") \ - METHOD (getName, "getName", "()Ljava/lang/String;") \ - METHOD (getId, "getId", "()J") \ - STATICMETHOD (currentThread, "currentThread", "()Ljava/lang/Thread;") \ - METHOD (setPriority, "setPriority", "(I)V") \ - -DECLARE_JNI_CLASS (JuceThread, "java/lang/Thread"); -#undef JNI_CLASS_MEMBERS - //============================================================================== #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ METHOD (constructor, "", "(IIII)V") \ diff --git a/modules/juce_core/native/juce_android_SystemStats.cpp b/modules/juce_core/native/juce_android_SystemStats.cpp index 5b40772262..db405c6bd6 100644 --- a/modules/juce_core/native/juce_android_SystemStats.cpp +++ b/modules/juce_core/native/juce_android_SystemStats.cpp @@ -100,23 +100,90 @@ jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, c } //============================================================================== -ThreadLocalValue androidJNIEnv; +JavaVM* androidJNIJavaVM = nullptr; -JNIEnv* getEnv() noexcept +class JniEnvThreadHolder { - JNIEnv* env = androidJNIEnv.get(); - jassert (env != nullptr); +public: + static JniEnvThreadHolder& getInstance() noexcept + { + // You cann only use JNI functions AFTER JNI_OnLoad was called + jassert (androidJNIJavaVM != nullptr); + + try + { + if (instance == nullptr) + instance = new JniEnvThreadHolder; + } + catch (...) + { + jassertfalse; + std::terminate(); + } + + return *instance; + } - return env; -} + static JNIEnv* getEnv() + { + JNIEnv* env = reinterpret_cast (pthread_getspecific (getInstance().threadKey)); -void setEnv (JNIEnv* env) noexcept -{ - androidJNIEnv.get() = env; -} + // You are trying to use a JUCE function on a thread that was not created by JUCE. + // You need to first call setEnv on this thread before using JUCE + jassert (env != nullptr); + + return env; + } + + static void setEnv (JNIEnv* env) + { + // env must not be a nullptr + jassert (env != nullptr); + + #if JUCE_DEBUG + JNIEnv* oldenv = reinterpret_cast (pthread_getspecific (getInstance().threadKey)); + + // This thread is already attached to the JavaVM and you trying to attach + // it to a different instance of the VM. + jassert (oldenv == nullptr || oldenv == env); + #endif + + pthread_setspecific (getInstance().threadKey, env); + } -extern "C" jint JNI_OnLoad (JavaVM*, void*) +private: + pthread_key_t threadKey; + + static void threadDetach (void* p) + { + if (JNIEnv* env = reinterpret_cast (p)) + { + ignoreUnused (env); + + androidJNIJavaVM->DetachCurrentThread(); + } + } + + JniEnvThreadHolder() + { + pthread_key_create (&threadKey, threadDetach); + } + + static JniEnvThreadHolder* instance; +}; + +JniEnvThreadHolder* JniEnvThreadHolder::instance = nullptr; + +//============================================================================== +JNIEnv* getEnv() noexcept { return JniEnvThreadHolder::getEnv(); } +void setEnv (JNIEnv* env) noexcept { JniEnvThreadHolder::setEnv (env); } + +extern "C" jint JNI_OnLoad (JavaVM* vm, void*) { + // Huh? JNI_OnLoad was called two times! + jassert (androidJNIJavaVM == nullptr); + + androidJNIJavaVM = vm; return JNI_VERSION_1_2; } @@ -127,6 +194,8 @@ AndroidSystem::AndroidSystem() : screenWidth (0), screenHeight (0), dpi (160) void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring dataDir) { + setEnv (env); + screenWidth = screenHeight = 0; dpi = 160; JNIClassBase::initialiseAllClasses (env); diff --git a/modules/juce_core/native/juce_android_Threads.cpp b/modules/juce_core/native/juce_android_Threads.cpp index 692e73d429..81e8b2eead 100644 --- a/modules/juce_core/native/juce_android_Threads.cpp +++ b/modules/juce_core/native/juce_android_Threads.cpp @@ -78,234 +78,3 @@ JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} - -struct AndroidThreadData -{ - AndroidThreadData (Thread* thread) noexcept - : owner (thread), tId (0) - { - } - - Thread* owner; - Thread::ThreadID tId; - WaitableEvent eventSet, eventGet; -}; - -void JUCE_API juce_threadEntryPoint (void*); - -void* threadEntryProc (AndroidThreadData* priv) -{ - priv->tId = (Thread::ThreadID) pthread_self(); - priv->eventSet.signal(); - priv->eventGet.wait (-1); - - juce_threadEntryPoint (priv->owner); - - return nullptr; -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceThread), runThread, - void, (JNIEnv* env, jobject /*device*/, jlong host)) -{ - // This thread does not have a JNIEnv assigned to it yet. So assign it now. - setEnv (env); - - if (AndroidThreadData* thread = reinterpret_cast (host)) - threadEntryProc (thread); -} - -void Thread::launchThread() -{ - threadHandle = 0; - - ScopedPointer threadPrivateData = new AndroidThreadData (this); - const LocalRef jName (javaString (threadName)); - - jobject juceNewThread = android.activity.callObjectMethod (JuceAppActivity.createNewThread, - (jlong) threadPrivateData.get(), - jName.get(), (jlong) threadStackSize); - - if (jobject juceThread = getEnv()->NewGlobalRef (juceNewThread)) - { - AndroidThreadData* priv = threadPrivateData.release(); - - threadHandle = (void*) juceThread; - getEnv()->CallVoidMethod (juceThread, JuceThread.start); - - priv->eventSet.wait (-1); - threadId = priv->tId; - priv->eventGet.signal(); - } -} - -void Thread::closeThreadHandle() -{ - if (threadHandle != 0) - { - jobject juceThread = reinterpret_cast (threadHandle); - getEnv()->DeleteGlobalRef (juceThread); - threadHandle = 0; - } - - threadId = 0; -} - -void Thread::killThread() -{ - if (threadHandle != 0) - { - jobject juceThread = reinterpret_cast (threadHandle); - getEnv()->CallVoidMethod (juceThread, JuceThread.stop); - } -} - -void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) -{ - LocalRef juceThread (getEnv()->CallStaticObjectMethod (JuceThread, JuceThread.currentThread)); - - if (jobject t = juceThread.get()) - getEnv()->CallVoidMethod (t, JuceThread.setName, javaString (name).get()); -} - -bool Thread::setThreadPriority (void* handle, int priority) -{ - if (handle == nullptr) - { - LocalRef juceThread (getEnv()->CallStaticObjectMethod (JuceThread, JuceThread.currentThread)); - - if (jobject t = juceThread.get()) - return setThreadPriority (t, priority); - - return false; - } - - jobject juceThread = reinterpret_cast (handle); - - const int minPriority = 1; - const int maxPriority = 10; - - jint javaPriority = ((maxPriority - minPriority) * priority) / 10 + minPriority; - - getEnv()->CallVoidMethod (juceThread, JuceThread.setPriority, javaPriority); - - return true; -} - -//============================================================================== -struct HighResolutionTimer::Pimpl -{ - struct HighResolutionThread : public Thread - { - HighResolutionThread (HighResolutionTimer::Pimpl& parent) - : Thread ("High Resolution Timer"), pimpl (parent) - { - startThread(); - } - - void run() override - { - pimpl.timerThread(); - } - - private: - HighResolutionTimer::Pimpl& pimpl; - }; - - //============================================================================== - Pimpl (HighResolutionTimer& t) : owner (t) {} - - ~Pimpl() - { - stop(); - } - - void start (int newPeriod) - { - if (periodMs != newPeriod) - { - if (thread.get() == nullptr - || thread->getThreadId() != Thread::getCurrentThreadId() - || thread->threadShouldExit()) - { - stop(); - - periodMs = newPeriod; - - thread = new HighResolutionThread (*this); - } - else - { - periodMs = newPeriod; - } - } - } - - void stop() - { - if (thread.get() != nullptr) - { - thread->signalThreadShouldExit(); - - if (thread->getThreadId() != Thread::getCurrentThreadId()) - { - thread->waitForThreadToExit (-1); - thread = nullptr; - } - } - } - - HighResolutionTimer& owner; - int volatile periodMs; - -private: - ScopedPointer thread; - - void timerThread() - { - jassert (thread.get() != nullptr); - - int lastPeriod = periodMs; - Clock clock (lastPeriod); - - while (! thread->threadShouldExit()) - { - clock.wait(); - owner.hiResTimerCallback(); - - if (lastPeriod != periodMs) - { - lastPeriod = periodMs; - clock = Clock (lastPeriod); - } - } - - periodMs = 0; - } - - struct Clock - { - Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) - { - } - - void wait() noexcept - { - struct timespec t; - t.tv_sec = (time_t) (delta / 1000000000); - t.tv_nsec = (long) (delta % 1000000000); - nanosleep (&t, nullptr); - } - - uint64 delta; - }; - - static bool setThreadToRealtime (pthread_t thread, uint64 periodMs) - { - ignoreUnused (periodMs); - struct sched_param param; - param.sched_priority = sched_get_priority_max (SCHED_RR); - return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0; - } - - JUCE_DECLARE_NON_COPYABLE (Pimpl) -}; diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h index e46defebcf..b32407e7b2 100644 --- a/modules/juce_core/native/juce_posix_SharedCode.h +++ b/modules/juce_core/native/juce_posix_SharedCode.h @@ -906,12 +906,25 @@ void InterProcessLock::exit() } //============================================================================== -#if ! JUCE_ANDROID void JUCE_API juce_threadEntryPoint (void*); +#if JUCE_ANDROID +extern JavaVM* androidJNIJavaVM; +#endif + extern "C" void* threadEntryProc (void*); extern "C" void* threadEntryProc (void* userData) { + #if JUCE_ANDROID + // JNI_OnLoad was not called - make sure you load the JUCE shared library + // using System.load inside of Java + jassert (androidJNIJavaVM != nullptr); + + JNIEnv* env; + androidJNIJavaVM->AttachCurrentThread (&env, nullptr); + setEnv (env); + #endif + JUCE_AUTORELEASEPOOL { juce_threadEntryPoint (userData); @@ -999,7 +1012,6 @@ bool Thread::setThreadPriority (void* handle, int priority) param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; } -#endif Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() { @@ -1233,7 +1245,6 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) } //============================================================================== -#if ! JUCE_ANDROID struct HighResolutionTimer::Pimpl { Pimpl (HighResolutionTimer& t) : owner (t), thread (0), destroyThread (false), isRunning (false) @@ -1241,7 +1252,7 @@ struct HighResolutionTimer::Pimpl pthread_condattr_t attr; pthread_condattr_init (&attr); - #if ! (JUCE_MAC || JUCE_IOS) + #if JUCE_LINUX || (JUCE_ANDROID && defined(__ANDROID_API__) && __ANDROID_API__ >= 21) pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); #endif @@ -1320,7 +1331,13 @@ private: static void* timerThread (void* param) { #if JUCE_ANDROID - const AndroidThreadScope androidEnv; + // JNI_OnLoad was not called - make sure you load the JUCE shared library + // using System.load inside of Java + jassert (androidJNIJavaVM != nullptr); + + JNIEnv* env; + androidJNIJavaVM->AttachCurrentThread (&env, nullptr); + setEnv (env); #else int dummy; pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &dummy); @@ -1469,5 +1486,3 @@ private: JUCE_DECLARE_NON_COPYABLE (Pimpl) }; - -#endif