R
R
rozhkovdmitrii2021-08-18 10:09:15
Android
rozhkovdmitrii, 2021-08-18 10:09:15

Why does QApplication inside a JNI call segfault an android application?

The task is to develop `.apk` in Android Studio, which calls a parallel native process in a lib, on board of which QtApplication is spinning with its event loop.

At first it was thought to develop Android archive (.aar) in QtCreator in order to easily link it to any `.apk` using Java - but this is a bad decision because QtCreator does not support android archives out of the box. However, I will mention that to build the android archive `.aar` instead of `.apk` we can simply edit build.gradle in the `cmake` build directory as shown in the picture:

4BaC2.png

Now it does not matter, we just take `.so` from the project created in the creator and dragging them into the Android Studio

Android Studio project

Now we create the application project in `AndroidStudio` and then the `SdkService` class with the definition of the native method `initSdk` - which should call the mentioned parallel, to be honest, hidden activity on board the lib. Such a class in my example is located in the following path: `AndroidStudioProjects/MyApplication7/app/src/main/java/io/company/companySdk/SdkService.java`

AO0o7.png

JNI header

We generate a JNI header using `javac`:

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


In the following listing, the definition of the `initSdk` method that implements the target native call from Java. This definition will be used in qtcreator. The definition is taken from the generated file `io_company_companySdk_SdkService.h`:

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


qtcreator project

In qtcrator we create a Qt Widgets Application . Recipes associated with `androiddeployqt` are automatically generated for this type of application - otherwise we won't get a set of all related qt libs. I'm more used to using CMAKE, so the qtcreator project is represented by the CMakeLists.txt listing. Does not turn off linking with the Widgets package, otherwise the assembly will break

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;
    }


Deploying libraries in Android Studio

From `android-build-debug.apk` compiled by qtcreator, we copy the entire set of related `.so`-nibs to a specialized `jniLibs` directory, from where they are loaded into memory at runtime using the loadLibrary command.

The following instructions show how this is done:

$ 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


Calling a native method from Java

The next step is to load the lib in the method and call the native `initSdk`. I pasted the required code into the default `MainActivity` as follows:

IyTIE.png

Starting the application - SEGFAULT

Finally, we run the example on the x86_64 emulator, but each time after such a start we have a segfault in the `libsample_service.so` lib on the `QCoreApplication` constructor. If you look at the stack trace, you can see that the segfault in `QJniEnvironmentPrivate`: stack

-trace


Tried with Qt 6.1.2 and 5.1.15 - the result is the same stack trace step




> `#04 pc 000000000032da4a /data/app/rozhkov.example.myapplication-2/lib/x86_64/libQt6Core_x86_64.so (_ZNK23QCoreApplicationPrivate10appVersionEv+372)`

Or should I use QtAndroidService

instead of QApplication Or something else ...

Can anyone give me some direction to solve this issue, thanks!

Answer the question

In order to leave comments, you need to log in

1 answer(s)
R
rozhkovdmitrii, 2021-08-26
@rozhkovdmitrii

https://stackoverflow.com/questions/68818557/qappl...

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question