Последнее обновление:
October 20, 2021

Есть мысль... Жми, напиши!
Что имеем: Постов : 143 Авторов: 1 Категорий: 22

Android background service Qt

Понадобилось на днях разобраться с фоновыми сервисаи на Qt. Написал небольшую заготовку.

1. Создаём новый проект, в мастере создания выбираете другой проект -> проект с поддиректориями
2. Создаём подпроект - основное приложение, из которого будет запускаться наш сервис, в моём случае Qml с названием MainApp
3. Создаём подпроект - сервис. Он у нас будет в отдельной подключаемой библиотеке.

 В дереве проектов кликаем по проекту, в субменюшке выбираем создать подпроект, и в мастере создания выбираем другой проект -> пустой проект qmake. Название ставим по вкусу, у меня MyService1
4. Открываем на редактирование .pro файл сервиса и прописываем:
TEMPLATE = lib
CONFIG += dll
QT += core androidextras
TARGET = myservice1 #Заменить на своё усмотрение
5. Создаём точку входа в библиотеку. Через мастер создания добавляем "файл исходных текстов C/C++", название по вкусу, у меня main, открываем на редактирование и прописываем:
#include <QAndroidService>
#include <QDebug>

int main(int argc, char *argv[])
{
    QAndroidService app(argc, argv);
    qWarning()<<"Service started!";
    return app.exec();
}
6. Пока что с сервисом хватит, переходим к основному приложению.
В левой колонке кликаем по "Проекты", из списка "Сборка и запуск" выбираем комплект для Андроида (разумеется, должно быть настроено окружение для него), в моём случае Android Qt 5.12.3 Clang armeabi-v7a, в группе "Сборка, этапы" раскрываем список "Build Anroid APK", выбираем версию SDK и нажимаем "Create Templates" что бы Qt Creator создал все необходимые файлы в папке с приложением.
 7. Через QtCreator открываем androidManifest.xml, в графе "Имя пакета" пишем название пакета приложения (грубо говоря пространство имён для Java), у меня "ru.pavelk.QtServiceHelloWorld", сохраняем.
8. Идём в файловом менеджере в папку проекта, в подпапку android и создаём папку src, в ней создаём подпапки согласно имени пакета. 
Вот так: android/src/ru/pavelk/QtServiceHelloWorld/ и в ней файл с активити, через который будет запускаться наш сервис, название по вкусу, у меня MyService1.java. Открываем на редактирование и прописываем:
package ru.pavelk.QtServiceHelloWorld; //-- Название пакета должно соответствовать пути
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.qtproject.qt5.android.bindings.QtService;

public class MyService1 extends QtService //-- Название класса должно соответствовать названию файла
{
    private static final String TAG = "MyService1";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Creating Service");
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "Destroying Service");
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int ret = super.onStartCommand(intent, flags, startId);
        // Do some work
        return ret;
    } 
}
9. Прописываем в манифесте инфу о сервисе. Открываем androidManifest.xml (если открываете в QtCreator, то в верху перейдите на "Исходник XML"), перед </appplication> добавляем:
<application>
    ....
    <!-- Название сервиса как название пакета и через точку класс активности, название процесса сервиса по вкусу -->
    <service android:name="ru.pavelk.QtServiceHelloWorld.MyService1" android:process=":MyService1Process">
        <!-- Имя библиотеки сервиса, т.е. значение TARGET из pro файла сервиса -->
        <meta-data android:name="android.app.lib_name" android:value="myservice1"/> 
        <!-- Что бы мог запускаться в фоне -->
        <meta-data android:name="android.app.background_running" android:value="true"/>
        
        <!-- Библиотеки Qt, можно скопировать из основной activity, которая выше в файле -->
        <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="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
        <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="-- %%USE_LOCAL_QT_LIBS%% --"/>
        <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="-- %%INSERT_LOCAL_LIBS%% --"/>
        <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
        <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>  
    </service>
    ....
</application>
10. Ну и осталось только запустить сервис из основного приложения. 
 В *.pro файл дописываем:
QT += androidextras
DISTFILES += android/src/ru/pavelk/QtServiceHelloWorld/MyService1.java
Код запуска:
#include <QtAndroid>
#include <QAndroidIntent>

QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(),"ru.pavelk.QtServiceHelloWorld.MyService1"); //-- Имя пакета и название класса
    QAndroidJniObject result = QtAndroid::androidActivity().callObjectMethod("startService", "(Landroid/content/Intent;)Landroid/content/ComponentName;", serviceIntent.handle().object());
вызываем когда нужно запустить сервис.
у меня он размещён в AppCore, смотрите исходники на GitHub.

Вывод отладочных сообщений сервиса в QtCreator не отображается, поэтому подключаемся через adb и смотрим лог: в консоле adb logcat -s «libmyservice1.so» Формат: «lib<значение target из .pro файла сервиса>.so»

Кстати, почему-то после изменения имени пакета QtCreator продолжал публиковать со старым, отчего была ошибка «W System.err: java.lang.ClassNotFoundException: Didn’t find class». После повторного «Create templates» (без замены файлов) всё обновилось.

Система может прибить сервис, когда ей будет не хватать памяти, что бы сервис перезапустился сам в активити сервиса из функции onStartCommand возвращайте START_STICKY.
Исходники на GitHub: https://github.com/Riflio/QtAndroidServiceHelloWorld
Вот как-то так. 
Views :

55

Qt 5.12 + Android с чего начать или Hello World

Потребовалось в очередной раз обновить Qt для кросскомпиляции под Андроид. В моём случае разработка идёт под Линукс, но с Виндой, думаю, особых проблем не будет.

Качаем Android SDK, для этого нужно скачать SDKManager. (На странице ищем «Command line tools only»), пока что не распаковываем.

Рабочим каталогом у меня будет «~/Projects/ANDROID», все манипуляции будут отталкиваться от него, как от основного.

Дальше запускаем Qt Update Manager (в папке с установленным Qt, называется «MaintanceTool») и добавляем все (либо только под целевую архитектуру) компоненты, связанные с Андроидом для вашей версии Qt.

Качаем JDK версии 8!, распаковываем куда-нить.
Кстати, подойдёт и OpenJDK 8 версии (sudo apt install openjdk-8-jre)
И сразу зададим, какую использовать версия Java, а то ошибок может быть куча:

 JAVA_HOME=/home/pavelk/Projects/ANDROID/jdk1.8.0_251/

Да, и консольку не закрываем, иначе придётся перепрописывать заново.

Создаём папку, в которой будет наш SDK:

mkdir ./SDK

и распаковываем в неё наш sdkmanager, что бы он оказался в подпапке tools (а в ней были bin и lib). Ну и начинаем с помощью него ставить все необходимые пакеты и утилиты:

cd ~/Projects/ANDROID
./SDK/tools/bin/sdkmanager --sdk_root="/home/pavelk/Projects/ANDROID/SDK" "platform-tools" "platforms;android-28"
./SDK/tools/bin/sdkmanager --sdk_root="/home/pavelk/Projects/ANDROID/SDK" "build-tools;28.0.1"

Очень внимательно читаем все лицензии, консультируемся с юристами и принимает, благославясь:

./SDK/tools/bin/sdkmanager --sdk_root="/home/pavelk/Projects/ANDROID/SDK" --license

Как Вы поняли, качаем под Android 9 (API 28 уровня), SDK будет распологаться по пути, указанным в —sdk_root
Полный список команд тут: https://developer.android.com/studio/command-line/sdkmanager.

На 64х битном Лине необходимо доставить 32х битные пакеты (но это не точно):

sudo apt install libstdc++6:i386 libgcc1:i386 zlib1g:i386 libncurses5:i386 libsdl1.2debian:i386

Версии зависят от вашего дистрибутива. Если какой-то нет, то вполне возможно, что придётся вручную качать пакет и устанавливать из старой версии.

Настраиваем QtCreator:
Tools > Options > Devices > Android
Выбираем пути до OpenJDK, AndroidSDK и AndroidNDK

Ну чтож, создадим первый проект.

Как обычно создаём новый проект, в моём случае QML.
Когда доёдёт до стадии выбора комплекта — выбираем архитектуру, которая на вашем устройстве для отладки (да, тестить будем на реальном девайсе). Если не вкурсе какая именно архитектуру процессора вашего девайса, то из Play Market ставим AIDA иди CPU-z.

Сразу прожимаем «запуск», и откроется окно с предложением выбрать девайс.

Включаем на телефоне режим разработчика, для этого нужно 7 раз кликнуть в настройках, подменюшка «о телефоне» по номеру сборку.
Дальше включаем «отладку по USB» в «Настройки» -> «Система» -> «Для разработчиков»

Первый раз телефон нужно подключить по USB, потом будем по WiFi.
Выясняем его ID:

lsusb

Вытыкаем девайс и снова набираем эту же команду, сравниваем вывод и выясняем, какая строка пропала. Запоминаем значение, которое сразу за «ID» идёт (к примеру «1d6b»).

Теперь пропишем правила монтирования, что бы при подключении по USB автоматически выдавались нужные права, а так же ADBAndroid Debug Bridge — Отладочный мост Android, через него мы как раз тправляем и тлаживаем приложение на устройстве) был вкурсе нашего устройства.

echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="<ID>", MODE="0666", GROUP="plugdev"' | sudo tee --append /etc/udev/rules.d/51-android.rules
echo '<ID>' | sudo tee --append ~/.android/adb_usb.ini

sudo chmod a+r /etc/udev/rules.d/51-android.rules
sudo udevadm control --reload-rules 
sudo udevadm trigger

Перезапускаем ADB

./SDK/platform-tools/adb kill-server
./SDK/platform-tools/adb start-server

Дальше приконнектимся к телефону по wifi, т.к. по проводу лень.

./SDK/platform-tools/adb tcpip 5555
./SDK/platform-tools/adb connect <IP девайса>

IP девайса можно узнать в настройках, подменюшка «о телефоне», в «общая информация».
По USB телефон теперь можно отключить и спокойно деплоить по WiFI.

Возвращаемся к Qt, жмём «Обновить список», выбираем наше устройство — справа в строке должен быть IP адрес его.

Гляньте в «консоль сборки», возможно, будут автоматически скачаны какие-либо пакеты дополнительные (возможно, при медленном соединеении, будут ошибки, поэтому запускать несколько раз придётся).

Кстати, если открыта Android Studio, то, при отладке в Qt, её придётся закрыть.

Надеюсь, сборка прошла успешно! Добро пожаловать в мир тормозов и лагов чудесный.

Пост на основе https://doc.qt.io/qt-5/android-getting-started.html

Views :

309

Android Автозапуск планшета/телефона при подключении зарядки/питания

Когда хочется использовать Андроид планшет как car-pc, или панель управления для станка, или просто как фоторамку, необходимо что бы он автоматически включался при подачи питания / зарядки.

Делается это просто:

    1. Рутуем девайс.
    2. Качаем root browser или total commander.
    3. Переходим в корневой каталог, ищем файл init.rc открываем на редактирование
    4. Находим в нём такую строчку:  «on charger«, а ниже будет команда, которая будет выполняться при подключении зарядки.
      По умолчанию, это «class_start charger«
    5. Заменяем эту строчку на такую:  «class_start autoOn«
    6. В каталоге «/system/bin/« создаём файлик  «autoOn» и прописываем в нём:
      #!/system/bin/sh
      su
      /system/bin/reboot
    7. Сохраняем, и изменяем права на 0755
    8. Выключаем девайс, подключаем зарядку. Через пару секунд начнётся запуск =)

Вот впринципе и всё =)

 

Views :

19584

Qt Android JNI преобразование QByteArray в jbytearray и обратно, а так же получение и передача в jni функцию

Подребовалось мне из Java класса вызвать функцию, наподобии этой:

public int send(byte[] data)
{     
....
}

Ну а что бы её вызвать из C++ нужно было преобразовать QByteArray в jbytearray
делается это так:

    jbyteArray QByteArray2jbyteArray(QByteArray buf)
    {
     QAndroidJniEnvironment env;
     jbyteArray array = env->NewByteArray(buf.length());
     env->SetByteArrayRegion (array, 0, buf.length(), reinterpret_cast<jbyte*>(buf.data()));
     return array;
    }

если нужно обратное преобразование, то делается так:

QByteArray jbyteArray2QByteArray(jbyteArray buf)
    {
       QAndroidJniEnvironment env;
       int len = env->GetArrayLength(buf);
       QByteArray array;
       array.resize(len);
       env->GetByteArrayRegion (buf, 0, len, reinterpret_cast<jbyte*>(array.data()));
       return array;
    }

ну а саму функцию из Java класса с помощью JNI можно вызвать так:

QAndroidJniObject myActivity=  QtAndroid::androidActivity();
myActivity.callMethod<int>("send", "([B)I", QByteArray2jbytearray(myByteArray));

Если нужно наоборот вернуть из функции jbytearray, то тут немного по сложнее:

QAndroidJniObject myActivity=  QtAndroid::androidActivity();
QAndroidJniObject readData = myActivity.callObjectMethod("read", "(V)[B");
jbyteArray array =readData.object<jbyteArray>();

Кстати, не забываем подключать заголовочники:

#include <QtAndroid>
#include <QAndroidJniEnvironment>
#include <QAndroidJniObject>

И в *.pro файле добавлять

QT +=  androidextras

Вот как то так =)

Views :

1301