Розробка Android додатків з використанням qt і android studio частина друга

Доброго часу доби всім відвідувачам та користувачам Хабра!

Нещодавно я на емоціях опублікував статті, де я розповідав про всі свої пригоди під час спроби створити qt додаток ( а саме викликати і використовувати
QApplication a(argc, argv);
і використовувати його за допомогою андроїд студії. Було знайдено «рішення», яке було надзвичайно костыльным. Тепер у мене були вихідні, щоб розібратися як треба працювати з qt без таких милиць з андроїд студії. Всім кому цікаво — ласкаво просимо під кат!

Постановка завдання

Отже, перш ніж рухатися далі і для всіх, хто не читав мою першу статтю або забув, нагадаю що мені треба зробити. У мене є 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? Тобто, куди вставити наступний фрагмент коду:
setContentView(R. layout.form_name);
Якщо, його вставляти до startApp(true);, то нічого не запрацює! Тому, я просто пораджу поки тут без пояснень зробити щось на кшталт такого:
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);
та… отримуємо те ж саме прокл знайоме виняток. Не створюється наше 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 тут
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.