Здравствуйте! Уверен, многие слышали о том, что Qt очень хорош для кросплатформенной разработки мобильных приложений. Однако, для решения некоторых задач приходится иметь дело с нативным кодом (Java, Objective-C), к примеру, вызов камеры, галереи, вызов стороннего api.


В этой статье на простом примере задания прозрачности для status bar я покажу, как осуществляется вызов нативного кода Java и Objective-C.


вжух


Andoid


Возможность использования прозрачного status bar появилась в Android 4.4 KitKat. Для того, чтобы status bar стал прозрачным, необходимо в Activity нашего проекта указать флаг прозрачности для Window (не путать с QQuickWindow, который используется для отображения QML).


Если кто не знает, как переопределить свою Activity от QtActivity

Открываем вкладку Проекты > Добавить сборку под 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()
}

Итог на экране:


android screenshot


iOS


Возможность задания различного стиля у status bar в iOS появилась в iOS 7.0. Для того, чтобы status bar в нашем приложение был прозрачен, нам нужно сделать 3 вещи:


  1. Изменить info.plist, а именно, изменить ключ UIViewControllerBasedStatusBarAppearance:

<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>

  1. Для отображения QQuickView или QQuickWindow использовать метод showFullScreen() вместо show().

  1. Выставить у 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;
}

Итог на экране:


ios screenshot


Заключение


Я постарался осветить как можно подробнее каждый шаг, чтобы на основе примеров из статьи вы могли легко дополнить свой мобильный проект нативным кодом. Исходный код примера смотрите на GitHub.

Поделиться с друзьями
-->

Комментарии (17)


  1. lieff
    28.01.2017 18:34

    Какой объем у apk и ipa получается на таком простом примере?


    1. FedyaShlyapkin
      29.01.2017 02:28
      -1

      из опыта, не менее 32мб


      1. Zifix
        29.01.2017 03:03
        +1

        Вы, вероятно, говорите о размере несжатого приложения, ipa получится в районе 15 Mb, может даже меньше.


    1. Vonabirg
      29.01.2017 03:49
      +2

      Немного цифр:
      apk занимает 7,1 мб
      ipa занимает 20 мб

      Вдобавок вышел Qt 5.8, а с ним вышел Qt Lite, там обещают ещё более легкий apk и ipa. К сожалению, до Qt Lite я не успел добраться пока что, а потому цифр не скажу.


      1. QtRoS
        29.01.2017 14:04

        Подтверждаю 7 мб, протестировал на приложении gallery, демонстрирубщем компоненты Qt Quick Controls 2. В эти 7 мб входят Gui, Network (с отличным QNetworkAccessManager'ом внутри), естественно Core (с QJson и прочими прелестями). Совсем не много по меркам современной разработки!

        P.S. По Qt Lite заметил странную вещь — в документации ничего конкретного нет, в статьях тоже. Я не хочу сказать, что нас обманули — работу по перестройке системы сборки проделали большую, практически с нуля переписали, но готового howto пока нет.


      1. lieff
        29.01.2017 15:03

        Интересно чем вызвано такой отрыв ipa от apk. А сколько native архитектур в apk? armeabi, armeabi-v7a, x86?
        Еще встречался с тем, что выложенный в стор ipa становился еще больше, рядом с Payload добавлялась папочка SupportFiles или как то так. В ней были файлы в основном относящиеся в Swift, не смотря на то что в Payload эти dylib были и для запуска на моем устройстве этого было достаточно, но они отличаются, возможно для поддержки разных версий устройств и ios. Было бы интересно если бы кто провел такой эксперимент с Qt приложением.


    1. QtRoS
      29.01.2017 12:07
      +1

      Один и тот же вопрос из раза в раз. Популярнее только вопрос «А вы сами проверяли PVS Studio с помощью PVS Studio?» к постам Andrey2008. Цена не так велика, как профит от покрытия всех популярных платформ одним исходным кодом. Тем более как действительно заметил Vonabirg ниже разработка библиотеки в этом направлении движется, и пока Ваш продукт будет разрабатываться и готовиться к выходу, дела станут еще лучше.


      1. lieff
        29.01.2017 15:15

        Да я собственно не против, я потому и спрашиваю что профит кроссплатформа как раз очень интересен.


  1. 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
        ...
    }
    


    1. Vonabirg
      29.01.2017 03:55

      Вы совершенно правы, цель именно показать вызов нативного кода. По факту, вы сделали всё то же самое, что и я, но другим путём.


  1. broken
    30.01.2017 08:12
    +1

    Почему вы используете непереносимую прагму вместо переносимого инклуд-гарда?


    1. DarkEld3r
      30.01.2017 12:56

      Просто любопытно: какие компиляторы, из тех, которыми можно собрать Qt, не поддерживают pragma once?


      1. broken
        30.01.2017 13:25

        Я не спорю, это модно. Я бы и сам его использовал, и вроде бы как даже википедии и прочие источники уверяют в том, что с ним все ок. Однако, рекомендую почитать вот этот пост на SO: Является ли #pragma once частью стандарта
        Кроме того, само то, что это extension говорит о том, что это implementation-defined-вещь. Собственно, если почитать этот пост на SO, там тоже про это сказано.


        1. DarkEld3r
          30.01.2017 20:57
          +1

          Это (и разное другое) читал, о потенциальных подводных камнях в курсе. Собственно, раньше, как раз начитавшись "умных советов", использовал и то и другое — мол компилятор может уметь обрабатывать эту прагму быстрее, а если не поддерживаемые прагмы по стандарту просто пропускаются. Затем, после участия в некотором количестве вполне себе кроссплатформенных проектов, где использовали pragma once и "не заморачивались", стал проще к этому относиться.


          Собственно, интересно было бы услышать о реальных граблях, а не гипотетических ситуациях. В конце концов, можно аргументировать тем, что случайно могут оказаться одинаковые гарды и проблема будет тоже не самой очевидной.


  1. broken
    30.01.2017 13:25

    del


  1. 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

    может кто в теме что и как?


    1. Zifix
      31.01.2017 12:34

      Использовать можно.