Android: Add runOnMainAndroidThread() under QNativeInterface
This replaces QtAndroidPrivate::runOnAndroidThread{Sync} calls. This also now allows passing std::function<> that can return values, and not only an std::function<void()>. This adds some tests for this calls as well. Fixes: QTBUG-90501 Change-Id: I138d2aae64be17347f7ff712d8a86edb49ea8350 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
parent
da30e402f3
commit
478ed8b71f
@ -105,3 +105,8 @@
|
|||||||
\externalpage https://doc.qt.io/qtcreator/creator-deploying-android.html#editing-manifest-files
|
\externalpage https://doc.qt.io/qtcreator/creator-deploying-android.html#editing-manifest-files
|
||||||
\title Qt Creator: Editing Manifest Files
|
\title Qt Creator: Editing Manifest Files
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\externalpage https://developer.android.com/training/articles/perf-anr
|
||||||
|
\title Android: Keeping your app responsive
|
||||||
|
*/
|
||||||
|
@ -44,6 +44,11 @@
|
|||||||
#include <QtCore/qnativeinterface.h>
|
#include <QtCore/qnativeinterface.h>
|
||||||
#include <QtCore/qcoreapplication.h>
|
#include <QtCore/qcoreapplication.h>
|
||||||
|
|
||||||
|
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
|
||||||
|
#include <QtCore/qfuture.h>
|
||||||
|
#include <QtCore/qvariant.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
|
#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
|
||||||
class _jobject;
|
class _jobject;
|
||||||
typedef _jobject* jobject;
|
typedef _jobject* jobject;
|
||||||
@ -61,6 +66,22 @@ struct Q_CORE_EXPORT QAndroidApplication
|
|||||||
static bool isActivityContext();
|
static bool isActivityContext();
|
||||||
static int sdkVersion();
|
static int sdkVersion();
|
||||||
static void hideSplashScreen(int duration = 0);
|
static void hideSplashScreen(int duration = 0);
|
||||||
|
|
||||||
|
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
|
||||||
|
static QFuture<QVariant> runOnAndroidMainThread(const std::function<QVariant()> &runnable,
|
||||||
|
const QDeadlineTimer
|
||||||
|
&timeout = QDeadlineTimer(-1));
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
std::enable_if_t<std::is_invocable_v<T> && std::is_same_v<std::invoke_result_t<T>, void>,
|
||||||
|
QFuture<void>> static runOnAndroidMainThread(const T &runnable,
|
||||||
|
const QDeadlineTimer
|
||||||
|
&timeout = QDeadlineTimer(-1))
|
||||||
|
{
|
||||||
|
std::function<QVariant()> func = [&](){ runnable(); return QVariant(); };
|
||||||
|
return static_cast<QFuture<void>>(runOnAndroidMainThread(func, timeout));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -358,6 +358,9 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
|
|||||||
if (!registerPermissionNatives())
|
if (!registerPermissionNatives())
|
||||||
return JNI_ERR;
|
return JNI_ERR;
|
||||||
|
|
||||||
|
if (!registerNativeInterfaceNatives())
|
||||||
|
return JNI_ERR;
|
||||||
|
|
||||||
g_runPendingCppRunnablesMethodID = env->GetStaticMethodID(jQtNative,
|
g_runPendingCppRunnablesMethodID = env->GetStaticMethodID(jQtNative,
|
||||||
"runPendingCppRunnablesOnAndroidThread",
|
"runPendingCppRunnablesOnAndroidThread",
|
||||||
"()V");
|
"()V");
|
||||||
|
@ -128,7 +128,9 @@ namespace QtAndroidPrivate
|
|||||||
Q_CORE_EXPORT PermissionsHash requestPermissionsSync(JNIEnv *env, const QStringList &permissions, int timeoutMs = INT_MAX);
|
Q_CORE_EXPORT PermissionsHash requestPermissionsSync(JNIEnv *env, const QStringList &permissions, int timeoutMs = INT_MAX);
|
||||||
Q_CORE_EXPORT PermissionsResult checkPermission(const QString &permission);
|
Q_CORE_EXPORT PermissionsResult checkPermission(const QString &permission);
|
||||||
Q_CORE_EXPORT bool shouldShowRequestPermissionRationale(const QString &permission);
|
Q_CORE_EXPORT bool shouldShowRequestPermissionRationale(const QString &permission);
|
||||||
|
|
||||||
bool registerPermissionNatives();
|
bool registerPermissionNatives();
|
||||||
|
bool registerNativeInterfaceNatives();
|
||||||
|
|
||||||
Q_CORE_EXPORT void handleActivityResult(jint requestCode, jint resultCode, jobject data);
|
Q_CORE_EXPORT void handleActivityResult(jint requestCode, jint resultCode, jobject data);
|
||||||
Q_CORE_EXPORT void registerActivityResultListener(ActivityResultListener *listener);
|
Q_CORE_EXPORT void registerActivityResultListener(ActivityResultListener *listener);
|
||||||
|
@ -37,12 +37,27 @@
|
|||||||
**
|
**
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
#include <QtCore/qcoreapplication.h>
|
#include <QtCore/qcoreapplication_platform.h>
|
||||||
|
|
||||||
#include <QtCore/private/qjnihelpers_p.h>
|
#include <QtCore/private/qjnihelpers_p.h>
|
||||||
#include <QtCore/qjniobject.h>
|
#include <QtCore/qjniobject.h>
|
||||||
|
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
|
||||||
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
#include <QtCore/qpromise.h>
|
||||||
|
#include <deque>
|
||||||
|
#endif
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
|
||||||
|
static const char qtNativeClassName[] = "org/qtproject/qt/android/QtNative";
|
||||||
|
|
||||||
|
typedef std::pair<std::function<QVariant()>, QSharedPointer<QPromise<QVariant>>> RunnablePair;
|
||||||
|
typedef std::deque<RunnablePair> PendingRunnables;
|
||||||
|
Q_GLOBAL_STATIC(PendingRunnables, g_pendingRunnables);
|
||||||
|
static QBasicMutex g_pendingRunnablesMutex;
|
||||||
|
#endif
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\class QNativeInterface::QAndroidApplication
|
\class QNativeInterface::QAndroidApplication
|
||||||
\since 6.2
|
\since 6.2
|
||||||
@ -110,4 +125,136 @@ void QNativeInterface::QAndroidApplication::hideSplashScreen(int duration)
|
|||||||
"hideSplashScreen", "(I)V", duration);
|
"hideSplashScreen", "(I)V", duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Posts the function \a runnable to the Android thread. The function will be
|
||||||
|
queued and executed on the Android UI thread. If the call is made on the
|
||||||
|
Android UI thread \a runnable will be executed immediately. If the Android
|
||||||
|
app is paused or the main Activity is null, \c runnable is added to the
|
||||||
|
Android main thread's queue.
|
||||||
|
|
||||||
|
This call returns a QFuture<QVariant> which allows doing both synchronous
|
||||||
|
and asynchronous calls, and can handle any return type. However, to get
|
||||||
|
a result back from the QFuture::result(), QVariant::value() should be used.
|
||||||
|
|
||||||
|
If the \a runnable execution takes longer than the period of \a timeout,
|
||||||
|
the blocking calls \l QFuture::waitForFinished() and \l QFuture::result()
|
||||||
|
are ended once \a timeout has elapsed. However, if \a runnable has already
|
||||||
|
started execution, it won't be cancelled.
|
||||||
|
|
||||||
|
The following example shows how to run an asynchronous call that expects
|
||||||
|
a return type:
|
||||||
|
|
||||||
|
\code
|
||||||
|
auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() {
|
||||||
|
QJniObject surfaceView;
|
||||||
|
if (!surfaceView.isValid())
|
||||||
|
qDebug() << "SurfaceView object is not valid yet";
|
||||||
|
|
||||||
|
surfaceView = QJniObject("android/view/SurfaceView",
|
||||||
|
"(Landroid/content/Context;)V",
|
||||||
|
QNativeInterface::QAndroidApplication::context());
|
||||||
|
|
||||||
|
return QVariant::fromValue(surfaceView);
|
||||||
|
}).then([](QFuture<QVariant> future) {
|
||||||
|
auto surfaceView = future.result().value<QJniObject>();
|
||||||
|
if (surfaceView.isValid())
|
||||||
|
qDebug() << "Retrieved SurfaceView object is valid";
|
||||||
|
});
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
The following example shows how to run a synchronous call with a void
|
||||||
|
return type:
|
||||||
|
|
||||||
|
\code
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([]() {
|
||||||
|
QJniObject activity = QNativeInterface::QAndroidApplication::context();
|
||||||
|
// Hide system ui elements or go full screen
|
||||||
|
activity.callObjectMethod("getWindow", "()Landroid/view/Window;")
|
||||||
|
.callObjectMethod("getDecorView", "()Landroid/view/View;")
|
||||||
|
.callMethod<void>("setSystemUiVisibility", "(I)V", 0xffffffff);
|
||||||
|
}).waitForFinished();
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\note Becareful about the type of operations you do on the Android's main
|
||||||
|
thread, as any long operation can block the app's UI rendering and input
|
||||||
|
handling. If the function is expected to have long execution time, it's
|
||||||
|
also good to use a \l QDeadlineTimer() in your \a runnable to manage
|
||||||
|
the execution and make sure it doesn't block the UI thread. Usually,
|
||||||
|
any operation longer than 5 seconds might block the app's UI. For more
|
||||||
|
information, see \l {Android: Keeping your app responsive}{Keeping your app responsive}.
|
||||||
|
|
||||||
|
\since 6.2
|
||||||
|
*/
|
||||||
|
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
|
||||||
|
QFuture<QVariant> QNativeInterface::QAndroidApplication::runOnAndroidMainThread(
|
||||||
|
const std::function<QVariant()> &runnable,
|
||||||
|
const QDeadlineTimer &timeout)
|
||||||
|
{
|
||||||
|
QSharedPointer<QPromise<QVariant>> promise(new QPromise<QVariant>());
|
||||||
|
QFuture<QVariant> future = promise->future();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
(void) QtConcurrent::run([=, &future]() {
|
||||||
|
if (!timeout.isForever()) {
|
||||||
|
QEventLoop loop;
|
||||||
|
QTimer::singleShot(timeout.remainingTime(), &loop, [&]() {
|
||||||
|
future.cancel();
|
||||||
|
promise->finish();
|
||||||
|
loop.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
QFutureWatcher<QVariant> watcher;
|
||||||
|
QObject::connect(&watcher, &QFutureWatcher<QVariant>::finished, &loop, [&]() {
|
||||||
|
loop.quit();
|
||||||
|
});
|
||||||
|
QObject::connect(&watcher, &QFutureWatcher<QVariant>::canceled, &loop, [&]() {
|
||||||
|
loop.quit();
|
||||||
|
});
|
||||||
|
watcher.setFuture(future);
|
||||||
|
loop.exec();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QMutexLocker locker(&g_pendingRunnablesMutex);
|
||||||
|
g_pendingRunnables->push_back(std::pair(runnable, promise));
|
||||||
|
locker.unlock();
|
||||||
|
|
||||||
|
QJniObject::callStaticMethod<void>(qtNativeClassName,
|
||||||
|
"runPendingCppRunnablesOnAndroidThread",
|
||||||
|
"()V");
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
// function called from Java from Android UI thread
|
||||||
|
static void runPendingCppRunnables(JNIEnv */*env*/, jobject /*obj*/)
|
||||||
|
{
|
||||||
|
// run all posted runnables
|
||||||
|
for (;;) {
|
||||||
|
QMutexLocker locker(&g_pendingRunnablesMutex);
|
||||||
|
if (g_pendingRunnables->empty())
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::pair pair = std::move(g_pendingRunnables->front());
|
||||||
|
g_pendingRunnables->pop_front();
|
||||||
|
locker.unlock();
|
||||||
|
|
||||||
|
// run the runnable outside the sync block!
|
||||||
|
auto promise = pair.second;
|
||||||
|
if (!promise->isCanceled())
|
||||||
|
promise->addResult(pair.first());
|
||||||
|
promise->finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool QtAndroidPrivate::registerNativeInterfaceNatives()
|
||||||
|
{
|
||||||
|
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
|
||||||
|
JNINativeMethod methods = {"runPendingCppRunnables", "()V", (void *)runPendingCppRunnables};
|
||||||
|
return QJniEnvironment().registerNativeMethods(qtNativeClassName, &methods, 1);
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -40,6 +40,7 @@ private slots:
|
|||||||
void assetsNotWritable();
|
void assetsNotWritable();
|
||||||
void testAndroidSdkVersion();
|
void testAndroidSdkVersion();
|
||||||
void testAndroidActivity();
|
void testAndroidActivity();
|
||||||
|
void testRunOnAndroidMainThread();
|
||||||
};
|
};
|
||||||
|
|
||||||
void tst_Android::assetsRead()
|
void tst_Android::assetsRead()
|
||||||
@ -77,6 +78,117 @@ void tst_Android::testAndroidActivity()
|
|||||||
QVERIFY(activity.callMethod<jboolean>("isTaskRoot"));
|
QVERIFY(activity.callMethod<jboolean>("isTaskRoot"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_Android::testRunOnAndroidMainThread()
|
||||||
|
{
|
||||||
|
// async void
|
||||||
|
{
|
||||||
|
int res = 0;
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{ res = 1; });
|
||||||
|
QTRY_COMPARE(res, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync void
|
||||||
|
{
|
||||||
|
int res = 0;
|
||||||
|
auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{
|
||||||
|
res = 1;
|
||||||
|
});
|
||||||
|
task.waitForFinished();
|
||||||
|
QCOMPARE(res, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync return value
|
||||||
|
{
|
||||||
|
auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([]{
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
task.waitForFinished();
|
||||||
|
QVERIFY(task.isResultReadyAt(0));
|
||||||
|
QCOMPARE(task.result().value<int>(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// nested calls
|
||||||
|
{
|
||||||
|
// nested async/async
|
||||||
|
int res = 0;
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{
|
||||||
|
res = 3;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
QTRY_COMPARE(res, 3);
|
||||||
|
|
||||||
|
// nested async/sync
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{
|
||||||
|
res = 5;
|
||||||
|
}).waitForFinished();
|
||||||
|
});
|
||||||
|
QTRY_COMPARE(res, 5);
|
||||||
|
|
||||||
|
// nested sync/sync
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{
|
||||||
|
res = 4;
|
||||||
|
}).waitForFinished();
|
||||||
|
}).waitForFinished();
|
||||||
|
QCOMPARE(res, 4);
|
||||||
|
|
||||||
|
|
||||||
|
// nested sync/async
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{
|
||||||
|
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([&res]{
|
||||||
|
res = 6;
|
||||||
|
});
|
||||||
|
}).waitForFinished();
|
||||||
|
QCOMPARE(res, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeouts
|
||||||
|
{
|
||||||
|
auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([]{
|
||||||
|
QThread::msleep(500);
|
||||||
|
return 1;
|
||||||
|
}, QDeadlineTimer(100));
|
||||||
|
task.waitForFinished();
|
||||||
|
QVERIFY(task.isCanceled());
|
||||||
|
QVERIFY(task.isFinished());
|
||||||
|
QVERIFY(!task.isResultReadyAt(0));
|
||||||
|
|
||||||
|
auto task2 = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([]{
|
||||||
|
return 2;
|
||||||
|
}, QDeadlineTimer(0));
|
||||||
|
task2.waitForFinished();
|
||||||
|
QVERIFY(task2.isCanceled());
|
||||||
|
QVERIFY(task2.isFinished());
|
||||||
|
QVERIFY(!task2.isResultReadyAt(0));
|
||||||
|
|
||||||
|
QDeadlineTimer deadline(1000);
|
||||||
|
auto task3 = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([]{
|
||||||
|
return 3;
|
||||||
|
}, QDeadlineTimer(10000));
|
||||||
|
task3.waitForFinished();
|
||||||
|
QVERIFY(deadline.remainingTime() > 0);
|
||||||
|
QVERIFY(task3.isFinished());
|
||||||
|
QVERIFY(!task3.isCanceled());
|
||||||
|
QVERIFY(task3.isResultReadyAt(0));
|
||||||
|
QCOMPARE(task3.result().value<int>(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancelled future
|
||||||
|
{
|
||||||
|
auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([]{
|
||||||
|
QThread::msleep(2000);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
task.cancel();
|
||||||
|
QVERIFY(task.isCanceled());
|
||||||
|
task.waitForFinished();
|
||||||
|
QVERIFY(task.isFinished());
|
||||||
|
QVERIFY(!task.isResultReadyAt(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_Android)
|
QTEST_MAIN(tst_Android)
|
||||||
#include "tst_android.moc"
|
#include "tst_android.moc"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user