Return specific types for frequently used Java objects

This allows us to specialize JNI type signature templates for e.g. the
context object, which in Java signatures is "android/content/Context".

Introduce a Q_DECLARE_JNI_TYPE macro that takes care of the plumbing.
The types declared this way live in the QtJniTypes namespace, and
transparently convert from and to jobject. Since jobject is a typedef
to _jobject* we cannot create a subclass. Use a "Object" superclass
that we can provide a QJniObject constructor for so that we don't
require the QJniObject declaration to be able to use the macro.

The APIs in the QNativeInterface namespace doesn't provide source or
binary compatibility guarantees, so we can change the return types.

Change-Id: I4cf9fa734ec9a5550b6fddeb14ef0ffd72663f29
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Volker Hilsheimer 2022-04-26 13:19:46 +02:00
parent f6e89e901b
commit 367092d7e0
9 changed files with 67 additions and 15 deletions

View File

@ -18,6 +18,7 @@
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
#if defined(Q_OS_ANDROID) || defined(Q_CLANG_QDOC) #if defined(Q_OS_ANDROID) || defined(Q_CLANG_QDOC)
#include <QtCore/qjnitypes.h>
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) #if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
#include <QtCore/qfuture.h> #include <QtCore/qfuture.h>
#include <QtCore/qvariant.h> #include <QtCore/qvariant.h>
@ -31,13 +32,21 @@ typedef _jobject* jobject;
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
#if defined(Q_OS_ANDROID)
Q_DECLARE_JNI_TYPE(Context, "Landroid/content/Context;")
#endif
namespace QNativeInterface namespace QNativeInterface
{ {
#if defined(Q_OS_ANDROID) || defined(Q_CLANG_QDOC) #if defined(Q_OS_ANDROID) || defined(Q_CLANG_QDOC)
struct Q_CORE_EXPORT QAndroidApplication struct Q_CORE_EXPORT QAndroidApplication
{ {
QT_DECLARE_NATIVE_INTERFACE(QAndroidApplication, 1, QCoreApplication) QT_DECLARE_NATIVE_INTERFACE(QAndroidApplication, 1, QCoreApplication)
#ifdef Q_CLANG_QDOC
static jobject context(); static jobject context();
#else
static QtJniTypes::Context context();
#endif
static bool isActivityContext(); static bool isActivityContext();
static int sdkVersion(); static int sdkVersion();
static void hideSplashScreen(int duration = 0); static void hideSplashScreen(int duration = 0);

View File

@ -289,18 +289,18 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
return JNI_OK; return JNI_OK;
} }
jobject QtAndroidPrivate::activity() QtJniTypes::Activity QtAndroidPrivate::activity()
{ {
QReadLocker locker(g_updateMutex()); QReadLocker locker(g_updateMutex());
return g_jActivity; return g_jActivity;
} }
jobject QtAndroidPrivate::service() QtJniTypes::Service QtAndroidPrivate::service()
{ {
return g_jService; return g_jService;
} }
jobject QtAndroidPrivate::context() QtJniTypes::Context QtAndroidPrivate::context()
{ {
QReadLocker locker(g_updateMutex()); QReadLocker locker(g_updateMutex());
if (g_jActivity) if (g_jActivity)

View File

@ -18,9 +18,13 @@
#include <jni.h> #include <jni.h>
#include <functional> #include <functional>
#include <QtCore/private/qglobal_p.h> #include <QtCore/private/qglobal_p.h>
#include <QtCore/qcoreapplication_platform.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
Q_DECLARE_JNI_TYPE(Activity, "Landroid/app/Activity;")
Q_DECLARE_JNI_TYPE(Service, "Landroid/app/Service;")
namespace QtAndroidPrivate namespace QtAndroidPrivate
{ {
class Q_CORE_EXPORT ActivityResultListener class Q_CORE_EXPORT ActivityResultListener
@ -66,9 +70,9 @@ namespace QtAndroidPrivate
virtual jobject onBind(jobject intent) = 0; virtual jobject onBind(jobject intent) = 0;
}; };
Q_CORE_EXPORT jobject activity(); Q_CORE_EXPORT QtJniTypes::Activity activity();
Q_CORE_EXPORT jobject service(); Q_CORE_EXPORT QtJniTypes::Service service();
Q_CORE_EXPORT jobject context(); Q_CORE_EXPORT QtJniTypes::Context context();
Q_CORE_EXPORT JavaVM *javaVM(); Q_CORE_EXPORT JavaVM *javaVM();
Q_CORE_EXPORT jint initJNI(JavaVM *vm, JNIEnv *env); Q_CORE_EXPORT jint initJNI(JavaVM *vm, JNIEnv *env);
Q_CORE_EXPORT jclass findClass(const char *className, JNIEnv *env); Q_CORE_EXPORT jclass findClass(const char *className, JNIEnv *env);

View File

@ -42,6 +42,7 @@ public:
std::forward<Args>(args)...) std::forward<Args>(args)...)
{} {}
QJniObject(jobject globalRef); QJniObject(jobject globalRef);
inline QJniObject(QtJniTypes::Object wrapper) noexcept : QJniObject(jobject(wrapper)) {}
~QJniObject(); ~QJniObject();
jobject object() const; jobject object() const;

View File

@ -227,7 +227,8 @@ static constexpr bool isObjectType()
return true; return true;
} else { } else {
constexpr auto signature = typeSignature<T>(); constexpr auto signature = typeSignature<T>();
return signature.startsWith('L') && signature.endsWith(';'); return (signature.startsWith('L') || signature.startsWith('['))
&& signature.endsWith(';');
} }
} }
@ -273,8 +274,35 @@ static constexpr auto constructorSignature()
return methodSignature<void, Args...>(); return methodSignature<void, Args...>();
} }
// A generic thin wrapper around jobject, convertible to jobject.
// We need this as a baseclass so that QJniObject can be implicitly
// constructed from the various subclasses - we can't provide an
// operator QJniObject() here as the class is not declared.
struct Object
{
jobject _object;
constexpr operator jobject() const { return _object; }
};
} // namespace QtJniTypes } // namespace QtJniTypes
#define Q_DECLARE_JNI_TYPE(Type, Signature) \
namespace QtJniTypes { \
struct Type : Object \
{ \
constexpr Type(jobject o) noexcept : Object{o} {} \
}; \
} \
template<> \
constexpr auto QtJniTypes::typeSignature<QtJniTypes::Type>() \
{ \
static_assert((Signature[0] == 'L' || Signature[0] == '[') \
&& Signature[sizeof(Signature) - 2] == ';', \
"Type signature needs to start with 'L' or '['" \
" and end with ';'"); \
return QtJniTypes::String(Signature); \
} \
QT_END_NAMESPACE QT_END_NAMESPACE
#endif #endif

View File

@ -38,14 +38,14 @@ static QBasicMutex g_pendingRunnablesMutex;
QT_DEFINE_NATIVE_INTERFACE(QAndroidApplication); QT_DEFINE_NATIVE_INTERFACE(QAndroidApplication);
/*! /*!
\fn jobject QNativeInterface::QAndroidApplication::context() \fn jobject QNativeInterface::QAndroidApplication::context()
Returns the Android context as a \c jobject. The context is an \c Activity Returns the Android context as a \c jobject. The context is an \c Activity
if the main activity object is valid. Otherwise, the context is a \c Service. if the main activity object is valid. Otherwise, the context is a \c Service.
\since 6.2 \since 6.2
*/ */
jobject QNativeInterface::QAndroidApplication::context() QtJniTypes::Context QNativeInterface::QAndroidApplication::context()
{ {
return QtAndroidPrivate::context(); return QtAndroidPrivate::context();
} }

View File

@ -43,9 +43,9 @@ static jmethodID m_loadClassMethodID = nullptr;
static AAssetManager *m_assetManager = nullptr; static AAssetManager *m_assetManager = nullptr;
static jobject m_assets = nullptr; static jobject m_assets = nullptr;
static jobject m_resourcesObj = nullptr; static jobject m_resourcesObj = nullptr;
static jobject m_activityObject = nullptr; static QtJniTypes::Activity m_activityObject = nullptr;
static jmethodID m_createSurfaceMethodID = nullptr; static jmethodID m_createSurfaceMethodID = nullptr;
static jobject m_serviceObject = nullptr; static QtJniTypes::Service m_serviceObject = nullptr;
static jmethodID m_setSurfaceGeometryMethodID = nullptr; static jmethodID m_setSurfaceGeometryMethodID = nullptr;
static jmethodID m_destroySurfaceMethodID = nullptr; static jmethodID m_destroySurfaceMethodID = nullptr;
@ -159,12 +159,12 @@ namespace QtAndroid
return m_applicationClass; return m_applicationClass;
} }
jobject activity() QtJniTypes::Activity activity()
{ {
return m_activityObject; return m_activityObject;
} }
jobject service() QtJniTypes::Service service()
{ {
return m_serviceObject; return m_serviceObject;
} }

View File

@ -11,6 +11,7 @@
#include <android/asset_manager.h> #include <android/asset_manager.h>
#include <QImage> #include <QImage>
#include <private/qjnihelpers_p.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -49,8 +50,8 @@ namespace QtAndroid
jobject assets(); jobject assets();
AAssetManager *assetManager(); AAssetManager *assetManager();
jclass applicationClass(); jclass applicationClass();
jobject activity(); QtJniTypes::Activity activity();
jobject service(); QtJniTypes::Service service();
// Keep synchronized with flags in ActivityDelegate.java // Keep synchronized with flags in ActivityDelegate.java
enum SystemUiVisibility { enum SystemUiVisibility {

View File

@ -40,6 +40,11 @@ static_assert(QtJniTypes::typeSignature<QtJavaWrapper>() == "Lorg/qtproject/qt/a
static_assert(QtJniTypes::typeSignature<QtJavaWrapper>() != "Ljava/lang/Object;"); static_assert(QtJniTypes::typeSignature<QtJavaWrapper>() != "Ljava/lang/Object;");
static_assert(!(QtJniTypes::typeSignature<QtJavaWrapper>() == "X")); static_assert(!(QtJniTypes::typeSignature<QtJavaWrapper>() == "X"));
Q_DECLARE_JNI_TYPE(JavaType, "Lorg/qtproject/qt/JavaType;");
static_assert(QtJniTypes::typeSignature<QtJniTypes::JavaType>() == "Lorg/qtproject/qt/JavaType;");
Q_DECLARE_JNI_TYPE(ArrayType, "[Lorg/qtproject/qt/ArrayType;")
static_assert(QtJniTypes::typeSignature<QtJniTypes::ArrayType>() == "[Lorg/qtproject/qt/ArrayType;");
static_assert(QtJniTypes::fieldSignature<jint>() == "I"); static_assert(QtJniTypes::fieldSignature<jint>() == "I");
static_assert(QtJniTypes::fieldSignature<jint>() != "X"); static_assert(QtJniTypes::fieldSignature<jint>() != "X");
static_assert(QtJniTypes::fieldSignature<jint>() != "Ljava/lang/Object;"); static_assert(QtJniTypes::fieldSignature<jint>() != "Ljava/lang/Object;");
@ -57,6 +62,8 @@ static_assert(QtJniTypes::methodSignature<void, jint>() == "(I)V");
static_assert(QtJniTypes::methodSignature<void, jint, jstring>() == "(ILjava/lang/String;)V"); static_assert(QtJniTypes::methodSignature<void, jint, jstring>() == "(ILjava/lang/String;)V");
static_assert(QtJniTypes::methodSignature<jlong, jint, jclass>() == "(ILjava/lang/Class;)J"); static_assert(QtJniTypes::methodSignature<jlong, jint, jclass>() == "(ILjava/lang/Class;)J");
static_assert(QtJniTypes::methodSignature<jobject, jint, jstring>() == "(ILjava/lang/String;)Ljava/lang/Object;"); static_assert(QtJniTypes::methodSignature<jobject, jint, jstring>() == "(ILjava/lang/String;)Ljava/lang/Object;");
static_assert(QtJniTypes::methodSignature<QtJniTypes::JavaType, jint, jstring>()
== "(ILjava/lang/String;)Lorg/qtproject/qt/JavaType;");
static_assert(QtJniTypes::isPrimitiveType<jint>()); static_assert(QtJniTypes::isPrimitiveType<jint>());
static_assert(QtJniTypes::isPrimitiveType<void>()); static_assert(QtJniTypes::isPrimitiveType<void>());
@ -66,6 +73,7 @@ static_assert(!QtJniTypes::isPrimitiveType<QtCustomJniObject>());
static_assert(!QtJniTypes::isObjectType<jint>()); static_assert(!QtJniTypes::isObjectType<jint>());
static_assert(!QtJniTypes::isObjectType<void>()); static_assert(!QtJniTypes::isObjectType<void>());
static_assert(QtJniTypes::isObjectType<jobject>()); static_assert(QtJniTypes::isObjectType<jobject>());
static_assert(QtJniTypes::isObjectType<jobjectArray>());
static_assert(QtJniTypes::isObjectType<QtCustomJniObject>()); static_assert(QtJniTypes::isObjectType<QtCustomJniObject>());
static_assert(QtJniTypes::String("ABCDE").startsWith("ABC")); static_assert(QtJniTypes::String("ABCDE").startsWith("ABC"));
@ -86,6 +94,7 @@ static_assert(!QtJniTypes::String("ABCDE").endsWith('F'));
void tst_QJniTypes::initTestCase() void tst_QJniTypes::initTestCase()
{ {
} }
QTEST_MAIN(tst_QJniTypes) QTEST_MAIN(tst_QJniTypes)