Содержание статьи
Когда OS Android только появилась, многие, и я в том числе, мечтали, чтобы на
нее как можно скорее портировали Qt. К сожалению, корпорация добра не оправдала
наших надежд, сообщив, что SDK Андроида будет только на Java. Новость о покупке
Trolltech корпорацией Nokia тоже не добавила оптимизма.
Спустя некоторое время к нам привалила нежданная радость — для Андроида вышел
NDK для нативной разработки на C++, и, конечно же, нашлись люди, которые стали
портировать Qt на Android. На данный момент порт уже более-менее юзабелен —
работают (и почти не глючат) практически все модули. Ну что ж, посмотрим, какие
возможности открывает нам этот порт.
Как оно работает?
Поначалу кажется, что данный порт — это очень большой костыль. Без Java все
равно не обошлось — с помощью NDK нельзя создавать исполняемые файлы, можно
только библиотеки .so. На Java, по сути, нужно написать всего одну строчку,
которая загружает нашу библиотеку на Qt. Далее виртуальная машина Android
запускает Java-приложение, которое, в свою очередь, грузит нашу либу.
Сборка QT
Весь процесс очень хорошо описан в Wiki проекта, но он содержит несколько
граблей, поэтому кое-какие пояснения нам дать все же придется.
Небольшая оговорка — процесс описывается для Ubuntu 10.04, но на других
дистрибутивах, в принципе, все должно происходить так же. А вот для того, чтобы
провернуть это дело под виндой, тебе придется немного попрыгать с бубном (какая
тонкая ирония, а?).
Итак, поехали:
Создаем директорию для SDK. Пишем в консоль:
wget
http://android-lighthouse.googlecode.com/files/qadk-1.x-2.x-rtti-exceptions.tar.lzma
tar xvfa qadk-1.x-2.x-rtti-exceptions.tar.lzma
Клонируем репозиторий Lighthouse:
git clone git://gitorious.org/~taipan/qt/androidlighthouse.git
Редактируем файл mkspecs/android-g++/qmake.conf. В нем нужно изменить
NDK_ROOT и ANDROID_PLATFORM (у меня — /data/local/qt и android-5
соответственно). Эти параметры отвечают за расположение собранной библиотеки и
ее версию. Также нужно отредактировать файл androidconfig.sh. Настоятельно
рекомендую заменить shared на static (для статической сборки библиотеки и
приложений). Все, конфигурируем (./androidconfig.sh) и собираем (make -j X, где
X — количество ядер твоего процессора).
Все? Не тут-то было! Не знаю, как обстоят дела с другими дистрибутивами, но
на Ubuntu "make" вылетал с ошибкой, говорящей о недоступности заголовочных
файлов OpenGL. Чего только я не предпринимал… Поставил все, что можно было, но
решение оказалось куда проще — надо было просто переустановить имеющиеся в
системе заголовочные файлы OpenGL. После этого можно повторять команду make -j X
и идти… нет, не пить пиво, а курить мануалы по разработке под Android —
информация лишней не бывает никогда, а собираться оно будет долго =).
Создание приложения
Запускай Qt Creator, создавай новое GUI-приложение. В нем (вернее, в файле .pro)
нам нужно будет изменить несколько строчек. Они должны выглядеть так:
TEMPLATE = lib
CONFIG += dll
В настройках Qt Creator нужно также указать путь до нашего (андроидовского)
qmake — у меня это /data/local/qt/bin/qmake.
Вообще, я бы посоветовал сначала дебажить приложение как десктопное и только
потом изменять параметры сборки.
Кстати, я ведь еще не говорил, что за приложение мы будем писать? Это будет
приложение для отправки СМС на номера самых различных операторов. Это возможно
благодаря сервису smste.ru, который мы и будем использовать. Не буду вдаваться в
подробности сниффинга, скажу только, что я использую для этих целей Wireshark.
Разберем алгоритм отправки сообщения:
- Делаем GET рута — главной страницы сайта, выдираем оттуда нужные нам
значения input’ов (те, которые hidden), а заодно и кукисы. - Запрашиваем капчу по номеру телефона и показываем ее пользователю.
- Отправляем POST-запрос с сообщением.
Для отправки HTTP-запросов в Qt существует класс QHttp. Кстати, не забудь
подключить модуль QtNetwork (QT += network) в файле проекта!
Набросай форму (мою ты можешь увидеть на скриншоте) и приступай к кодингу.
От объекта http класса QHtpp нам требуются только два сигнала — done() и
readyRead(). Сразу при создании главного виджета отправим GET-запрос главной
страницы:
http.setHost("smste.ru");
http.get("/");
Сигнал done(), по сути, и не используется — по нему можно будет только
опознать ошибку сетевого уровня (например, отключение Wi-Fi). Рассмотрим
некоторые части слота onHttpReadyRead(const QHttpResponseHeader&resp):
QString str(http.readAll());
qint32 index=str.indexOf("value=\"code")+7;
if ( index != 6 )
codeMod = str.mid(index, str.indexOf("\" />", index) - index);
Здесь мы копируем "спрятанную" (hidden) переменную codeMod из исходника
страницы. Идем дальше:
QString cookieStr;
for ( qint8 i = 0; i < resp.values().count(); i++ )
{
if ( resp.values().at(i).first == "Set-Cookie" ) cookieStr.append(resp.values().at(i).second+'\n');
}
cookies = QNetworkCookie::parseCookies(cookieStr.toAscii());
Ну, а в этом куске кода, как ты, наверное, догадался, мы парсим печеньки.
cookies — это QList из QnetworkCookie.
qint32 index = str.indexOf("<image>/pix/") + 12;
image = str.mid(index,str.indexOf(".jpg") - index);
QHttpRequestHeader header = createHeader("GET",QString("/pix/%1.jpg").arg(image));
http.request(header);
Здесь копируется адрес капчи (запрос адреса я покажу чуть позже) и посылается
запрос этого самого JPEG’а.
А вот так он сохраняется:
if ( resp.value("Content-Type") == "image/jpeg")
{
ui->captchaLb->setPixmap(QPixmap::fromImage(QImage::fromData(http.readAll())));
return;
}
Так, с этим слотом разобрались.
Капчу нужно запрашивать, как только пользователь введет номер телефона, то
есть, когда закончится редактирование текста ui->numberLE. Для этого есть
специальный слот:
void MainWidget::on_numberLE_editingFinished()
{
if ( ui->numberLE->text().length() != 11 )
return;
QHttpRequestHeader header = createHeader("GET", QString("/netxml.php?number=%1&rnd=94728").arg(ui->numberLE->text()));
http.request(header);
}
Функцию createHeader() смотри на врезке — она создает хедер HTTP-запроса
(вообще, можно и проще, но нам надо отправлять еще и куки).
Создание HTTP-заголовка
QHttpRequestHeader MainWidget::createHeader(
const QString &method,
const QString &path
)
{
QHttpRequestHeader header(method, path);
header.addValue("Host", "smste.ru");
header.addValue("Connection", "keep-alive");
header.addValue("User-Agent", "Mozilla/5.0");
header.addValue("Referer", "http://smste.ru");
header.addValue("Accept", "*/*");
QString cookie;
for ( qint8 i = 0; i < cookies.length(); i++ )
cookie += ( cookies.at(i).toRawForm(QNetworkCookie::NameAndValueOnly) + ";
");
header.addValue("Cookie", cookie);
return header;
}
Остался последний слот — нажатие кнопки "Отправить", и он предельно прост:
QHttpRequestHeader header = createHeader("POST", "/");
header.addValue("", QString("number=%1& message=%2&sign=ax-soft.ru&event=%3&
codemod=%4&%5=%6"). arg(ui->numberLE-> text()). arg(ui->textPTE->toPlainText()).
arg(image). arg(codeMod). arg(codeMod). arg(ui->captchaLE->text()));
qDebug() << header.toString();
http.request(header);
Вот и все! Делай Build All, собирай .apk-пакет :).
Создание виртуальной машины
Для тестирования приложения нам нужно создать виртуальную машину. Кстати,
надеюсь, у тебя установлена Java Runtime Environment? Если нет, то поставь, вещь
нужная. Кроме того, для создания .apk-пакетов понадобится ant. Ставится он легко
— sudo apt-get install ant. Теперь переходи в сабдиректорию tools в Android SDK
и вводи ./android. Запустится менеджер настроек и виртуальных машин.
Сначала скачай нужные API (разобраться нетрудно, для этого примера нужна
версия 8), далее переходи на вкладку Virtual Devices, жми New. В Name — любое
имя, Target — Android 2.2, Skin — какой хочешь (я выбрал WVGA800), и нажимай
Create AVD. Затем выбирай машину и жми Start, Launch. Все, будем ждать. На моем
нетбуке оно запускалось около десяти минут, на десктопе — 1,5-2 минуты. Работает
эмулятор так же медленно, как и запускается (ибо эмулирует ARM с помощью QEMU).
С одной стороны это плохо, что все тормозит, а с другой стороны — мы получаем
достоверные на 100% результаты. Как только появится рабочий стол Android,
виртуальную машину можно будет оставить в покое.
Создание тестового проекта
Переходим в директорию tools Android SDK (в консоли). Открываем документацию,
начинаем вкуривать. Вводим: ./android create project. Опс, ошибочка! Смотрим,
чего от нас хотят. Ага, мы не указали параметры нашего будущего проекта, а
точнее: цель. Нужна версия API, путь до проекта, его имя, имя Activity и имя
пространства имен для приложения. У меня получилось вот так:
./android create project --target 8 --name hello --path ./TestPro --activity
helloActivity --package com.example.hello
Делаем ls… ага, вот она — директория TestPro. Входим в нее, и опять вызываем
ls. Далее в директории libs нужно создать сабдиректорию armeabi. В нее мы
копируем нашу собранную Qt’шную либу (.so).
В каталог src/ надо скопировать все содержимое androidlighthouse/src/android/java/com,
чтобы получилось src/com/nokia/qt. После этого идем в src/com/example/hello/ и
редактируем там главный Activity — helloActivity.java. Удаляем onCreate,
добавляем функцию:
public helloActivity()
{
setApplication("Hello");
}
Здесь Hello — имя приложения. Следовательно, наша библиотека .so должна
называться libHello.so.
Ну и, наконец, идем в консоли в корень проекта и командуем ant install. Ждем
(долго, поскольку либа статическая и весит много. У меня, например — 12.5 Мб).
После того, как в консоли появится заветное SUCCESSFUL, можно идти в главное
меню Андроида и запускать оттуда свое приложение.
Заключение
Когда-то (то ли в 2007, то ли в 2008) у меня на телефоне (Motorola A1200e,
один из первых телефонов с Linux, и, кстати, с гуем, написанным на Qt 2)
появилась QTopia, также известная как Qt Embedded — встраиваемая ОС от Trolltech
на базе Linux Kernel 2.6 с оболочкой на Qt 4, заброшенная после покупки троллей
нокией. Появилась она благодаря труженикам с форума motofan, сумевшим
портировать ее на ядро 2.4 (другого у A1200 не было и не будет, поэтому не будет
и Андроида). Так вот, когда я ее поставил, был удивлен простотой портирования
приложений с десктопа на телефон — иногда требовалось просто пересобрать его
кросс-компилятором, и все!
К сожалению, новомодного Qt 4.5 платформа не получила (и зря — на мой взгляд,
она была не хуже, чем Maemo). Теперь такой метод портирования возможен и на
Android, а ведь за ним будущее. И, кстати, вовсю идет портирование Qt Mobility,
классного фреймворка для телефонов Nokia. Жаль, пока что портированием
занимается только один, пусть и очень крутой человек (кстати, помочь не
желаешь?). В общем, нам осталось дождаться портирования Qt на iOS (там, к
сожалению, все далеко не так радужно), и тогда можно будет смело заявлять, что
лозунг Qt Software не высосан из пальца.
Qt Everywhere!
Thanks to:
Огромное спасибо румыну taipanromania (автор порта) и marflon (раньше,
кстати, писал в ][) за помощь с созданием .apk, ну и, традиционно, группе И-3-1
(Прикладная Математика) МГТУ "Станкин".
WARNING
Неожиданный пункт, не правда ли? Не бойся ничего противозаконного, только
один ньюанс — со статически собранной библиотекой, при использовании
QtWebkit и Phonon, лицензия твоего приложения не должна отличаться от LGPL.
INFO
У меня не получилось наладить отправку на "Мегафон". Может быть, это
получится у тебя?
LINKS
http://code.google.com/p/android-lighthouse/ — страница проекта Qt for
Android на гуглокоде.http://developer.android.com/sdk/index.html — Android SDK, must have!