Back to Home

Android application development using qt and android studio

qt creator · android studio · android development

Android application development using qt and android studio

Irrelevant, except for setting the problem . The right decision in the next article .
Good afternoon, dear Khabrovites! In this article I want to talk about my experience using qt and android studio. Namely, how I needed to draw text in qt and transfer it to android studio. Despite the simplicity of the task, it took me quite a while to solve it and maybe someone will save a lot of time somewhere. The article, in a sense, claims to invent a bicycle, but on the Internet I could not find a solution. Who cares - welcome to kat!

A little about the task itself


Recently, I faced the task of porting the application from ios to android. The main pain when porting was working with the SDK application. It was written in Qt and used to draw text / arrows / areas and everything else. That is, the application was written in objective c, and used the qt library, and was not a qt project. Therefore, the first thing that came up was the development environment. Since I am completely new to this business, my choice fell on the android studio. Still, the whole graphical interface, as it seemed to me, is better done in the android studio, and our qtshnoe SDK does the computing tasks. On the Internet, not much is written about using qt for android, but here was the task of making friends qt and android studio. To work with the pros, Android NDK is used and everything is implemented through the use of JNI. Working with JNI is a pretty interesting thing in itself. In nete you can find a lot of articles on this topic (for example, this wonderful cycle) However, I'm interested in JNI in terms of using it with Qt. Again, what is the problem, you ask? We take high-grade sorsas, make a shared lib, connect to the project in the android studio and get profit! Here, as for example here. And here the fun begins ...

Using qt in android studio


As you remember, I indicated above that
it was written in Qt and used to draw text / arrows / areas and everything else
.
To draw a graphic primitive in QT, we do not need to create an instance of QApplication or QGuiApplication. Even QCoreApplication - and he is not needed! But for drawing text without QApplication or QGuiApplication, you can’t do anything. So what's the problem, you ask? The problem occurs just at the time the constructor is called:

QApplication a(argc, argv);

If you create a library, there is some function in it that calls the QApplication constructor, and then call it through JNI from the android studio application, then immediately say:
This application failed to start because it could not find or load the Qt platform plugin "android".

Who is to blame ? What to do?


Classic option. Learn materiel!


The first thing I decided to do was google a solution to the problem on the Internet. I did not find an exact match, but in a fairly large number of posts people complained about similar problems for the Windows plugin. So I tried everything that was mentioned here , but, alas, no solution (working for me!) Was found.

In search of an answer to my questions, I came across such a rather curious blog, as I understand the author qt for android. The blog is very interesting, but in it the author focuses (again, my IMHO) on developing from C ++ and launching all the good from qt creator. Honestly, this approach didn’t suit me for one reason: debugging the Java part from Qt is almost impossible (you can only compile the code, then wait for the studio to attach from Android and watch what is happening from there), and I also have a fairly large number of different layoutov, custom views, asynchronous tasks, but how can I put this stuff into a qt project and debug it normally? Honestly, I do not know.

The experiments


I tried to create a Qt application as well and run it on android. I ran it through qt-creator and, oddly enough, it started safely. I began to look in more detail at how the manifest, graddle, application code are arranged. I found such an interesting thing in the manifest:

In short, its meaning is clear. When I built apk applications, I pointed out that qt libraries should be inside apk and it is from there that you should load them to your application. Connecting the corresponding jar-s into the project on the android, registering in the android manifest what was in qt, placing qtшny .so plugins in the jniLibs folder did not give any effect.

Learning plugins


I tried already finally to load independently from java this unfortunate plugin libqtforandroid.so (before creating QApplication) by
System.loadLibrary ("plugins_platforms_android_libqtforandroid");
but still falling! True, the exception here was already different and more interesting:
I / Qt: qt start
05-17 11: 12: 33.975 11084-11084 / project name A / libc: Fatal signal 11 (SIGSEGV) at 0x00000000 (code = 1), thread 11084 (ndroid.gribview)
05-17 11: 12: 33.978 11084-11084 / project name A / libc: Send stop signal to pid: 11084 in void debuggerd_signal_handler (int, siginfo_t, void)

At least we have a clue where you can watch. Quickly by qt start we find the method we are interested in:

Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
{
    QT_USE_NAMESPACE
    typedef union {
        JNIEnv *nativeEnvironment;
        void *venv;
    } UnionJNIEnvToVoid;
    __android_log_print(ANDROID_LOG_INFO, "Qt", "qt start");
    UnionJNIEnvToVoid uenv;
    uenv.venv = Q_NULLPTR;
    m_javaVM = Q_NULLPTR;
    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        __android_log_print(ANDROID_LOG_FATAL, "Qt", "GetEnv failed");
        return -1;
    }
    JNIEnv *env = uenv.nativeEnvironment;
    if (!registerNatives(env)
           || !QtAndroidInput::registerNatives(env)
            || !QtAndroidMenu::registerNatives(env)
            || !QtAndroidAccessibility::registerNatives(env)
            || !QtAndroidDialogHelpers::registerNatives(env)) {
        __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
        return -1;
    }
    m_javaVM = vm;
    return JNI_VERSION_1_4;
}

Judging by the log, it fell somewhere in one of the registerNatives. So it was (I registered the logs in each of the registerNatives). He fell in

registerNatives(env)

Namely:

jmethodID methodID;
    GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "activity", "()Landroid/app/Activity;");
         __android_log_print(ANDROID_LOG_INFO, "Check Class 8", "activity ");
    jobject activityObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
         __android_log_print(ANDROID_LOG_INFO, "Check Class 9 ", " methodID ");
    GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "classLoader", "()Ljava/lang/ClassLoader;");
    __android_log_print(ANDROID_LOG_INFO, "Check Class 10", " classLoader ");
    if(activityObject!=nullptr)
    {
        __android_log_print(ANDROID_LOG_INFO, "No tull activityObject", " Not Null ");
    }
    if(methodID!=nullptr)
    {
        __android_log_print(ANDROID_LOG_INFO, "No tull methodID", " Not Null ");
    }
    m_classLoaderObject = env->NewGlobalRef(env->CallStaticObjectMethod(m_applicationClass, methodID));
    if(m_classLoaderObject!=nullptr)
    {
        __android_log_print(ANDROID_LOG_INFO, "No tull m_classLoaderObject", " Not Null ");
    }
    clazz = env->GetObjectClass(m_classLoaderObject);

The fall occurred on the last line. classLoaderObject turned out to be null. And it happened that activityObject is also null. Okay Before loading this ill-fated plugin, let's try to create activity for JNI. To do this, write the following lines in Java code:

        QtNative.setActivity(this, null);
        QtNative.setClassLoader(getClassLoader());

A small digression. The QtNative class lies in the jar files that we connect to the project. Moreover, this is a very curious class. There are methods in it:

       QtNative.loadBundledLibraries();
       QtNative.loadQtLibraries();

which should load the required plugins. For now, remember this, and return to connecting our plugin manually. Calling the QtNative methods setActivity and setClassLoader helped to get through:

registerNatives(env)

but the ambush was already in QtAndroidInput :: registerNatives (env). Function signatures did not match for the keyDown event. Basically, I do not need anything other than fonts and I commented out the following section of code:

  if (!registerNatives(env)
          /* || !QtAndroidInput::registerNatives(env)
            || !QtAndroidMenu::registerNatives(env)
            || !QtAndroidAccessibility::registerNatives(env)
            || !QtAndroidDialogHelpers::registerNatives(env)*/) {
        __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
        return -1;
    }

and it seems to have safely downloaded this plugin. We launch the application, load the plugin, call QApplication and ... catch our exceptionally familiar exception:
This application failed to start because it could not find or load the Qt platform plugin "android".

Moreover, the challenge

       QtNative.loadBundledLibraries();
       QtNative.loadQtLibraries();

also did not solve the problem. Good. Okay. We climb into the sorts of creating a constructor. By exception, we quickly find the method:

static void init_platform(const QString &pluginArgument, const QString &platformPluginPath, const QString &platformThemeName, int &argc, char **argv)
{
    // Split into platform name and arguments
    QStringList arguments = pluginArgument.split(QLatin1Char(':'));
    const QString name = arguments.takeFirst().toLower();
    QString argumentsKey = name;
    argumentsKey[0] = argumentsKey.at(0).toUpper();
    arguments.append(QLibraryInfo::platformPluginArguments(argumentsKey));
   // Create the platform integration.
    QGuiApplicationPrivate::platform_integration = QPlatformIntegrationFactory::create(name, arguments, argc, argv, platformPluginPath);
    if (QGuiApplicationPrivate::platform_integration) {
        QGuiApplicationPrivate::platform_name = new QString(name);
    } else {
        QStringList keys = QPlatformIntegrationFactory::keys(platformPluginPath);
        QString fatalMessage
                = QStringLiteral("This application failed to start because it could not find or load the Qt platform plugin \"%1\".\n\n").arg(name);
       ....

Good. We are looking for where this method is called from:

void QGuiApplicationPrivate::createPlatformIntegration()
{
    // Use the Qt menus by default. Platform plugins that
    // want to enable a native menu implementation can clear
    // this flag.
    QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, true);
    // Load the platform integration
    QString platformPluginPath = QLatin1String(qgetenv("QT_QPA_PLATFORM_PLUGIN_PATH"));
    QByteArray platformName;
#ifdef QT_QPA_DEFAULT_PLATFORM_NAME
    platformName = QT_QPA_DEFAULT_PLATFORM_NAME;
#endif
    QByteArray platformNameEnv = qgetenv("QT_QPA_PLATFORM");
    if (!platformNameEnv.isEmpty()) {
        platformName = platformNameEnv;
    }
    QString platformThemeName = QString::fromLocal8Bit(qgetenv("QT_QPA_PLATFORMTHEME"));
    // Get command line params
    QString icon;
    int j = argc ? 1 : 0;
    for (int i=1; i

То бишь, нам можно через argc и argv передать аргументы, где надо искать этот плагин. Сразу оговорюсь, я пробовал в дебаггере qt запускать приложение под андроид, и там argc и argv соответственно равны: 1 и имя_нашей_библиотеки_которую_собирает_qt, но никак не плагин. Попробуем присвоить argc и argv соответствующие значения:

char *SDKEnvironment::argv[] = {"-platform libplugins_platforms_android_libqtforandroid.so:plugins/platforms/android/libqtforandroid.so -platformpluginpath /data/app-lib/папка_для_jniLibs"};

Неа, не сработало.

Решение


Честно говоря, сроки поджимают, а дальше заниматься изучением что и где не сработало — у меня нету силвремени. Решение, которое мне помогло, следующее:

  1. Создадим в qt не apk, не so, а aar. Для этого идём в qt creator и находим gradle файл, а в нём меняем строчку apply plugin: 'com.android.applicatioin' на apply plugin: 'com.android.library' . Таким образом мы создаём aar файл, а не apk
  2. Теперь добавим его в наше приложение в андроид студии. Идём в New->Module выбираем import aar, затем правой кнопкой мышки щёлкаем на наш модуль, выбиараем Open Module Settings, идём во вкладку dependency и добавляем зависимость к qtному модулю

Затем я перенёс всё jni, которое было у меня в андроид студии, в qt. Попробовал снова создать QApplication — и всё заработало.

Резюме


Я почти что уверен, что есть и другой способ решить эту проблему. Если кто-нибудь укажет, где я ошибся — то было бы просто замечательно. В интернете я не нашёл решения проблемы, поэтому предлагаю своё.

Read Next