Skip to content

Commit

Permalink
Android: Fix QtAbstractItemModel proxy threading problem
Browse files Browse the repository at this point in the history
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 <[email protected]>
(cherry picked from commit 6c30a60)
Reviewed-by: Qt Cherry-pick Bot <[email protected]>
  • Loading branch information
Soarmin authored and Qt Cherry-pick Bot committed Sep 20, 2024
1 parent 950f928 commit 1f0cf1e
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 9 deletions.
10 changes: 9 additions & 1 deletion src/corelib/platform/android/qandroiditemmodelproxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <QtCore/private/qandroidmodelindexproxy_p.h>
#include <QtCore/private/qandroidtypeconverter_p.h>

#include <QtCore/qcoreapplication.h>

QT_BEGIN_NAMESPACE

using namespace QtJniTypes;
Expand Down Expand Up @@ -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<void>("setNativeReference", reinterpret_cast<jlong>(nativeProxy));
connect(nativeProxy, &QAndroidItemModelProxy::destroyed, nativeProxy, [](QObject *obj) {
Expand Down Expand Up @@ -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());
}

Expand Down
41 changes: 33 additions & 8 deletions src/corelib/platform/android/qandroiditemmodelproxy_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <QtCore/qjniobject.h>
#include <QtCore/qjnienvironment.h>
#include <QtCore/qjnitypes.h>
#include <QtCore/qthread.h>

QT_BEGIN_NAMESPACE

Expand Down Expand Up @@ -58,34 +59,58 @@ class Q_CORE_EXPORT QAndroidItemModelProxy : public QAbstractItemModel
static QJniObject createProxy(QAbstractItemModel *abstractClass);

template <typename Func, typename... Args>
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<QAndroidItemModelProxy *>(nativeInstance(jvmObject));
Q_ASSERT(model);
return std::invoke(func, model, std::forward<Args>(args)...);
return safeCall(model, std::forward<Func>(func), std::forward<Args>(args)...);
}

template <typename Func, typename... Args>
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>(args)...);
return safeCall(model, std::forward<Func>(func), std::forward<Args>(args)...);
}

template <typename Func1, typename Func2, typename... Args>
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<QAndroidItemModelProxy *>(nativeModel);
if (nativeProxyModel)
return std::invoke(defaultFunc, nativeProxyModel, std::forward<Args>(args)...);
return safeCall(nativeProxyModel, std::forward<Func1>(defaultFunc),
std::forward<Args>(args)...);
else
return std::invoke(func, nativeModel, std::forward<Args>(args)...);
return safeCall(nativeModel, std::forward<Func2>(func), std::forward<Args>(args)...);
}

template <typename Object, typename Func, typename... Args>
static auto safeCall(Object *object, Func &&func, Args &&...args)
{
using ReturnType = decltype(std::invoke(std::forward<Func>(func), object,
std::forward<Args>(args)...));

if constexpr (std::is_void_v<ReturnType>) {
QMetaObject::invokeMethod(object, std::forward<Func>(func), Qt::AutoConnection,
std::forward<Args>(args)...);
} else {
ReturnType returnValue;

const auto connectionType = object->thread() == QThread::currentThread()
? Qt::DirectConnection
: Qt::BlockingQueuedConnection;

QMetaObject::invokeMethod(object, std::forward<Func>(func), connectionType,
qReturnArg(returnValue), std::forward<Args>(args)...);
return returnValue;
}
}

static jint jni_columnCount(JNIEnv *env, jobject object, JQtModelIndex parent);
Expand Down

0 comments on commit 1f0cf1e

Please sign in to comment.