LINUX.ORG.RU

Как отловить момент закрытия программы в Android + Qt ?

 , , ,


0

3

Имеется приложение на Qt/QML под Android. Qt 5.9.2.

Мне нужно при закрытии программы сделать сохранение некоторых значений.

И я никак не могу отловить момент закрытия программы. Например, пользователь «смахнул» программу в спике открытых программ, и мне надо в программе успеть обработать этот момент.

Я искал всякие решения. Пробовал через QML так:

Window {
    id: mainWindow

    Connections {
        target: mainWindow
        onClosing: {
            console.log("On QML main windows close.");
            close.accepted = true
        }
    }
}

Пробовал на более низком уровне и через сигнал aboutToQuit() и в деструкторе объекта приложения:
class App : public QApplication
{
    Q_OBJECT

public:
    App(int &argc, char **argv);
    virtual ~App();

private slots:

    void onAaboutToQuit();
}

#include "App.h"

App::App(int &argc, char **argv) : QApplication(argc, argv)
{
    connect( this, SIGNAL(aboutToQuit()), this, SLOT(onAaboutToQuit()) );
}

App::~App()
{
    qWarning() << "Application destructor. Exit from application.";
}

void App::onAaboutToQuit()
{
    qWarning() << "onAaboutToQuit(): Exit from application.";
}

Результат таков: на десктопе все эти методы работают при закрытии окна. А на Андроиде при смахивании приложения - нет.

Вопрос: есть ли гарантированно работающий способ отлавливания закрытия приложения на Android в Qt?

Неужели придется городить подсистему периодического сохранения значений, которая будет тупо выжирать ресурс SD-карточки?

★★★★★

«смахнул»

Ты уверен, что это означает завершение программы? Я вот нет.
Более того — я почти уверен, что когда программа действительно завершится, то тогда все твои методы будут дёрнуты.

Deleted
()
Ответ на: комментарий от Deleted

Ты уверен, что это означает завершение программы?

Понятия не имею.

При последующем клике на иконку программы (после смахивания) она стартует с нуля, полностью заново «разворачиваясь».

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Deleted

В консоли отладки в момент смахивания пишется:


«ru.vendor.application» аварийно завершился.


Я вот думаю, может это просто отладочные сообщения не доходят до отладочной консоли, а код на самом деле срабатывает?

Xintrea ★★★★★
() автор топика

Лезь в Java, наследуйся от Qt'шного Activity, перегружай там onDestroy() и сохраняй в нём настройки.

EXL ★★★★★
()
Последнее исправление: EXL (всего исправлений: 1)

man Activity lifecycle или сразу https://developer.android.com/reference/android/app/Activity#ActivityLifecycle

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

EXL тебе почти правильно подсказал, но нужно сохранять в onStop, т.к. вызов onDestroy не гарантирован.

der_looser ★★
()
Ответ на: комментарий от EXL

Лезь в Java, наследуйся от Qt'шного Activity, перегружай там onDestroy() и сохраняй в нём настройки.

Не, я такое не осилю.

Есть ли что-то готовое для инфраструктуры Qt/QML, сделанное вот так как ты говоришь?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Я вот думаю, может это просто отладочные сообщения не доходят до отладочной консоли, а код на самом деле срабатывает?

Сделал запись в файл в этих местах - и ничего. То есть, код не срабатывает.

Xintrea ★★★★★
() автор топика

Я смотрю вы тут все специалисты по жизненому циклу ведройд приложений. Один совет лучше другого. Неужели так сложно почитать документацию?

Основная статья: Understand the Activity Lifecycle - https://developer.android.com/guide/components/activities/activity-lifecycle

Раздел где описано как правильно сохранить и восстановить стэйт: https://developer.android.com/guide/components/activities/activity-lifecycle#...

Не специалист по QT, но уверен, что у вас там просто кольбэки на все эти метолды. И да нет гарантированного способа отлавливания закрытя приложения. Пользователь может «смахнуть», также и сама ОС может «смахнуть» ваше приложение, если оно будет в бэкграунде. Таже советую потестить решение на нескольких верисия ОС. С каждой новой всё больше зажимают ремни нативным поделкам.

ii8_ ★★★★
()
Ответ на: комментарий от Xintrea

Есть ли что-то готовое для инфраструктуры Qt/QML, сделанное вот так как ты говоришь?

Без понятия, может и есть. Мирок Qt/QML очень ограничен и не особо подходит для использования на Android OS, ИМХО.

Вот тебе минимальный пример, который демонстрирует различные извращения, вроде:

QML => C++
C++ => JNI => Java
QML => C++ => JNI => Java
QML => C++ => JNI => Java => JNI => C++
Java => JNI => C++

Туда же я добавил перегрузку методов onStop(), onDestroy() и пример работы с QSettings: в onStop() (как подсказал der_looser, а не в onDestroy()) вызывается нативный метод из C++-ядра приложения, который сохраняет число c помощью QSettings, которое можно извлечь в лог по кнопке Log Arg.

Возможно, конечно, есть более привычные методы в инфраструктуре Qt/QML, но я не специалист в QtQuick. Это надо смотреть доки на Qt.

https://github.com/EXLMOTODEV/QmlDestroyTest

EXL ★★★★★
()
Последнее исправление: EXL (всего исправлений: 1)
Ответ на: комментарий от EXL

Ох нифига себе, ты сделал то, чего другие ни в жизнь бы не почесались написать. Тут однозначный респект.

Щас поразбираюсь.

Xintrea ★★★★★
() автор топика

которая будет тупо выжирать ресурс SD-карточки?

Да, сохраняй все сразу. А то юзеры понаставляют чудо прог для оптимизации которые будут килить процессы... У тебя что настройки меняются еже миллисекундно или хотя бы каждую секунду?

LinuxDebian ★★★★
()
Ответ на: комментарий от LinuxDebian

У тебя что настройки меняются еже миллисекундно или хотя бы каждую секунду?

Да, надо запоминать координаты, которые поступают 20 раз в секунду. Чтобы последующий запуск помнил последние координаты предыдущей сессии. Даже прореживание в 50 раз за счет снижения точности, будет дергать SD-карту каждые 2,5 сек.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от EXL

В общем, посмотрел код, запустил и увидел одну тонкость. Метод onStop() срабатывает не в момент «смахивания» приложения, а в момент нажатия кнопки обзора всех приложений (то есть, до «смахивания»). Само же смахивание не отслеживается никак.

В принципе это не критично, достаточно отслеживания ухода приложения в фон при переходе к обзору приложений.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

onSuspend значит, что поверх вашего активити есть другая (но, возможно, она закрывает вашу только частично и её ещё видно). onStop значит, что вашу активити больше не видно на экране. Всё остальное не гарантируется.

KivApple ★★★★★
()
Ответ на: комментарий от Xintrea

Ох нифига себе, ты сделал то, чего другие ни в жизнь бы не почесались написать

У меня этот код валялся давно, я к нему только onStop() и onDestroy() прикрутил.

P.S. На само смахивание у меня всегда регистрируется onDestroy(), видимо в новых Android'ах что-то поменяли.

EXL ★★★★★
()
Последнее исправление: EXL (всего исправлений: 1)
Ответ на: комментарий от EXL

В общем, повторил в рамках своего проекта код из примера, только имена файлов/каталогов на более удобные поменял. Собирается без ошибок, но функция по типу Java_ru_exlmoto_qmldestroytest_NativeHelper_invokeVoidMethod() в main.cpp не вызывается.

Есть какая-нибудь тонкость в AndroidManifest.xml или в *.pro-файле, которая влияет на возможность работы с JNI?

Вроде смотрел, ничего особенного не нашел.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от EXL

Может быть, нельзя использовать большие буквы в именах директорий?

Я назвал директорию с Java--классами .../ru/company/eventHelper

и соответсвенно пакет пишу как:

package ru.company.eventHelper;

Может есть какие-то ограничения, и большие буквы нельзя использовать?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от EXL

Самое интересное, что ни при сборке, ни в консоли запущенного Android-приложения никаких java-ошибок нет. Просто не вызывается ..._invokeVoidMethod() в main.cpp и все.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от EXL

Да, конечно:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class ru_company_eventHelper_NativeHelper */

#ifndef _Included_ru_company_eventHelper_NativeHelper
#define _Included_ru_company_eventHelper_NativeHelper
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     ru_company_eventHelper_NativeHelper
 * Method:    invokeVoidMethod
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_ru_company_eventHelper_NativeHelper_invokeVoidMethod
  (JNIEnv *, jclass, jint);

#ifdef __cplusplus
}
#endif
#endif

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Сходу сказать не могу, пробуйте найти различия. Ещё смотрите в лог, там часто пишется причина того, почему не вызывается код. В AndroidManifest.xml необходимо обязательно выставить:

1. <manifest package="ru.exlmoto.qmldestroytest" ... (аналогично для вашего пакета).

2. <activity ... android:name="ru.exlmoto.qmldestroytest.QmlDestroyTest" ... (аналогично для вашего пакета).

EXL ★★★★★
()
Ответ на: комментарий от EXL

Вот, тут поподробнее.

У меня сейчас в AndroidManifest.xml задано так:

<manifest package="ru.company.gnss.company.ma" ...

<activity ... android:name="org.qtproject.qt5.android.bindings.QtActivity"

То есть, никак не коррелирует с каталогом:
.../ru/company/eventHelper

То есть, я думал, что проброс JNI делается отдельным пакетом, а оказывается вся программа - это один пакет.

1.
В любом случае, «ru.company.gnss.company.ma» я поменять не могу. Это значит, что мне нужно разместить Java-файлы в каталоге:

/android/src/ru/company/gnss/company/ma ?

и перегенрировать h-файл через javah?

2.
А вместо названия активити «org.qtproject.qt5.android.bindings.QtActivity» свободно можно писать что угодно?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

1. Да.

2. Нет, там должно быть ru.company.gnss.company.ma.УНАСЛЕДОВАННЫЙ_КЛАСС от org.qtproject.qt5.android.bindings.QtActivity.

И в Java-файлах требуется сменить package на ru.company.gnss.company.ma;.

У вас ничего и не работает, потому что вы ванильный QtActivity запускаете, а не ваш унаследованный.

EXL ★★★★★
()
Ответ на: комментарий от EXL

Исправил, заработало. Огромное спасибо!

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от EXL

На само смахивание у меня всегда регистрируется onDestroy(), видимо в новых Android'ах что-то поменяли.

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

der_looser ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.