Здравствуйте! Уверен, многие слышали о том, что Qt очень хорош для кросплатформенной разработки мобильных приложений. Однако, для решения некоторых задач приходится иметь дело с нативным кодом (Java, Objective-C), к примеру, вызов камеры, галереи, вызов стороннего api.
В этой статье на простом примере задания прозрачности для status bar я покажу, как осуществляется вызов нативного кода Java и Objective-C.
Andoid
Возможность использования прозрачного status bar появилась в Android 4.4 KitKat. Для того, чтобы status bar стал прозрачным, необходимо в Activity нашего проекта указать флаг прозрачности для Window (не путать с QQuickWindow, который используется для отображения QML).
Открываем вкладку Проекты > Добавить сборку под Andoid > “Сборка” > Нажимаем “Подробнее” в “Собрать Android APK” > “Создать шаблоны”.
Тем самым мы создали AndroidManifest, папку с ресурсами и файлы gradle, которые будут находится в папке android. Для размещения нашего java класса создадим папку src в папке android.
Создадим файл MyActivity.java. Важно, чтобы путь до файла совпадал с именем пакета, т.е. используя пакет с именем com.example.myPackage, путь должен быть android/src/com/example/myPackage/MyActivity.java
package com.example.myPackage;
import org.qtproject.qt5.android.bindings.QtActivity;
import android.app.Activity;
import android.os.Bundle;
public class MyActivity extends QtActivity
{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
Теперь, нам надо задать имя Activity в AndroidManifest.xml. Ищем
android:name="com.example.myPackage.MyActivity"
и меняем на
android:name="org.qtproject.qt5.android.bindings.QtActivity"
Этими нехитрыми манипуляциями мы переопределили стандартную QtActivity.
Функция выставляющая флаг прозрачности status bar и пример её применения:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTranparentStatusBar();
}
void function setTranparentStatusBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
getWindow().setStatusBarColor(Color.TRANSPARENT);
} else {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
Функция, возвращающая высоту status bar:
public int statusBarHeight() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return 0;
}
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
Для того, чтобы вызвать метод Java класса из QML потребуется написать С++ класс, который будет использовать JNI. Для работы с JNI добавим в наш *.pro файл модуль Android:
QT += androidextras.
Создадим singleton класс DeviceInfo.
DeviceInfo.h:
#pragma once
#include <QObject>
class DeviceInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(int statusBarHeight READ statusBarHeight)
public:
DeviceInfo(QObject *parent = NULL);
static DeviceInfo &instance(QObject *parent = 0);
Q_INVOKABLE int statusBarHeight();
private:
static DeviceInfo _instance;
};
DeviceInfo.cpp:
#include "DeviceInfo.h"
#if defined(Q_OS_ANDROID)
#include <QAndroidJniObject>
#include <QtAndroidExtras>
#include <QtAndroid>
#endif
DeviceInfo::DeviceInfo(QObject *parent)
: QObject(parent)
{}
DeviceInfo &DeviceInfo::instance(QObject *parent)
{
static DeviceInfo instance(parent);
return instance;
}
int DeviceInfo::statusBarHeight()
{
#if defined (Q_OS_ANDROID)
QAndroidJniObject activity = QtAndroid::androidActivity();
jint height = activity.callMethod<jint>("statusBarHeight");
return (int) height;
#endif
return 0;
}
Далее, определим наш класс в QML:
view.rootContext()->setContextProperty("DeviceInfo", &DeviceInfo::instance());
Всё готово, осталось лишь вызвать метод statusBarHeight в QML:
Rectangle {
width: parent.width
height: DeviceInfo.statusBarHeight()
}
Итог на экране:
iOS
Возможность задания различного стиля у status bar в iOS появилась в iOS 7.0. Для того, чтобы status bar в нашем приложение был прозрачен, нам нужно сделать 3 вещи:
- Изменить info.plist, а именно, изменить ключ UIViewControllerBasedStatusBarAppearance:
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
- Для отображения QQuickView или QQuickWindow использовать метод showFullScreen() вместо show().
- Выставить у status bar стиль UIStatusBarStyleLightContent
Если с первыми двумя пунктами всё понятно, разберём третий более подробно. Изменить стиль у status bar'a можно следующим методом на Objective-C:
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
Функция, возвращающая высоту status bar’a:
[UIApplication sharedApplication].statusBarFrame.size.height
Для того, чтобы код на Objective-C работал в классе DeviceInfo, нам потребуется поменять разрешение исходника с .cpp на .mm. Поэтому в .pro файле сделаем следующее:
HEADERS += Include/DeviceInfo.h
!ios {
SOURCES += Source/DeviceInfo.cpp
}
ios {
OBJECTIVE_SOURCES += Source/DeviceInfo.mm
}
DeviceInfo.mm:
#include "DeviceInfo.h"
#import <UIKit/UIKit.h>
DeviceInfo::DeviceInfo(QObject *parent)
: QObject(parent)
{
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
}
DeviceInfo &DeviceInfo::instance(QObject *parent)
{
static DeviceInfo instance(parent);
return instance;
}
int DeviceInfo::statusBarHeight()
{
return [UIApplication sharedApplication].statusBarFrame.size.height;
}
Итог на экране:
Заключение
Я постарался осветить как можно подробнее каждый шаг, чтобы на основе примеров из статьи вы могли легко дополнить свой мобильный проект нативным кодом. Исходный код примера смотрите на GitHub.
Комментарии (17)
Zifix
29.01.2017 03:10Я понимаю, что цель была показать вызов нативного кода, но как минимум на iOS можно обойтись без него:
Info.plist:
<key>UIRequiresFullScreen</key> <true/> <key>UIStatusBarStyle</key> <string>UIStatusBarStyleLightContent</string> <key>UIViewControllerBasedStatusBarAppearance</key> <false/>
main.qml:
ApplicationWindow { visible: true flags: Qt.MaximizeUsingFullscreenGeometryHint ... }
Vonabirg
29.01.2017 03:55Вы совершенно правы, цель именно показать вызов нативного кода. По факту, вы сделали всё то же самое, что и я, но другим путём.
broken
30.01.2017 08:12+1Почему вы используете непереносимую прагму вместо переносимого инклуд-гарда?
DarkEld3r
30.01.2017 12:56Просто любопытно: какие компиляторы, из тех, которыми можно собрать Qt, не поддерживают
pragma once
?broken
30.01.2017 13:25Я не спорю, это модно. Я бы и сам его использовал, и вроде бы как даже википедии и прочие источники уверяют в том, что с ним все ок. Однако, рекомендую почитать вот этот пост на SO: Является ли #pragma once частью стандарта
Кроме того, само то, что это extension говорит о том, что это implementation-defined-вещь. Собственно, если почитать этот пост на SO, там тоже про это сказано.DarkEld3r
30.01.2017 20:57+1Это (и разное другое) читал, о потенциальных подводных камнях в курсе. Собственно, раньше, как раз начитавшись "умных советов", использовал и то и другое — мол компилятор может уметь обрабатывать эту прагму быстрее, а если не поддерживаемые прагмы по стандарту просто пропускаются. Затем, после участия в некотором количестве вполне себе кроссплатформенных проектов, где использовали
pragma once
и "не заморачивались", стал проще к этому относиться.
Собственно, интересно было бы услышать о реальных граблях, а не гипотетических ситуациях. В конце концов, можно аргументировать тем, что случайно могут оказаться одинаковые гарды и проблема будет тоже не самой очевидной.
ChapayHabr
30.01.2017 13:36+1Есть вопрос по лицензии qt на мобилах
либа же может как динамическая подключаться и тогда можно использовать ee бесплатную opensource версию?
или есть какие-то подводные камни
Я тут почитал ответ на «Commercial edition of iOS or Android etc?» и чето ничего не понял )))
https://wiki.qt.io/Licensing-talk-about-mobile-platforms
может кто в теме что и как?
lieff
Какой объем у apk и ipa получается на таком простом примере?
FedyaShlyapkin
из опыта, не менее 32мб
Zifix
Вы, вероятно, говорите о размере несжатого приложения, ipa получится в районе 15 Mb, может даже меньше.
Vonabirg
Немного цифр:
apk занимает 7,1 мб
ipa занимает 20 мб
Вдобавок вышел Qt 5.8, а с ним вышел Qt Lite, там обещают ещё более легкий apk и ipa. К сожалению, до Qt Lite я не успел добраться пока что, а потому цифр не скажу.
QtRoS
Подтверждаю 7 мб, протестировал на приложении gallery, демонстрирубщем компоненты Qt Quick Controls 2. В эти 7 мб входят Gui, Network (с отличным QNetworkAccessManager'ом внутри), естественно Core (с QJson и прочими прелестями). Совсем не много по меркам современной разработки!
P.S. По Qt Lite заметил странную вещь — в документации ничего конкретного нет, в статьях тоже. Я не хочу сказать, что нас обманули — работу по перестройке системы сборки проделали большую, практически с нуля переписали, но готового howto пока нет.
lieff
Интересно чем вызвано такой отрыв ipa от apk. А сколько native архитектур в apk? armeabi, armeabi-v7a, x86?
Еще встречался с тем, что выложенный в стор ipa становился еще больше, рядом с Payload добавлялась папочка SupportFiles или как то так. В ней были файлы в основном относящиеся в Swift, не смотря на то что в Payload эти dylib были и для запуска на моем устройстве этого было достаточно, но они отличаются, возможно для поддержки разных версий устройств и ios. Было бы интересно если бы кто провел такой эксперимент с Qt приложением.
QtRoS
Один и тот же вопрос из раза в раз. Популярнее только вопрос «А вы сами проверяли PVS Studio с помощью PVS Studio?» к постам Andrey2008. Цена не так велика, как профит от покрытия всех популярных платформ одним исходным кодом. Тем более как действительно заметил Vonabirg ниже разработка библиотеки в этом направлении движется, и пока Ваш продукт будет разрабатываться и готовиться к выходу, дела станут еще лучше.
lieff
Да я собственно не против, я потому и спрашиваю что профит кроссплатформа как раз очень интересен.