QJniEnvironment: extend API

This patch adds some convenience methods to QJniEnvironment API:
* an overload of registerNativeMethods() that accepts jclass instead
  of const char *className.
* a findMethod() function is added to query a methodID of a static
  or nonstatic method by its name and signature.

Task-number: QTBUG-92952
Change-Id: Ib1bc892decea97e625c4822888b6183af6edd6dc
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
This commit is contained in:
Ivan Solovev 2021-04-30 11:49:08 +02:00
parent 96d9cf8de4
commit 34f72ca52e
4 changed files with 149 additions and 7 deletions

View File

@ -197,6 +197,54 @@ jclass QJniEnvironment::findClass(const char *className)
return QtAndroidPrivate::findClass(className, d->jniEnv);
}
/*!
Searches for an instance method of a class \a clazz. The method is specified
by its \a methodName and \a signature.
Returns the method ID or \c nullptr if the method is not found.
A usecase for this method is searching for class methods and caching their
IDs, so that they could later be used for calling the methods.
*/
jmethodID QJniEnvironment::findMethod(jclass clazz, const char *methodName, const char *signature)
{
jmethodID id = d->jniEnv->GetMethodID(clazz, methodName, signature);
if (checkAndClearExceptions(d->jniEnv))
return nullptr;
return id;
}
/*!
Searches for a static method of a class \a clazz. The method is specified
by its \a methodName and \a signature.
Returns the method ID or \c nullptr if the method is not found.
A usecase for this method is searching for class methods and caching their
IDs, so that they could later be used for calling the methods.
\code
QJniEnvironment env;
jclass javaClass = env.findClass("org/qtproject/example/android/CustomClass");
jmethodID methodId = env.findStaticMethod(javaClass,
"staticJavaMethod",
"(Ljava/lang/String;)V");
QJniObject javaMessage = QJniObject::fromString("findStaticMethod example");
QJniObject::callStaticMethod<void>(javaClass,
methodId,
javaMessage.object<jstring>());
\endcode
*/
jmethodID QJniEnvironment::findStaticMethod(jclass clazz, const char *methodName, const char *signature)
{
jmethodID id = d->jniEnv->GetStaticMethodID(clazz, methodName, signature);
if (checkAndClearExceptions(d->jniEnv))
return nullptr;
return id;
}
/*!
\fn JavaVM *QJniEnvironment::javaVM()
@ -216,7 +264,7 @@ JavaVM *QJniEnvironment::javaVM()
which can call native C++ functions from class \a className. These methods
must be registered before any attempt to call them.
Returns True if the registration is successful, otherwise False.
Returns \c true if the registration is successful, otherwise \c false.
Each element in the methods array consists of:
\list
@ -240,14 +288,31 @@ bool QJniEnvironment::registerNativeMethods(const char *className, JNINativeMeth
return false;
jclass clazz = d->jniEnv->GetObjectClass(classObject.object());
if (d->jniEnv->RegisterNatives(clazz, methods, size) < 0) {
checkAndClearExceptions();
d->jniEnv->DeleteLocalRef(clazz);
return false;
}
const bool result = registerNativeMethods(clazz, methods, size);
d->jniEnv->DeleteLocalRef(clazz);
return result;
}
/*!
\overload
This overload uses a previously cached jclass instance \a clazz.
\code
JNINativeMethod methods[] {{"callNativeOne", "(I)V", reinterpret_cast<void *>(fromJavaOne)},
{"callNativeTwo", "(I)V", reinterpret_cast<void *>(fromJavaTwo)}};
QJniEnvironment env;
jclass clazz = env.findClass("org/qtproject/android/TestJavaClass");
env.registerNativeMethods(clazz, methods, 2);
\endcode
*/
bool QJniEnvironment::registerNativeMethods(jclass clazz, JNINativeMethod methods[], int size)
{
if (d->jniEnv->RegisterNatives(clazz, methods, size) < 0) {
checkAndClearExceptions();
return false;
}
return true;
}

View File

@ -58,8 +58,11 @@ public:
JNIEnv &operator*() const;
JNIEnv *jniEnv() const;
jclass findClass(const char *className);
jmethodID findMethod(jclass clazz, const char *methodName, const char *signature);
jmethodID findStaticMethod(jclass clazz, const char *methodName, const char *signature);
static JavaVM *javaVM();
bool registerNativeMethods(const char *className, JNINativeMethod methods[], int size);
bool registerNativeMethods(jclass clazz, JNINativeMethod methods[], int size);
enum class OutputMode {
Silent,

View File

@ -31,11 +31,16 @@ package org.qtproject.qt.android.testdatapackage;
public class QtJniEnvironmentTestClass
{
private static native void callbackFromJava(String message);
private static native void intCallbackFromJava(int value);
public static void appendJavaToString(String message)
{
callbackFromJava("From Java: " + message);
}
public static void convertToInt(String message)
{
intCallbackFromJava(Integer.parseInt(message));
}
}

View File

@ -36,6 +36,7 @@ static const char javaTestClass[] =
"org/qtproject/qt/android/testdatapackage/QtJniEnvironmentTestClass";
static QString registerNativesString = QStringLiteral("Qt");
static int registerNativeInteger = 0;
class tst_QJniEnvironment : public QObject
{
@ -45,6 +46,9 @@ private slots:
void jniEnv();
void javaVM();
void registerNativeMethods();
void registerNativeMethodsByJclass();
void findMethod();
void findStaticMethod();
};
void tst_QJniEnvironment::jniEnv()
@ -134,6 +138,71 @@ void tst_QJniEnvironment::registerNativeMethods()
QVERIFY(registerNativesString == QStringLiteral("From Java: Qt"));
}
static void intCallbackFromJava(JNIEnv *env, jobject /*thiz*/, jint value)
{
Q_UNUSED(env)
registerNativeInteger = static_cast<int>(value);
}
void tst_QJniEnvironment::registerNativeMethodsByJclass()
{
JNINativeMethod methods[] {
{ "intCallbackFromJava", "(I)V", reinterpret_cast<void *>(intCallbackFromJava) }
};
QJniEnvironment env;
jclass clazz = env.findClass(javaTestClass);
QVERIFY(clazz != 0);
QVERIFY(env.registerNativeMethods(clazz, methods, 1));
QCOMPARE(registerNativeInteger, 0);
QJniObject parameter = QJniObject::fromString(QString("123"));
QJniObject::callStaticMethod<void>(clazz, "convertToInt", "(Ljava/lang/String;)V",
parameter.object<jstring>());
QTest::qWait(200);
QCOMPARE(registerNativeInteger, 123);
}
void tst_QJniEnvironment::findMethod()
{
QJniEnvironment env;
jclass clazz = env.findClass("java/lang/Integer");
QVERIFY(clazz != nullptr);
// existing method
jmethodID methodId = env.findMethod(clazz, "toString", "()Ljava/lang/String;");
QVERIFY(methodId != nullptr);
// invalid signature
jmethodID invalid = env.findMethod(clazz, "unknown", "()I");
QVERIFY(invalid == nullptr);
// check that all exceptions are already cleared
QVERIFY(!env.checkAndClearExceptions());
}
void tst_QJniEnvironment::findStaticMethod()
{
QJniEnvironment env;
jclass clazz = env.findClass("java/lang/Integer");
QVERIFY(clazz != nullptr);
// existing method
jmethodID staticMethodId = env.findStaticMethod(clazz, "parseInt", "(Ljava/lang/String;)I");
QVERIFY(staticMethodId != nullptr);
QJniObject parameter = QJniObject::fromString("123");
jint result = QJniObject::callStaticMethod<jint>(clazz, staticMethodId,
parameter.object<jstring>());
QCOMPARE(result, 123);
// invalid method
jmethodID invalid = env.findStaticMethod(clazz, "unknown", "()I");
QVERIFY(invalid == nullptr);
// check that all exceptions are already cleared
QVERIFY(!env.checkAndClearExceptions());
}
QTEST_MAIN(tst_QJniEnvironment)
#include "tst_qjnienvironment.moc"