Свежие впечатления о BlackBerry 10 NDK

в 6:08, , рубрики: bb10, blackberry, boost, c++, jolla, mobile development, Ndk, QML, qt, Qt Software, морг, не взлетело, печаль

image

Последние две недели я снова копался в BlackBerry 10 NDK, так как один из моих клиентов попросил помочь ему. Я предложил адаптировать свой курс «введение в Qt» под платформу BlackBerry, а также порекомендовал следовать советам из моей серии учебных роликов про BB10 и Cascades, опубликованных в начале этого года на YouTube. Теперь мне хочется поделиться с вами моими свежими впечатлениями о BlackBerry 10 NDK. Кстати, я уже писал о моих первых экспериментах с BB10 NDK этой весной.

Внимание. Это вольный перевод заметки Йэнса Веллера. Перевод сделан для составления общей картины о текущем состоянии мира [BB10 + Qt]. Приятного чтения.

Приложения и C++

Перед тем как начать, небольшое введение о приложениях и C++. Люди, переходящие с других языков, вроде Java или .NET, часто не понимают необходимости написания приложений именно на C++. Особенно переходящие с языков, принудительно завязанных на ООП и Сборщик Мусора — им непросто понять все концепты, используемые в C++. По-моему, на самом деле, есть много причин использовать C++ для разработки приложений, особенно в связке с таким мощным фреймворком Qt. Одна из причин — производительность, так как C++ действительно ближе к железу, ваше приложение будет кушать минимум батареи. Не говоря уже о том, что имеется потолок роста производительности устройств в будущем, об этом упоминал Герб Саттер в своей заметке «бесплатные пайки закончились» (есть перевод на хабре от webmasconприм. переводчика). Qt Framework теперь доступен и для Android и для iOS, так что C++ и Qt/QML стали действительно мощной комбинацией для создания мобильных приложений.

NDK и Cascades

Поэтому, когда вы разрабатываете приложения под BlackBerry 10, вам необходимо начинать именно с BlackBerry NDK. У меня не было достаточно времени поиграть со свежевышедшей 1.2 версией, но IDE на первый взгляд стала заметно лучше и стабильнее. Но 1.2 пока не получила «золотой» статус (фиксация API — прим. переводчика), поэтому я рекомендую писать под 1.1, разве что вам что-нибудь нужно из 1.2. BlackBerry NDK поставляется вместе с Cascades Framework, API которого вы будете использовать при разработки приложения под BB10. Cascades сделан поверх Qt и использует QML, в то время как у Qt5 есть QtQuick 2, потому что у BlackBerry своя реализация QML, работающая в UI-потоке. Так что QML, созданный под QtQuick1 или 2 не будет работать под Cascades. А ещё Qt5 не полностью поддерживается Cascades — текущая версия основана на Qt 4.8.

С 2010 года я интересуюсь мобильной разработкой на Qt и QML, раньше был занят MeeGo, а сейчас я с BlackBerry. QML в BB10 немного отличается, в нём используются специальные элементы, например: Container, Pages и Controls, в то время как в QtQuick1/2 предлагает совсем базовые элементы вроде Item или Rectangle. Так что для QML и его API у BlackBerry имеется свой маленький мирок. Тем временем Qt5-приложения можно собрать и запустить на BB10, правда это не будет сопровождаться той степенью интеграции, которую предлагает Cascades.

QML и C++

Судя по документации и обсуждениям, основной подход сводится к использованию QML для большинства задач и мостов в C++ только при необходимости. Например, при написании моделей в C++ и дкларирования методов класса с помощью Q_PROPERTY для доступа их QML. Затем бóльшая часть работы происходит на стороне QML. У QML немного способов верификации и проверок на ошибки, например console.assert, но так как QML траслируется в JavaScript и не обладает сторогой типизацией, следовательно, не может проверять соответствие типов. Опечатка в имени переменной приведёт к тому, что QML может не заметить этой ошибки, а посчитать эту переменную объявленной новой. Простой пример:

Container {
    layout: DockLayout {
    }
    Label{
        text:ListItemData.titel
    }
}

Этот простой элемент для отображения в контейнере ListView, через ListItemData мы получаем доступ к данным для отображения. Я тут внёс небольшую ошибку, на самом деле элемент называется title, а titel — это по-немецки. Тем более, немец это не сразу заметит. А QML не заметит тем более. Вы можете вставить любое слово здесь, QML не проверяет такого рода ошибки не во время компиляции, ни во время исполнения. Ничего не будет отображено, возможно вам повезёт обнаружить предупреждение в консоли. Кстати, если правильно настроить IDE, то сообщение точно должно появляться, даже при отладке на устройстве.

Как с этим бороться? За Cascades скрывается C++ фреймворк, построенный на Qt, так что хотя бы в C++ у нас есть шанс обнаружить это и запротоколировать ошибку. К сожалению, это невозможно обнаружить во время компиляции, но я работаю в этом направлении. Так что Q_ASSERT делает проверку во время работы приложени. Для всех элементов из Cascades, задействованных в QML, имеется класс в C++, инстанциирующийся динамически для каждого элемента во время исполнения приложения. Cascades API позволяет искать объекты этих классов и предоставляет способ контроллировать некоторые вещи. Для ListView также имеется класс, поставляющий элементы для ListView из C++: ListItemProvider. У этого классе есть свой интерфейс:

virtual bb::cascades::VisualNode* createItem(bb::cascades::ListView* listview,const QString& type);
virtual void updateItem(bb::cascades::ListView* listview,bb::cascades::VisualNode* node,const QString& type,const QVariantList& indexPath, const QVariant& data);

Своя реализация этих виртуальных методов позволяет создавать элементы для отображения в ListView и также заполнять их актуальными значениями. BlackBerry предоставляет примеры реализации. К сожалению, эти примеры не испольуют QML, а написаны целиком на C++. Но мне лично нравится использовать QML для UI. А ещё такой ООП-стиль, как в примере от BlackBerry, подразумевает наследование от ListItemProvider для каждого отдельно взятого ListView, а мне хотелось бы решить данную задачу раз и навсегда, так что у меня свой обобщённый ListItemProvider. Так как все проверки происходят во время работы приложения, шаблоны C++ — не вариант, давайте взглянем на следующую реализацию. Перед тем как я перейду к crateItem, краткая отсановка на addType, вспомогательном методе для декларирования обработчиков для каждого типа:

void ListViewItemProvider::addType(const QString& type, const QString& qmlasset, const listitem_callback& callback)
{
    bb::cascades::QmlDocument* doc = bb::cascades::QmlDocument::create(qmlasset);
    if(!doc->hasErrors())
    {
        doc->setParent(this);
        type_map.insert(type,doc);
        callback_map.insert(type,callback);
    }//TODO add additional error checking & handling
}

Этот метод добавляет различные обработчики для различных типов. Типы описываются в QML с помощью объектов QString, так что QMap<QString, QmlDocument*> будет достаточно для хранения поддерживаемых типов. Данный метод преобразует qml-ресурс (например, file:///partial.qmlприм. переводчика) в QmlDocument с помощью QmlDocument::create. Между прочим, QmlDocument::create на самом деле не возвращает QmlDocument*, как написано в примере выше. Он возвращает ссылку на служебный класс Builder для создания QmlDocument'ов, затем, похоже, производит неявное преобразование в QmlDocument*. В принципе, здесь ничего интересного, идём дальше, метод createItem:

bb::cascades::VisualNode* ListViewItemProvider::createItem(bb::cascades::ListView* listview,const QString& type)
{
     if(type_map.find(type)!=type_map.end())
     {
          bb::cascades::Container* node = type_map[type]->createRootObject<bb::cascades::Container>();
          return node;
     }
     Q_ASSERT_X(false,__FUNCTION__,type +" TYPE not handled");
     bb::cascades::Container* con = new bb::cascades::Container(0);
     bb::cascades::Label* label = new bb::cascades::Label(con);
     label->setText("ERROR");
     return con;
}

Данный код сначала проверяет регистрацию типа, а затем создаёт объект через метод QmlDocument::createRootObject, который возвращает указатель на созданный объект. Это шаблон, так что мы должны заранее знать тип объекта для создания. Пока я для себя определился, что для всех UI элементов я буду использовать Container в качестве корневого объекта. Возможно, здесь также подойдёт тип VisualNode*, который является возвращаемым значением метода createItem. Здесь самое интересное — обработка ошибки, как мы должны это сделать? Тут на помощь приходит Q_ASSERT_X и оповещает об ошибке. Но в случае сборки без отладки он ничего не сообщит, а тем не менее метод должен вернуть какое-либо значение. Про 0 ничего не сказано в документации, поэтому нет оснований думать, что это верное возвращаемое значение для данного метода. Но зато в документации явно указано, что возвращаемый указатель будет принадлежать ListView Даже если мы вернём 0 (к счастью, разработчики BB10 предусмотрели это), это утаит ошибку от тестировщика. Так что я решил возвращать небольшой Container с Label с текстом об ошибке. Может быть, я мог бы сформулировать сообщение лучше, но в данном варианте тестер скорее заметит эту ошибку. Как вариант, можно было бросить исключение, но после этого контроль передаётся обратно в Cascades API и Qt, а это не лучший вариант, так как Qt и Cascades не используют исключения, хоть они и поддерживаются в BB10.

Последнее, что нужно реализовать — метод updateItem. И снова не получается обойтись проще, ведь мы пишем обощённый код. В конце концов, загружаемый QML файл должен быть загружен с правильными значениями, что также явилось одной из причин, почему я решил написать велосипед реализовать обобщённый подход. Но есть вариант с заимствованием реализации для этого прямо из нашего класа — мы регистрировали callback'и для создания объектов нужных типов, теперь остаётся просто вызвать соответствующий callback в методе updateItem:

if(callback_map.find(type)!=callback_map.end())
{
#ifndef USE_BOOST
        (*callback_map[type])(node,indexPath,data);
#else
        callback_map[type](node,indexPath,data);
#endif
}

До этого момента я мог не использовать/скрыть дефайн USE_BOOST, но для такого callback'а программист С++ должен сначала учесть boost::function. И так как BlackBerry утверждает, что boost поддерживается, я конечно же использую его. И тут выходит, что это не так-то просто, по крайней мере мой тулчайн падает с ошибкой в boost/type_traits/detail/cv_trait_impl.hpp. Я знаю, что boost используется многими, так что это скорее всего ошибка из-за настроек моего тулчайна или дистрибутива Linux. Ошибка судя по всему идёт из препроцессора, на версии GCC 4.6.3, в тексте ошибки сказано про несовпадение скобок. Я предпочёл пропатчить мою версию boost на локальной машине и сообщил об этом в сообщество boost и в BlackBerry. Если вы используете boost под BB10, то вам лучше использовать версию boost с GitHub'а BlackBerry. Так как не всем нужен boost, я также сделал версию без него, на случай если boost поломается снова.

И последнее, но не менее важное, реализация callback'a:

void ApplicationUI::callbackMyListItem(bb::cascades::VisualNode* node,const QVariantList& indexPath, const QVariant& data)
{
    bb::cascades::ImageView* image = node->findChild<bb::cascades::ImageView*>("imageview");
    Q_ASSERT(image);
    if(image)
    {
        QString name_image = "image"; //this must be correct!
        QVariantMap map = data.toMap();
        bool hasdata = map.contains(name_image);
        Q_ASSERT(hasdata);
        if(hasdata)
            image->setImageSource(map[name_image].toUrl());
    }
}

В данном случае путь до изображения задан. Указатель на VisualNode унаследован от QObject, так что дочерние объекты могут быть получены через findChild, который вернёт 0 в случае, если ничего не будет найдено. Поэтому уместно использовать здесь Q_ASSERT для проверки этого случая. Затем происходит поиск данных в QVariantMap. Так как нам в любом случае нужно какое-либо изображение, проверяется наличие элемента в контейнере QVariantMap. Если его нет, то Q_ASSERT снова приходит на помощь. Этот callback просто регистрируется с помощью boost::bind.

Поиск значений также может быть реализован через модель, но BB10 не поддерживает обычные модели из Qt, вместо этого BlackBerry реализовали свои классы моделей. В целом это хорошо, но мне лично больше нравятся модели из Qt, тем более это позволило бы использовать их при портировании Qt-приложения под Android, iOS, PC/Mac или даже Jolla. KDAB — один из наших «золотых» спонсоров — опубликовал решение, которое устраняет это недоразумение и делает Qt-модели пригодными для использования в Cascades.

IDE

Теперь, пару слов об IDE. Как я уже сказал ранее, IDE улучшилась с выходом версии 1.2. Среда улучшается, но в некоторых случаях всё равно остаётся далёкой от совершентсва. Редактор QML по-прежднему не достаточно хорош, зато теперь при падении не роняет всю IDE. Альтернативой мог бы стать Qt Creator, в нём поддержка QML также улучшилась. На данный момент, мне кажется Momentics IDE (базируется на Eсlipse — прим. переводчика) от BlackBerry лучше, чем QtCreator, если речь о разработке под Cascades. Во-первых, в QtCreator нет интеграции с Cascades вообще, поэтому автодополнение QML работать не будет, потому как в NDK отсуствтует необходимый для этого файл. По этой же причине не будет работать визуальный редактор QML. Qt, конечно, чуть лучше поддерживается в QtCreator, но в 1.2 версии NDK действительно много улучшений в этом направлении. Шаблоны проектов, предлагаемые QtCreator, не такие хорошие, как в Momentics, например, в них отсутствует интеграция кода локализации. Мне нравится то, что шаблоны в Momentics включают QTranslator в main.cpp. Momentics и QtCreator обма могут создать рабочее приложение под моё DevAlpha устройство, так что разработка под BB10 в QtCreator возможна, но есть куда расти.

Я загрузил исходные коды ListViewItemProvider, если вам нужно…

Популярные комментарии (1 шт)

— Кто хочет навестить морг?

Автор: Xlab

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js