⬆️ ⬇️

Android development using qt and android studio part two

Good day to all visitors and users of Habr!



Recently, I published an article on emotions, where I told about all my misadventures when trying to create a qt application (namely, to call and use
QApplication a(argc, argv); 
and use it with android studio. A “solution” was found that was extremely crutch. Now I had a weekend to figure out how to work with qt without such crutches from the android studio. Anyone interested is welcome under the cat!



Formulation of the problem



So, before moving on, and for anyone who has not read my first article or forgot, I’ll remind you what I need to do. I have an ios application on objective c , which uses qt library to calculate and draw some things. I emphasize. It is the ios application and a small library on qt (in the first article I crookedly expressed myself and understood that my qt application was under ios. No. On qt, only a small library). I need to port it to android. I successfully implemented a graphical interface, logic, connected qt library. What is the problem? The problem is that when I try to call the JNI function from the Java part, and this function executes the following code
 QApplication a(argc, argv); 
then i get an exception:
This was not the case for the Qt platform plugin "android".


I need a call to the qt constructor of the application so that I can draw text in qt. Therefore, I need to deal with this problem. Option: “I didn’t like to rewrite everything to qt” (after all, the application was already ready for Java in the android studio), therefore, after many, many hours, I suggested a variant: to create a project for android in QtCreator. Collect aar from it and import it into the android studio and use it (the details of this whole kitchen in the previous article). Obviously, this is a decisive ... as if to put it mildly ... bicycle-crutch. And moreover, as it turned out containing a flaw (about him in ps. The first article). Therefore, here we analyze how to achieve the desired effect in a more correct way.



Decision



The whole kitchen with qt for android works in the assumption that we have 2 classes:

Therefore, we need to add them to our project. These two classes can be found in the folder where qt is installed for android. I have this C: \ Qt \ QT.Android \ 5.5 \ android_armv7 \ src \ android \ java \ src \ org \ qtproject \ qt5 \ android \ bindings.

QtApplication can be added to the project itself, only to replace the following code sections there:

')

  ___QT_.class.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes()); 
and
 if (-1 == stackDeep) { String activityClassName = ___QT_.class.getCanonicalName(); for (int it=0;it<elements.length;it++) if (elements[it].getClassName().equals(activityClassName)) { stackDeep = it; break; } } 


Fine. We now turn to the QtActivity class. Everything that is there should be our QtActivity. In my case it was MainViewController, into which I copied all the code from QtActivity. Next, let's pay attention to the OnCreate method ...



 super.onCreate(savedInstanceState); try { m_activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); for (Field f : Class.forName("android.R$style").getDeclaredFields()) { if (f.getInt(null) == m_activityInfo.getThemeResource()) { QT_ANDROID_THEMES = new String[] {f.getName()}; QT_ANDROID_DEFAULT_THEME = f.getName(); } } } catch (Exception e) { e.printStackTrace(); finish(); return; } try { setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null)); } catch (Exception e) { e.printStackTrace(); } if (Build.VERSION.SDK_INT > 10) { try { requestWindowFeature(Window.class.getField("FEATURE_ACTION_BAR").getInt(null)); } catch (Exception e) { e.printStackTrace(); } } else { requestWindowFeature(Window.FEATURE_NO_TITLE); } if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) { QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState); return; } m_displayDensity = getResources().getDisplayMetrics().densityDpi; ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME + "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t"; if (null == getLastNonConfigurationInstance()) { // if splash screen is defined, then show it if (m_activityInfo.metaData.containsKey("android.app.splash_screen_drawable")) getWindow().setBackgroundDrawableResource(m_activityInfo.metaData.getInt("android.app.splash_screen_drawable")); else getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000)); if (m_activityInfo.metaData.containsKey("android.app.background_running") && m_activityInfo.metaData.getBoolean("android.app.background_running")) { ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t"; } else { ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t"; } startApp(true); } } 


There is a subtlety. Where will we load layouts from layouta? That is, where to insert the following piece of code:
  setContentView(R.layout.form_name); 
If, insert it before startApp (true); then earn nothing! Therefore, I will simply advise, while here without explanation, to do something like this:
  startApp(true); } new Thread(new Runnable() { @Override public void run() { while (!MyLibSo.isQtReady()) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } runOnUiThread(new Runnable() { @Override public void run() { initializeGUI(); } }); } }).start(); 


Here, MyLibSo.isQtReady () is my native function. Its main task is to check whether QApplication has already been created in the c-part. If created, then start drawing our layouta in initializeGUI (); There already and call
 setContentView(R.layout.form_name); 


Placement qt lib



Now the most interesting part. The main problem arises in the fact that qt-plugins are not loaded in a normal way ( in the previous article this is described ). Copied code from QtActivity and QApplication should solve all these problems. But for this he needs either. Where to get them?



The second option is deleted immediately - you never know, the user will delete the directory and that's it. The first option is a bit more interesting. It is carried out using the Ministro service. We will be interested in the third option. First we go to string resources and define the following:



  <string name="ministro_not_found_msg">Can\'t find Ministro service.\nThe application can\'t start.</string> <string name="ministro_needed_msg">This application requires Ministro service. Would you like to install it?</string> <string name="fatal_error_msg">Your application encountered a fatal error and cannot continue.</string> <array name="qt_sources"> <item>https://download.qt-project.org/ministro/android/qt5/qt-5.4</item> </array> <array name="bundled_libs"> <item>MyLibWithQt</item> </array> <array name="qt_libs"> <item>gnustl_shared</item> <item>Qt5Core</item> <item>Qt5Gui</item> <item>Qt5Widgets</item> </array> <array name="bundled_in_lib"> <item> libplugins_platforms_android_libqtforandroid.so:plugins/platforms/android/libqtforandroid.so </item> <item>libplugins_platforms_libqminimal.so:plugins/platforms/libqminimal.so</item> <item>libplugins_platforms_libqoffscreen.so:plugins/platforms/libqoffscreen.so</item> </array> <array name="bundled_in_assets"> </array> 


Everything connected with the ministro is not interesting for us. MyLibWithQt is from lib, using qt. Pay attention to libplugins_platforms_android_ All plugins that go further should contain just such a beginning !!! Now, in the manifest, insert the following code:



 <meta-data android:name="android.app.lib_name" android:value="MyLibWithQt " /> <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources" /> <meta-data android:name="android.app.repository" android:value="default" /> <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs" /> <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs" /> <!-- 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" /> 


Here we indicate which ones he should download from the apk file and how our lib is called with qt. Of course, do not forget to put all these plugins in the jniLibs folder. Compile, run, create in C code
 QApplication a(argc, argv); 
and ... we get the same proc familiar exception. Our QApplication is not created.



Last attack


How so? It seems everything is done correctly, and why it does not work? Everything turned out ... judge for yourself. The libqtforandroid.so plugin will only be loaded correctly if our library has a main function . If it is not there, then one of the jni methods in this plugin will not be fully completed and the application will not be created! Therefore, we go to our library using qt, and define the function main there. It makes sense to create a QApplication in it. Now everything, everything works.



Summary



So let's summarize. To work with qt from Android studio we need:

1. Take and modify QtApplication (a couple of places in the code) and define your QtActivity (to call up your markup, setContentView is called later, how QtApplication will be created). All this add yourself to the code.

2. Set up the manifest to load the libraries from apk.

3. In the library in which qt is used, add the function main.

four. ??????

5. Profit !!!



Update A wonderful series of articles about the development for android on qt here

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



All Articles