Android: Separate JNI initialization from JNI_OnLoad

Currently, we create references to Java classes and objects
in JNI_OnLoad. However, that is done only once, when the
Android QPA plugin is loaded, and in case we would like to stop
Qt entirely and then start it again, we want
to reinitialize all these things. Move these initializations to
a separate method, and call it before starting the Qt runtime and
app.

Remove the synchronization block guarder by m_mainActivityMutex
in QtNative.startApplication(), as it calls startQtAndroidPlugin()
which now will call activity() if the JNI needs to be
reinitialized, leading to a deadlock as that method is guarded
with the same object.

Task-number: QTBUG-130610
Pick-to: 6.8 6.9
Change-Id: I826dfb9b032a6c615f2408d484d1f4f0ea528d02
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
Reviewed-by: Petri Virkkunen <petri.virkkunen@qt.io>
This commit is contained in:
Tinja Paavoseppä 2024-11-26 12:40:11 +02:00
parent 4f05eac0a8
commit 9f8e49360c
2 changed files with 106 additions and 60 deletions

View File

@ -339,17 +339,15 @@ public class QtNative
static void startApplication(String params, String mainLib)
{
synchronized (m_mainActivityMutex) {
m_qtThread.run(() -> {
final String qtParams = mainLib + " " + params;
if (!startQtAndroidPlugin(qtParams))
Log.e(QtTAG, "An error occurred while starting the Qt Android plugin");
});
m_qtThread.post(QtNative::startQtApplication);
waitForServiceSetup();
m_stateDetails.isStarted = true;
notifyAppStateDetailsChanged(m_stateDetails);
}
m_qtThread.run(() -> {
final String qtParams = mainLib + " " + params;
if (!startQtAndroidPlugin(qtParams))
Log.e(QtTAG, "An error occurred while starting the Qt Android plugin");
});
m_qtThread.post(QtNative::startQtApplication);
waitForServiceSetup();
m_stateDetails.isStarted = true;
notifyAppStateDetailsChanged(m_stateDetails);
}
static void quitApp()

View File

@ -362,9 +362,24 @@ namespace QtAndroid
} // namespace QtAndroid
static bool initJavaReferences(QJniEnvironment &env);
static jboolean startQtAndroidPlugin(JNIEnv *env, jobject /*object*/, jstring paramsString)
{
Q_UNUSED(env)
// Init all the Java refs, if they haven't already been initialized. They get initialized
// when the library is loaded, but in case Qt is terminated, they are cleared, and in case
// Qt is then started again JNI_OnLoad will not be called again, since the library is already
// loaded - in that case we need to init again here, hence the check.
// TODO QTBUG-130614 QtCore also inits some Java references in qjnihelpers - we probably
// want to reset those, too.
QJniEnvironment qEnv;
if (!qEnv.isValid()) {
__android_log_print(ANDROID_LOG_FATAL, "Qt", "Failed to initialize the JNI Environment");
return JNI_ERR;
}
if (!initJavaReferences(qEnv))
return false;
m_androidPlatformIntegration = nullptr;
m_androidAssetsFileEngineHandler = new AndroidAssetsFileEngineHandler();
@ -486,6 +501,46 @@ static void quitQtAndroidPlugin(JNIEnv *env, jclass /*clazz*/)
m_androidApkFileEngineHandler = nullptr;
}
static void clearJavaReferences(JNIEnv *env)
{
if (m_applicationClass) {
env->DeleteGlobalRef(m_applicationClass);
m_applicationClass = nullptr;
}
if (m_resourcesObj) {
env->DeleteGlobalRef(m_resourcesObj);
m_resourcesObj = nullptr;
}
if (m_bitmapClass) {
env->DeleteGlobalRef(m_bitmapClass);
m_bitmapClass = nullptr;
}
if (m_ARGB_8888_BitmapConfigValue) {
env->DeleteGlobalRef(m_ARGB_8888_BitmapConfigValue);
m_ARGB_8888_BitmapConfigValue = nullptr;
}
if (m_RGB_565_BitmapConfigValue) {
env->DeleteGlobalRef(m_RGB_565_BitmapConfigValue);
m_RGB_565_BitmapConfigValue = nullptr;
}
if (m_bitmapDrawableClass) {
env->DeleteGlobalRef(m_bitmapDrawableClass);
m_bitmapDrawableClass = nullptr;
}
if (m_assets) {
env->DeleteGlobalRef(m_assets);
m_assets = nullptr;
}
if (m_qtActivityClass) {
env->DeleteGlobalRef(m_qtActivityClass);
m_qtActivityClass = nullptr;
}
if (m_qtServiceClass) {
env->DeleteGlobalRef(m_qtServiceClass);
m_qtServiceClass = nullptr;
}
}
static void terminateQt(JNIEnv *env, jclass /*clazz*/)
{
// QAndroidEventDispatcherStopper is stopped when the user uses the task manager to kill the application
@ -500,23 +555,8 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/)
sem_destroy(&m_terminateSemaphore);
env->DeleteGlobalRef(m_applicationClass);
if (m_resourcesObj)
env->DeleteGlobalRef(m_resourcesObj);
if (m_bitmapClass)
env->DeleteGlobalRef(m_bitmapClass);
if (m_ARGB_8888_BitmapConfigValue)
env->DeleteGlobalRef(m_ARGB_8888_BitmapConfigValue);
if (m_RGB_565_BitmapConfigValue)
env->DeleteGlobalRef(m_RGB_565_BitmapConfigValue);
if (m_bitmapDrawableClass)
env->DeleteGlobalRef(m_bitmapDrawableClass);
if (m_assets)
env->DeleteGlobalRef(m_assets);
if (m_qtActivityClass)
env->DeleteGlobalRef(m_qtActivityClass);
if (m_qtServiceClass)
env->DeleteGlobalRef(m_qtServiceClass);
clearJavaReferences(env);
m_androidPlatformIntegration = nullptr;
delete m_androidAssetsFileEngineHandler;
m_androidAssetsFileEngineHandler = nullptr;
@ -752,17 +792,9 @@ Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtDisplayManager
static bool registerNatives(QJniEnvironment &env)
{
jclass clazz;
FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtNative");
m_applicationClass = static_cast<jclass>(env->NewGlobalRef(clazz));
if (!env.registerNativeMethods(m_applicationClass,
methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
return false;
}
bool success = env.registerNativeMethods(
bool success = env.registerNativeMethods(m_applicationClass,
methods, sizeof(methods) / sizeof(methods[0]));
success &= env.registerNativeMethods(
QtJniTypes::Traits<QtJniTypes::QtDisplayManager>::className(),
{
Q_JNI_NATIVE_METHOD(setDisplayMetrics),
@ -774,13 +806,34 @@ static bool registerNatives(QJniEnvironment &env)
Q_JNI_NATIVE_METHOD(handleUiDarkModeChanged)
});
if (!success) {
qCritical() << "QtDisplayManager: registerNativeMethods() failed";
return JNI_FALSE;
}
success = success
&& QtAndroidInput::registerNatives(env)
&& QtAndroidMenu::registerNatives(env)
&& QtAndroidAccessibility::registerNatives(env)
&& QtAndroidDialogHelpers::registerNatives(env)
&& QAndroidPlatformClipboard::registerNatives(env)
&& QAndroidPlatformWindow::registerNatives(env)
&& QtAndroidWindowEmbedding::registerNatives(env)
&& AndroidBackendRegister::registerNatives()
&& QAndroidModelIndexProxy::registerNatives(env)
&& QAndroidItemModelProxy::registerAbstractNatives(env)
&& QAndroidItemModelProxy::registerProxyNatives(env);
return success;
}
static bool initJavaReferences(QJniEnvironment &env)
{
if (m_applicationClass)
return true;
jclass clazz;
FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtNative");
m_applicationClass = static_cast<jclass>(env->NewGlobalRef(clazz));
jmethodID methodID;
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "activity", "()Landroid/app/Activity;");
jobject contextObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
if (!contextObject) {
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "service", "()Landroid/app/Service;");
@ -825,6 +878,11 @@ static bool registerNatives(QJniEnvironment &env)
FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtServiceBase");
m_qtServiceClass = static_cast<jclass>(env->NewGlobalRef(clazz));
// The current thread will be the Qt thread, name it accordingly
QThread::currentThread()->setObjectName("QtMainLoopThread");
QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
return true;
}
@ -838,31 +896,21 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM */*vm*/, void */*reserved*/)
initialized = true;
QT_USE_NAMESPACE
QJniEnvironment env;
if (!env.isValid()) {
__android_log_print(ANDROID_LOG_FATAL, "Qt", "Failed to initialize the JNI Environment");
return -1;
return JNI_ERR;
}
if (!registerNatives(env)
|| !QtAndroidInput::registerNatives(env)
|| !QtAndroidMenu::registerNatives(env)
|| !QtAndroidAccessibility::registerNatives(env)
|| !QtAndroidDialogHelpers::registerNatives(env)
|| !QAndroidPlatformClipboard::registerNatives(env)
|| !QAndroidPlatformWindow::registerNatives(env)
|| !QtAndroidWindowEmbedding::registerNatives(env)
|| !AndroidBackendRegister::registerNatives()
|| !QAndroidModelIndexProxy::registerNatives(env)
|| !QAndroidItemModelProxy::registerAbstractNatives(env)
|| !QAndroidItemModelProxy::registerProxyNatives(env)) {
if (!initJavaReferences(env))
return JNI_ERR;
if (!registerNatives(env)) {
__android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
return -1;
return JNI_ERR;
}
QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
// attach qt main thread data to this thread
QThread::currentThread()->setObjectName("QtMainLoopThread");
__android_log_print(ANDROID_LOG_INFO, "Qt", "qt started");
return JNI_VERSION_1_6;
}