📜 ⬆️ ⬇️

Android development using qt and android studio

Irrelevant, except for setting the problem . The correct solution is in the next article .
Good afternoon, dear habrovchane! In this article I want to talk about my experience using qt and android studio. Namely, how I had to draw text in qt and transfer it to the android studio. Despite the simplicity of the problem, it took me quite a long time to solve it and maybe someone will save a lot of time somewhere. The article in some sense claims the invention of the bicycle, but I have not found a solution on the Internet. Who cares - welcome under the cat!

A little about the task itself


Recently, I faced the task of porting an application from ios to android. The main pain when porting was working with the SDK application. It was written in Qt and was used to draw text / arrows / areas and so on. That is, the application was written in objective c , and used the qt library, and was not a qt project. Therefore, the first step was the issue of 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 to do in the android studio, and let our qtshny SDK do the computational tasks. On the Internet, not much is written about using qt for android, but here was the task of making friends with qt and android studio. To work with the pluses, the Android NDK is used and 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 am interested in JNI in terms of using it with Qt. Again, what's the problem, you ask? We take sorshnye sorsy, we make a shared lib, we connect to the project in the android studio and we get profit! Here, as for example here. And here the most interesting begins ...

Using qt in android studio


As you remember, I indicated above that
it was written in Qt and was 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 is not needed! But to draw text without QApplication or QGuiApplication can not do. So what's the problem, you ask? The problem comes just at the time of the constructor call:

QApplication a(argc, argv); 

If you create a library, in it any function that calls the QApplication constructor, and then call it via JNI from the android studio application, then immediately catch:
This was not the case for the Qt platform plugin "android".

Who is guilty What to do?


The 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 plugin for Windows. So I tried everything that was indicated 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 understood the author of qt under android. The blog is very interesting, but the author focuses on it (again, my IMHO) on the development from the ++ and the launch of all the good from the qt creator. To be honest, this approach did not suit me very well for one reason: debugging Java parts from Qt is almost impossible (you can only compile the code, then wait for attachments from the android studio and watch the events from there), and I also have quite a lot of different layouts. custom, asynchronous tasks, and how it is good to thrust the project into qt and normally to debug? Honestly, I do not know.

Experiments


I also tried to create a Qt application and run it on an android. I launched it through qt-creator and strangely enough it started up safely. I began to look in more detail how the manifest, graddl, application code. I discovered such an interesting thing in the manifest:
 <!-- Deploy Qt libs as part of package --> <meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/> <meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/> <meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/> <!-- Run with local libs --> <meta-data android:name="android.app.use_local_qt_libs" android:value="1"/> <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/> <meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so"/> <meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidAccessibility.jar:jar/QtAndroid-bundled.jar:jar/QtAndroidAccessibility-bundled.jar"/> <meta-data android:name="android.app.static_init_classes" android:value=""/> 

In short, its meaning is clear. When I compiled apk applications, I indicated that the qt libraries must be inside apk and it is from there that you need to ship them to your application. Connecting the corresponding jar-s to the project on android, prescribing what was in qt in the android manifesto, placing qtshny .so plug-ins in the jniLibs folder did not have any effect.

Learning plugins


I tried to finally load this unfortunate plugin libqtforandroid.so (before creating QApplication) by java by myself
System.loadLibrary ("plugins_platforms_android_libqtforandroid");
, but it still fell! True, the exception here was another 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 on 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 some of the registerNatives. So it was (I registered the logs in each of the registerNatives). He fell into

 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, we will try to create an activation for JNI. To do this, write in Java code the following lines:

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

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

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

which should load the required plugins. For now, remember this, and return to connecting our plug-in manually. Calling the QtNative setActivity and setClassLoader methods helped skip:

 registerNatives(env) 

but the ambush was already in QtAndroidInput :: registerNatives (env). The function signatures for the keyDown event did not match. In principle, I do not need anything except fonts and I commented out the following piece 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 loaded this plugin safely. We start the application, load the plugin, call QApplication and ... catch our osto familiar exception:
This was not the case for the Qt platform plugin "android".

Moreover, the challenge

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

also did not solve the problem. Good. Okay. Use the sorts of constructor creation. 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, from where we call this method:

 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; i++) { if (argv[i] && *argv[i] != '-') { argv[j++] = argv[i]; continue; } const bool isXcb = platformName == "xcb"; QByteArray arg = argv[i]; if (arg.startsWith("--")) arg.remove(0, 1); if (arg == "-platformpluginpath") { if (++i < argc) platformPluginPath = QLatin1String(argv[i]); } else if (arg == "-platform") { if (++i < argc) platformName = argv[i]; } else if (arg == "-platformtheme") { if (++i < argc) platformThemeName = QString::fromLocal8Bit(argv[i]); } else if (arg == "-qwindowgeometry" || (isXcb && arg == "-geometry")) { if (++i < argc) windowGeometrySpecification = QWindowGeometrySpecification::fromArgument(argv[i]); } else if (arg == "-qwindowtitle" || (isXcb && arg == "-title")) { if (++i < argc) firstWindowTitle = QString::fromLocal8Bit(argv[i]); } else if (arg == "-qwindowicon" || (isXcb && arg == "-icon")) { if (++i < argc) { icon = QString::fromLocal8Bit(argv[i]); } } else { argv[j++] = argv[i]; } } if (j < argc) { argv[j] = 0; argc = j; } init_platform(QLatin1String(platformName), platformPluginPath, platformThemeName, argc, argv); if (!icon.isEmpty()) forcedWindowIcon = QDir::isAbsolutePath(icon) ? QIcon(icon) : QIcon::fromTheme(icon); } 

I mean, we can pass arguments through argc and argv, where to look for this plugin. Immediately make a reservation, I tried to launch the application under the android in the qt debagger, and there argc and argv are respectively equal: 1 and the name of our_lib_library_, which is collected by qt, but not a plugin. Let's try to assign argc and argv to the corresponding values:

 char *SDKEnvironment::argv[] = {"-platform libplugins_platforms_android_libqtforandroid.so:plugins/platforms/android/libqtforandroid.so -platformpluginpath /data/app-lib/__jniLibs"}; 

Nope, it did not work.

Decision


To be honest, the deadlines are tight, and then I’m not sure what and where it didn’t work - I don’t have the time. The solution that helped me is the following:

  1. Let's create in qt not apk, not so, but aar. To do this, go to the qt creator and find the gradle file, and in it we change the line apply plugin: 'com.android.applicatioin' to apply plugin: 'com.android.library' . So we create aar file, not apk
  2. Now add it to our application in the android studio. Go to New-> Module, select import aar, then right-click on our module, select Open Module Settings, go to the dependency tab and add the dependency to the qt module

Then I transferred all the jni that I had in the android studio to qt. I tried to create QApplication again - and it all worked.

Summary


I am pretty sure that there is another way to solve this problem. If someone indicates where I was wrong, it would be just great. I did not find a solution to the problem on the Internet, so I offer my own.

Source: https://habr.com/ru/post/301852/


All Articles