@rozhkovdmitrii

Почему QApplication внутри JNI вызова сегфолтит android приложение?

Стоит задача разработать `.apk` в Android Studio, вызывающую паралельный нативный процесс в либе, на борту которой крутится QtApplication со своим event-лупом.

Сначала думалось разработать Android archive (.aar) в QtCreator чтобы без проблем линковать её в любых `.apk` средствами Java - но это плохое решение поскольку QtCreator не поддерживает андроид архивы из коробки. Тем не менее упомяну что для сборки андроид архива `.aar` вместо `.apk` мы можем попросту отредактировать build.gradle в каталоге билда `cmake`-а как показано на картинке:

4BaC2.png

Сейчас это не важно, мы просто берем `.so` из сбилженного в креаторе проекта и тащим их в Android Studio

Android Studio проект

Теперь создаем проект приложения в `AndroidStudio` и затем класс `SdkService` с определением нативного метода `initSdk` - что должен вызывать упомянутую параллельную, что уж греха таить, скрытую активность на борту либы. Такой класс в моем примере располагается по следующему пути: `AndroidStudioProjects/MyApplication7/app/src/main/java/io/company/companySdk/SdkService.java`

AO0o7.png

JNI header

Генерируем заголовок JNI при помощи `javac`:

bash
$ pwd
/home/rozhkov/AndroidStudioProjects/MyApplication7/app/src/main/java/io/company/companySdk
$ javac -h . SdkService.java


В следующем листинге определение метода `initSdk`, реализующего целевой нативный вызов из Java. Это определение будем использовать в qtcreator. Определение берется из сгенерированного файла `io_company_companySdk_SdkService.h`:

extern "C"
JNIEXPORT void JNICALL Java_io_company_companySdk_SdkService_initSdk
  (JNIEnv *, jclass);


проект qtcreator

В qtcrator создаем Qt Widgets Application. Для этого типа приложения автоматически генерятся рецепты связанные с `androiddeployqt` - иначе мы не получим набор всех связанных qt-либ. Мне привычней использовать CMAKE поэтому и проект qtcreator представлен листингом CMakeLists.txt. Не выключает линковку с пакетом Widgets, а то сборка поломается

CMakeLists.txt
make_minimum_required(VERSION 3.5)

    project(sample_service VERSION 0.1 LANGUAGES CXX)

    set(CMAKE_INCLUDE_CURRENT_DIR ON)

    set(CMAKE_AUTOUIC ON)
    set(CMAKE_AUTOMOC ON)
    set(CMAKE_AUTORCC ON)

    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)

    find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets REQUIRED)
    find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Widgets REQUIRED)

    set(PROJECT_SOURCES
            main.cpp
    )

    if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
        qt_add_executable(sample_service
            MANUAL_FINALIZATION
            ${PROJECT_SOURCES}
        )
    endif()

    target_link_libraries(sample_service PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

    if(QT_VERSION_MAJOR EQUAL 6)
        qt_finalize_executable(sample_service)
    endif()


main.cpp
#include <thread>
    #include <memory>
    #include <jni.h>

    #include <android/log.h>

    #include <QCoreApplication>
    #include <QJniEnvironment>

    extern "C"
    JNIEXPORT void JNICALL
    Java_io_company_companySdk_SdkService_initSdk(JNIEnv *, jclass);

    class ServiceHolder
    {
    public:
        typedef std::unique_ptr<std::thread> UniqueThreadPtr;
        static void init_app_worker() {
            if (_appThread)
                return;
            _appThread = std::make_unique<std::thread>([]() {
                int argc = 0;
                using namespace std::chrono_literals;
                QCoreApplication app(argc, nullptr);
                app.exec();
            });
        }
    private:
        static UniqueThreadPtr _appThread;
    };

    ServiceHolder::UniqueThreadPtr ServiceHolder::_appThread;

    extern "C"
    JNIEXPORT void JNICALL
    Java_io_company_companySdk_SdkService_initSdk(JNIEnv * env, jclass)
    {
        int argc = 0;
        __android_log_print(ANDROID_LOG_VERBOSE, "SdkConnect", "Java_io_company_companySdk_SdkService_initSdk");
        if (QJniEnvironment::checkAndClearExceptions(env, QJniEnvironment::OutputMode::Verbose))
        {
            __android_log_print(ANDROID_LOG_VERBOSE, "SdkConnect", "Java environment checked");
        }
        else
        {
            __android_log_print(ANDROID_LOG_VERBOSE, "SdkConnect", "Java environment not checked");
        }
        ServiceHolder::init_app_worker();
    }

    jint JNI_OnLoad(JavaVM * aVm, void * aReserved)
    {
        __android_log_print(ANDROID_LOG_INFO, "SdkConnect", "Company sdk on load");
        return JNI_VERSION_1_6;
    }


Деплой библиотек в Android Studio

Из `android-build-debug.apk` сбилженного qtcreator-ом мы копируем весь набор связанных `.so`-шек в специализированный каталог `jniLibs`, откуда в рантайме по команде loadLibrary они подгружаются в память.

Следующие инструкции показывают как это делается:

$ pwd
/home/rozhkov/sources/android/sample_service/build-sample_service-Android_Qt_6_1_2_Clang_x86_64-Release/android-build/build/outputs/apk/debug
$ mkdir extracted
$ unzip -qod extracted/ android-build-debug.apk 
$ cp extracted/lib/x86_64/* ~/AndroidStudioProjects/MyApplication7/app/src/main/jniLibs/x86_64/
$ mv ~/AndroidStudioProjects/MyApplication7/app/src/main/jniLibs/x86_64/libsample_service_x86_64.so ~/AndroidStudioProjects/MyApplication7/app/src/main/jniLibs/x86_64/libsample_service.so 
$ ls ~/AndroidStudioProjects/MyApplication7/app/src/main/jniLibs/x86_64/
libc++_shared.so                        libplugins_imageformats_qjpeg_x86_64.so                                           libplugins_styles_qandroidstyle_x86_64.so  libQt6Network_x86_64.so
libplugins_imageformats_qgif_x86_64.so  libplugins_networkinformationbackends_androidnetworkinformationbackend_x86_64.so  libQt6Core_x86_64.so                       libQt6Widgets_x86_64.so
libplugins_imageformats_qico_x86_64.so  libplugins_platforms_qtforandroid_x86_64.so                                       libQt6Gui_x86_64.so                        libsample_service.so


Вызов нативного метода из Java

Следующий шаг - это загрузка либы в методе и вызов нативного `initSdk`. Я вставил необходимый код в дефолтный `MainActivity` следующим образом:

IyTIE.png

Запуск приложения - SEGFAULT

Наконец запускаем пример на эмуляторе x86_64, но каждый раз после такого запуска имеем сегфолт в либе `libsample_service.so` на конструкторе `QCoreApplication`. Если посмотреть стек-трейс видно что сегфолтится в `QJniEnvironmentPrivate`:

stack-trace


Пробовал с Qt 6.1.2 и 5.1.15 - результат один

Вопросы

Уверен много я не попробовал:

Можно еще посмотреть исходники Qt
Или же проблема в версии приложения судя по этому шагу стек-трейса
> `#04 pc 000000000032da4a /data/app/rozhkov.example.myapplication-2/lib/x86_64/libQt6Core_x86_64.so (_ZNK23QCoreApplicationPrivate10appVersionEv+372)`

Или надо вместо QApplication воспользоваться QtAndroidService

Или что то еще ...

Может ли кто-нибудь дать направление для решения данного вопроса, спасибо!
  • Вопрос задан
  • 128 просмотров
Пригласить эксперта
Ответы на вопрос 1
@rozhkovdmitrii Автор вопроса
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы