JNI: merge class loading implementations

QtAndroidPrivate::findClass first tried the cache, then JNIEnv::FindClass.
Failing that, it tried QJniObject::loadClass which also checked the
cache before falling back to the ClassLoader.

Merge the implementations into QtAndroidPrivate::findClass. Loading a
class now always first tries the cache, then FindClass, then the
ClassLoader. Reduce the overhead from temporary QJniObject creations by
explicitly creating the class string, and avoid printing the exception
in case of failure twice by clearing the exception explicitly after the
first try.

Assert that the function receives a JNIEnv pointer, as any call without
is a programming error (and all call-sites in Qt pass in a valid pointer
anyway, so no need to test otherwise).

With both implementations merged we can centralize the binary encoding
of the class name strings to the single place where it's needed, which
is when calling the ClassLoader::loadClass method. FindClass takes a
fully qualified, slash-separated class name string, so in most cases we
never have to pay any overhead for re-encoding the class name string.

Change-Id: I5a3648763052cebdec69d7d77c503aebdebf5bee
Reviewed-by: Juha Vuolle <juha.vuolle@qt.io>
bb10
Volker Hilsheimer 2023-09-21 20:22:21 +02:00
parent 064b46779c
commit 39294317e0
1 changed files with 58 additions and 76 deletions

View File

@ -286,17 +286,6 @@ public:
env->DeleteGlobalRef(m_jclass);
}
friend jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env);
static jclass loadClass(const QByteArray &className, JNIEnv *env, bool binEncoded = false)
{
return QJniObject::loadClass(className, env, binEncoded);
}
static QByteArray toBinaryEncClassName(const QByteArray &className)
{
return QJniObject::toBinaryEncClassName(className);
}
jobject m_jobject = nullptr;
jclass m_jclass = nullptr;
bool m_own_jclass = true;
@ -313,16 +302,11 @@ typedef QHash<QByteArray, jclass> JClassHash;
Q_GLOBAL_STATIC(JClassHash, cachedClasses)
Q_GLOBAL_STATIC(QReadWriteLock, cachedClassesLock)
static jclass getCachedClass(const QByteArray &classBinEnc, bool *isCached = nullptr)
static jclass getCachedClass(const QByteArray &className)
{
QReadLocker locker(cachedClassesLock);
const auto &it = cachedClasses->constFind(classBinEnc);
const bool found = (it != cachedClasses->constEnd());
if (isCached)
*isCached = found;
return found ? it.value() : 0;
const auto &it = cachedClasses->constFind(className);
return it != cachedClasses->constEnd() ? it.value() : nullptr;
}
QByteArray QJniObject::toBinaryEncClassName(const QByteArray &className)
@ -330,37 +314,67 @@ QByteArray QJniObject::toBinaryEncClassName(const QByteArray &className)
return QByteArray(className).replace('/', '.');
}
jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env, bool binEncoded)
/*!
\internal
\a className must be slash-encoded
*/
jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
{
const QByteArray &binEncClassName = binEncoded ? className : QJniObject::toBinaryEncClassName(className);
bool isCached = false;
jclass clazz = getCachedClass(binEncClassName, &isCached);
if (clazz || isCached)
Q_ASSERT(env);
const QByteArray classNameArray(className);
jclass clazz = getCachedClass(classNameArray);
if (clazz)
return clazz;
QJniObject classLoader(QtAndroidPrivate::classLoader());
if (!classLoader.isValid())
return nullptr;
QWriteLocker locker(cachedClassesLock);
// did we lose the race?
const auto &it = cachedClasses->constFind(binEncClassName);
// Check again; another thread might have added the class to the cache after
// our call to getCachedClass and before we acquired the lock.
const auto &it = cachedClasses->constFind(classNameArray);
if (it != cachedClasses->constEnd())
return it.value();
QJniObject stringName = QJniObject::fromString(QString::fromLatin1(binEncClassName));
QJniObject classObject = classLoader.callObjectMethod("loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
stringName.object());
// JNIEnv::FindClass wants "a fully-qualified class name or an array type signature"
// which is a slash-separated class name.
jclass localClazz = env->FindClass(className);
if (localClazz) {
clazz = static_cast<jclass>(env->NewGlobalRef(localClazz));
env->DeleteLocalRef(localClazz);
} else {
// Clear the exception silently; we are going to try the ClassLoader next,
// so no need for warning unless that fails as well.
env->ExceptionClear();
}
if (!QJniEnvironment::checkAndClearExceptions(env) && classObject.isValid())
clazz = static_cast<jclass>(env->NewGlobalRef(classObject.object()));
if (!clazz) {
// We didn't get an env. pointer or we got one with the WRONG class loader...
QJniObject classLoader(QtAndroidPrivate::classLoader());
if (!classLoader.isValid())
return nullptr;
// ClassLoader::loadClass on the other hand wants the binary name of the class,
// e.g. dot-separated. In testing it works also with /, but better to stick to
// the specification.
const QString binaryClassName = QString::fromLatin1(className).replace(u'/', u'.');
jstring classNameObject = env->NewString(reinterpret_cast<const jchar*>(binaryClassName.constData()),
binaryClassName.length());
QJniObject classObject = classLoader.callMethod<jclass>("loadClass", classNameObject);
env->DeleteLocalRef(classNameObject);
if (!QJniEnvironment::checkAndClearExceptions(env) && classObject.isValid())
clazz = static_cast<jclass>(env->NewGlobalRef(classObject.object()));
}
if (clazz)
cachedClasses->insert(classNameArray, clazz);
cachedClasses->insert(binEncClassName, clazz);
return clazz;
}
jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env, bool /*binEncoded*/)
{
return QtAndroidPrivate::findClass(className, env);
}
typedef QHash<QByteArray, jmethodID> JMethodIDHash;
Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID)
Q_GLOBAL_STATIC(QReadWriteLock, cachedMethodIDLock)
@ -492,38 +506,6 @@ jfieldID QJniObject::getCachedFieldID(JNIEnv *env,
return QJniObject::getCachedFieldID(env, d->m_jclass, d->m_className, name, signature, isStatic);
}
jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
{
const QByteArray &classDotEnc = QJniObjectPrivate::toBinaryEncClassName(className);
bool isCached = false;
jclass clazz = getCachedClass(classDotEnc, &isCached);
if (clazz || isCached)
return clazz;
if (env) { // We got an env. pointer (We expect this to be the right env. and call FindClass())
QWriteLocker locker(cachedClassesLock);
const auto &it = cachedClasses->constFind(classDotEnc);
// Did we lose the race?
if (it != cachedClasses->constEnd())
return it.value();
jclass fclazz = env->FindClass(className);
if (!QJniEnvironment::checkAndClearExceptions(env)) {
clazz = static_cast<jclass>(env->NewGlobalRef(fclazz));
env->DeleteLocalRef(fclazz);
}
if (clazz)
cachedClasses->insert(classDotEnc, clazz);
}
if (!clazz) // We didn't get an env. pointer or we got one with the WRONG class loader...
clazz = QJniObjectPrivate::loadClass(classDotEnc, QJniEnvironment().jniEnv(), true);
return clazz;
}
/*!
\fn QJniObject::QJniObject()
@ -549,8 +531,8 @@ QJniObject::QJniObject(const char *className)
: d(new QJniObjectPrivate())
{
QJniEnvironment env;
d->m_className = toBinaryEncClassName(className);
d->m_jclass = loadClass(d->m_className, env.jniEnv(), true);
d->m_className = className;
d->m_jclass = loadClass(d->m_className, env.jniEnv());
d->m_own_jclass = false;
if (d->m_jclass) {
// get default constructor
@ -582,8 +564,8 @@ QJniObject::QJniObject(const char *className, const char *signature, ...)
: d(new QJniObjectPrivate())
{
QJniEnvironment env;
d->m_className = toBinaryEncClassName(className);
d->m_jclass = loadClass(d->m_className, env.jniEnv(), true);
d->m_className = className;
d->m_jclass = loadClass(d->m_className, env.jniEnv());
d->m_own_jclass = false;
if (d->m_jclass) {
jmethodID constructorId = getCachedMethodID(env.jniEnv(), "<init>", signature);
@ -1004,7 +986,7 @@ QJniObject QJniObject::callStaticObjectMethod(const char *className, const char
jclass clazz = QJniObject::loadClass(className, env.jniEnv());
if (clazz) {
jmethodID id = QJniObject::getCachedMethodID(env.jniEnv(), clazz,
QJniObject::toBinaryEncClassName(className),
className,
methodName, signature, true);
if (id) {
va_list args;
@ -1202,7 +1184,7 @@ QJniObject QJniObject::getStaticObjectField(const char *className,
if (!clazz)
return QJniObject();
jfieldID id = QJniObject::getCachedFieldID(env.jniEnv(), clazz,
QJniObject::toBinaryEncClassName(className),
className,
fieldName,
signature, true);
if (!id)