From 1f0cf1e6e5625587caec3951c9a439ac0b3e3cfb Mon Sep 17 00:00:00 2001 From: Soheil Armin Date: Wed, 7 Aug 2024 14:06:12 +0300 Subject: [PATCH] Android: Fix QtAbstractItemModel proxy threading problem This change sets the thread affinity of the QAndroidItemModelProxy instance to the Qt main thread, enabling its APIs to be called from both the Qt main thread and Android threads. Synchronization between threads is ensured by using the newly introduced QAndroidItemModelProxy::safeCall(), which utilizes QMetaObject::invokeMethod() with the appropriate connection type based on the calling thread. For void functions, Qt::AutoConnection can be used. Functions returning values will either use a Qt::DirectConnection if invoked from the same thread as the QAndroidItemModelProxy instance, or a Qt::BlockingQueuedConnection if called from a different thread. Pick-to: 6.8.0 Fixes: QTBUG-127701 Change-Id: I214bc43d20d8bdf301fc97920493415d503d26d8 Reviewed-by: Assam Boudjelthia (cherry picked from commit 6c30a60a42800dd5d732d2c7018d87e8f3a9c6a0) Reviewed-by: Qt Cherry-pick Bot --- .../android/qandroiditemmodelproxy.cpp | 10 ++++- .../android/qandroiditemmodelproxy_p.h | 41 +++++++++++++++---- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/corelib/platform/android/qandroiditemmodelproxy.cpp b/src/corelib/platform/android/qandroiditemmodelproxy.cpp index 260e131fad1..cba8420dd91 100644 --- a/src/corelib/platform/android/qandroiditemmodelproxy.cpp +++ b/src/corelib/platform/android/qandroiditemmodelproxy.cpp @@ -5,6 +5,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE using namespace QtJniTypes; @@ -131,7 +133,12 @@ QAndroidItemModelProxy::createNativeProxy(QJniObject itemModel) { QAbstractItemModel *nativeProxy = nativeInstance(itemModel); if (!nativeProxy) { + Q_ASSERT(QCoreApplication::instance()); + nativeProxy = new QAndroidItemModelProxy(itemModel); + QThread *qtMainThread = QCoreApplication::instance()->thread(); + if (nativeProxy->thread() != qtMainThread) + nativeProxy->moveToThread(qtMainThread); itemModel.callMethod("setNativeReference", reinterpret_cast(nativeProxy)); connect(nativeProxy, &QAndroidItemModelProxy::destroyed, nativeProxy, [](QObject *obj) { @@ -205,7 +212,8 @@ jobject QAndroidItemModelProxy::jni_createIndex(JNIEnv *env, jobject object, jin { QModelIndex (QAndroidItemModelProxy::*createIndexPtr)(int, int, quintptr) const = &QAndroidItemModelProxy::createIndex; - const QModelIndex index = invokeNativeProxyMethod(env, object, createIndexPtr, row, column, id); + const QModelIndex index = + invokeNativeProxyMethod(env, object, createIndexPtr, row, column, quintptr(id)); return env->NewLocalRef(QAndroidModelIndexProxy::jInstance(index).object()); } diff --git a/src/corelib/platform/android/qandroiditemmodelproxy_p.h b/src/corelib/platform/android/qandroiditemmodelproxy_p.h index 241f4966b13..9f5369af47a 100644 --- a/src/corelib/platform/android/qandroiditemmodelproxy_p.h +++ b/src/corelib/platform/android/qandroiditemmodelproxy_p.h @@ -21,6 +21,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -58,34 +59,58 @@ class Q_CORE_EXPORT QAndroidItemModelProxy : public QAbstractItemModel static QJniObject createProxy(QAbstractItemModel *abstractClass); template - static auto invokeNativeProxyMethod(JNIEnv */*env*/, jobject jvmObject, Func func, Args &&...args) + static auto invokeNativeProxyMethod(JNIEnv * /*env*/, jobject jvmObject, Func &&func, + Args &&...args) { Q_ASSERT(jvmObject); auto model = qobject_cast(nativeInstance(jvmObject)); Q_ASSERT(model); - return std::invoke(func, model, std::forward(args)...); + return safeCall(model, std::forward(func), std::forward(args)...); } template - static auto invokeNativeMethod(JNIEnv */*env*/, jobject jvmObject, Func func, Args &&...args) + static auto invokeNativeMethod(JNIEnv * /*env*/, jobject jvmObject, Func &&func, Args &&...args) { Q_ASSERT(jvmObject); auto model = nativeInstance(jvmObject); Q_ASSERT(model); - return std::invoke(func, model, std::forward(args)...); + return safeCall(model, std::forward(func), std::forward(args)...); } template - static auto invokeNativeImpl(JNIEnv */*env*/, jobject jvmObject, Func1 defaultFunc, Func2 func, - Args &&...args) + static auto invokeNativeImpl(JNIEnv * /*env*/, jobject jvmObject, Func1 &&defaultFunc, + Func2 &&func, Args &&...args) { Q_ASSERT(jvmObject); auto nativeModel = nativeInstance(jvmObject); auto nativeProxyModel = qobject_cast(nativeModel); if (nativeProxyModel) - return std::invoke(defaultFunc, nativeProxyModel, std::forward(args)...); + return safeCall(nativeProxyModel, std::forward(defaultFunc), + std::forward(args)...); else - return std::invoke(func, nativeModel, std::forward(args)...); + return safeCall(nativeModel, std::forward(func), std::forward(args)...); + } + + template + static auto safeCall(Object *object, Func &&func, Args &&...args) + { + using ReturnType = decltype(std::invoke(std::forward(func), object, + std::forward(args)...)); + + if constexpr (std::is_void_v) { + QMetaObject::invokeMethod(object, std::forward(func), Qt::AutoConnection, + std::forward(args)...); + } else { + ReturnType returnValue; + + const auto connectionType = object->thread() == QThread::currentThread() + ? Qt::DirectConnection + : Qt::BlockingQueuedConnection; + + QMetaObject::invokeMethod(object, std::forward(func), connectionType, + qReturnArg(returnValue), std::forward(args)...); + return returnValue; + } } static jint jni_columnCount(JNIEnv *env, jobject object, JQtModelIndex parent);