Доброго времени суток всем посетителям и пользователям Хабра!
Недавно я на эмоциях опубликовал статью, где я рассказывал о всех своих злоключениях при попытки создать qt приложение ( а именно вызвать и использовать
Итак, прежде чем двигаться дальше и для всех, кто не читал мою первую статью или забыл, напомню что мне надо сделать. У меня есть ios приложение на objective c, которое для расчёта и прорисовки некоторых вещей использует qt библиотеку. Подчёркиваю. Именно ios приложение и небольшая библиотека на qt (в первой статье я криво выразился и меня поняли, что у меня qt приложение было под ios. Нет. На qt только небольшая библиотека). Мне надо портировать его на андроид. Я благополучно реализовал графический интерфейс, логику, подключил qt библиотеку. В чём проблема? Проблема в том, что когда пытаюсь вызвать JNI функцию из Java части, а эта функция выполняет следующий код
Мне необходим вызов конструктора qt приложения для того, чтобы я мог рисовать текст в qt. Поэтому мне необходимо р��збираться с этой проблемой. Вариант: «Всё переписать на qt» меня не устраивал (ведь приложение уже было готово на Java в андроид студии), поэтому, после много-много часов мною был предложен варинт: создать в QtCreator проект под андроид. Собрать из него aar и импортировать его в андроид студию и пользоваться им (подробнобности всей этой кухни в предыдущей статье). Очевидно, что это решние… как бы помягче выразиться… велосипедно-костыльное. И более того, как оказалось содержащее изъян ( о нём в ps. первой статьи). Поэтому, здесь разберём как добиться нужного эффекта более правильным способом.
Вся кухня с qt для андроида работает в предположении, что у нас есть 2 класса:
Поэтому, нам необходимо их добавить к себе в проект. Эти два класса можно найти в папке, где установлено qt для андроида. У меня это C:\Qt\QT.Android\5.5\android_armv7\src\android\java\src\org\qtproject\qt5\android\bindings.
QtApplication можно добавить себе в проект, только заменить там следующие участки кода:
Отлично. Перейдём теперь к классу QtActivity. Всё, что там есть, должно быть и у нашей QtActivity. В моём случае это был MainViewController, в который я скопипастил весь код из QtActivity. Дальше, обратим внимание на метод OnCreate…
Здесь есть тонкость. Где мы будем загружать разметку из layouta? То есть, куда вставить следующий участок кода:
Здесь MyLibSo.isQtReady() — моя нативная функция. Её основная задача проверять, было ли уже создано в с-части QApplication. Если создано, то запускаем рисование нашего layouta в initializeGUI(); Там уже и вызываем
Теперь наибо��ее интересная часть. Главная проблема возникает в том, что не грузятся по-нормальному qt-плагины ( в предыдущей статье об этом расписано). Скопипащенный код из QtActivity и QApplication должен решить все эти проблемы. Но для этого ему нужны либы. Где их взять?
Второй вариант удаляем сразу — мало ли, пользователь удалит каталог и всё. Первый вариант чуть более интересный. Он осуществляется при помощи сервиса Ministro. Нас будет интересовать третий вариант. Для начала идём в строковые ресурсы и определяем следующее:
Всё, что связано с министро — нам не интересно. MyLibWithQt — это с либа, использующая qt. Обратите внимание на libplugins_platforms_android_ Все плагины, которые идут дальше, должны содержать именно такое начало!!! Теперь, в манифесте вставляем следующий код:
Здесь мы указываем, какие либы надо ему загрузить из apk файла и как называется наша либа с qt. Разумеется, не забываем положить все эти плагины в папку jniLibs. Компилируем, запускаем, создаём в С кодепрокл знакомое исключение. Не создаётся наше QApplication.
Как же так? Вроде всё сделано правильно, а почему не работает? Всё оказалось… судите сами. Плагин libqtforandroid.so будет корректно загружен только в том случае, если в нашей библиотеке есть функция main. Если её нет, то один из jni методов в данном плагине не доработает до конца и приложение не будет создано! Поэтому, идём в нашу библиотеку, использующую qt, и определяем там функцию main. Логично, в ней и создать QApplication. Теперь всё, всё работает.
Итак, подведём итоги. Чтобы работать с qt из Android studio нам необходимо:
1. Взять и видоизменить QtApplication (пара мест в коде) и определить свою QtActivity (чтобы вызвать свою разметку, setContentView вызываем позже, как создано будет QtApplication). Всё это добавить себе в код.
2. Настроить манифест, чтобы он подгружал библиотеки из apk.
3. В либу, в которой используется qt, добавить функцию main.
4. ??????
5. Profit!!!
Update Замечательный цикл статей про разработку для андроида на qt тут
Недавно я на эмоциях опубликовал статью, где я рассказывал о всех своих злоключениях при попытки создать qt приложение ( а именно вызвать и использовать
и использовать его при помощи андроид студии. Было найдено «решение», которое было чрезвычайно костыльным. Теперь у меня были выходные, чтобы разобраться как надо работать с qt без таких костылей из андроид студии. Всем кому интересно — добро пожаловать под кат!QApplication a(argc, argv);
Постановка задачи
Итак, прежде чем двигаться дальше и для всех, кто не читал мою первую статью или забыл, напомню что мне надо сделать. У меня есть ios приложение на objective c, которое для расчёта и прорисовки некоторых вещей использует qt библиотеку. Подчёркиваю. Именно ios приложение и небольшая библиотека на qt (в первой статье я криво выразился и меня поняли, что у меня qt приложение было под ios. Нет. На qt только небольшая библиотека). Мне надо портировать его на андроид. Я благополучно реализовал графический интерфейс, логику, подключил qt библиотеку. В чём проблема? Проблема в том, что когда пытаюсь вызвать JNI функцию из Java части, а эта функция выполняет следующий код
то я получаю исключение:QApplication a(argc, argv);
This application failed to start because it could not find or load the Qt platform plugin «android».
Мне необходим вызов конструктора qt приложения для того, чтобы я мог рисовать текст в qt. Поэтому мне необходимо р��збираться с этой проблемой. Вариант: «Всё переписать на qt» меня не устраивал (ведь приложение уже было готово на Java в андроид студии), поэтому, после много-много часов мною был предложен варинт: создать в QtCreator проект под андроид. Собрать из него aar и импортировать его в андроид студию и пользоваться им (подробнобности всей этой кухни в предыдущей статье). Очевидно, что это решние… как бы помягче выразиться… велосипедно-костыльное. И более того, как оказалось содержащее изъян ( о нём в ps. первой статьи). Поэтому, здесь разберём как добиться нужного эффекта более правильным способом.
Решение
Вся кухня с qt для андроида работает в предположении, что у нас есть 2 класса:
- QtApplication
- QtActivity
Поэтому, нам необходимо их добавить к себе в проект. Эти два класса можно найти в папке, где установлено qt для андроида. У меня это C:\Qt\QT.Android\5.5\android_armv7\src\android\java\src\org\qtproject\qt5\android\bindings.
QtApplication можно добавить себе в проект, только заменить там следующие участки кода:
иИМЯ_КЛАССА_НАШЕЙ_QT_АКТИВИТИ.class.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes());
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; } }
Отлично. Перейдём теперь к классу QtActivity. Всё, что там есть, должно быть и у нашей QtActivity. В моём случае это был MainViewController, в который я скопипастил весь код из QtActivity. Дальше, обратим внимание на метод OnCreate…
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); } }
Здесь есть тонкость. Где мы будем загружать разметку из layouta? То есть, куда вставить следующий участок кода:
Если, его вставлять до startApp(true);, то ничего не заработает! Поэтому, я просто посоветую пока здесь без объяснений сделать нечто вроде такого:setContentView(R.layout.form_name);
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();
Здесь MyLibSo.isQtReady() — моя нативная функция. Её основная задача проверять, было ли уже создано в с-части QApplication. Если создано, то запускаем рисование нашего layouta в initializeGUI(); Там уже и вызываем
setContentView(R.layout.form_name);
Размещение qt либ
Теперь наибо��ее интересная часть. Главная проблема возникает в том, что не грузятся по-нормальному qt-плагины ( в предыдущей статье об этом расписано). Скопипащенный код из QtActivity и QApplication должен решить все эти проблемы. Но для этого ему нужны либы. Где их взять?
- Скачать из интернета.
- Указать Qt, где это всё лежит.
- Загрузить из apk
Второй вариант удаляем сразу — мало ли, пользователь удалит каталог и всё. Первый вариант чуть более интересный. Он осуществляется при помощи сервиса Ministro. Нас будет интересовать третий вариант. Для начала идём в строковые ресурсы и определяем следующее:
<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>
Всё, что связано с министро — нам не интересно. MyLibWithQt — это с либа, использующая qt. Обратите внимание на libplugins_platforms_android_ Все плагины, которые идут дальше, должны содержать именно такое начало!!! Теперь, в манифесте вставляем следующий код:
<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" />
Здесь мы указываем, какие либы надо ему загрузить из apk файла и как называется наша либа с qt. Разумеется, не забываем положить все эти плагины в папку jniLibs. Компилируем, запускаем, создаём в С коде
и… получаем то же самоеQApplication a(argc, argv);
Последняя напасть
Как же так? Вроде всё сделано правильно, а почему не работает? Всё оказалось… судите сами. Плагин libqtforandroid.so будет корректно загружен только в том случае, если в нашей библиотеке есть функция main. Если её нет, то один из jni методов в данном плагине не доработает до конца и приложение не будет создано! Поэтому, идём в нашу библиотеку, использующую qt, и определяем там функцию main. Логично, в ней и создать QApplication. Теперь всё, всё работает.
Резюме
Итак, подведём итоги. Чтобы работать с qt из Android studio нам необходимо:
1. Взять и видоизменить QtApplication (пара мест в коде) и определить свою QtActivity (чтобы вызвать свою разметку, setContentView вызываем позже, как создано будет QtApplication). Всё это добавить себе в код.
2. Настроить манифест, чтобы он подгружал библиотеки из apk.
3. В либу, в которой используется qt, добавить функцию main.
4. ??????
5. Profit!!!
Update Замечательный цикл статей про разработку для андроида на qt тут