Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Mobile and Embedded
  4. QApplication on Android, started from JNI leads up to SEGFAULT
Forum Updated to NodeBB v4.3 + New Features

QApplication on Android, started from JNI leads up to SEGFAULT

Scheduled Pinned Locked Moved Unsolved Mobile and Embedded
2 Posts 1 Posters 527 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • R Offline
    R Offline
    rozhkovdmitrii
    wrote on last edited by rozhkovdmitrii
    #1

    The main goal is to develop an .apk in Android Studio with qt based shared libraries (.so) onboard and start hidden qt event loop (QCoreApplication) in there.


    The first of all,

    I were concerned on how to make an Android archive (.aar) in qtcreator to use that in any target .apk as is - but it's wrong way because qtcreator doesn't support Android archives from the box. By the way to make a .aar instead of .apk we should change build.gradle inside cmake build directory like on the picture

    But now its no matter from where we'll get .so to bring them into Android Studio project.

    Android Studio project

    Then we should create an activity based AndroidStudio project and then a SdkService class with native method initSdk that should be called to start our hidden native processing. Here is the path of mentioned class: AndroidStudioProjects/MyApplication7/app/src/main/java/io/company/companySdk/SdkService.java

    AndroidStudio project overview:

    JNI header

    Then to generate JNI header I used javac:

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

    Here is initSdk definition to include it in qtcreator project from io_company_companySdk_SdkService.h:

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

    I created Qt Widgets Application because qtcreator generates androiddeployqt step for that kind of project. CMAKE type of project is most common for me.

    It's important to not take off linking with Widgets qt library

    Here are simple CMakeLists.txt and main.cpp following:

    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;
    }
    
    Deploy to Android Studio

    Qtcreator's build result is a android-build-debug.apk in cmake build directory. Right from there we should copy all related .so into jniLibs special directory from where that shared libs should be loaded as part of target .apk.

    The following instructions discover how it can be 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
    
    Call natives from Java

    The next step is loading shared library from Java and call mentioned initSdk. I extened MainActivity class in the following way:

    Call natives from java:

    Running Android application - SEGFAULT

    At last our Android application sample running on x86_64 emulator device.
    Always when sample .apk was running there happened SEGFAULT in libsample_service.so on QCoreApplication constructor.

    In stack-trace we can see it happens exactly in QJniEnvironmentPrivate:

    stack-trace

    There is no matter if it used Qt 6.1.2 or, for example, 5.1.15 - that segfault happened always.

    Questions

    There are remaining options to do that I not made in time.

    For example may be that happens because of app version not set.

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

    Or may be it is QtAndroidService is better for that kind of tasks.

    Or may be anything else...

    Does anyone know how it should be done?

    1 Reply Last reply
    0
    • R Offline
      R Offline
      rozhkovdmitrii
      wrote on last edited by
      #2

      The following steps guide you in proper way how to manage Android Studio project to make possible start QtService with some service activities on native side of android application.

      Qt and QtCreator:
      1. There are QtAndroidExtras in Qt 5.15, so you should build project under this version.

      2. In your build configuration you should enable a few build options for any architecture that you need.

      3. To start QtService you can use simple code:

      #include <android/log.h>
      #include <QtAndroidExtras/QAndroidService>
      
      int main(int argc, char ** argv) {
          QAndroidService app(argc, argv);
          __android_log_print(ANDROID_LOG_INFO, "Sample service", "Service being started");
          return app.exec();
      }
      
      Then in Android Studio project you need to:
      1. Create jniLibs folder and copy all .so architecture related sets, that qt placed in android-build/libs this way:
      [@ libs]$ pwd 
      /home/rozhkov/sources/android/service_apk/build-service_apk-Android_Qt_5_15_0_Clang_Multi_Abi_369ced-Release/android-build/libs
      [@ libs]$ ls
      arm64-v8a  armeabi-v7a  QtAndroidBearer.jar  QtAndroidExtras.jar  QtAndroid.jar  x86  x86_64 
      [@ libs]$ cp -R x86* arm* ~/AndroidStudioProjects/MyApplication7/app/src/main/jniLibs/    
      [@ libs]$ 
      
      1. Copy all .jar files:
      [@ libs]$ cp QtAndroid*.jar ~/AndroidStudioProjects/MyApplication7/app/src/main/java/jar/
      
      1. You'll need related .aidls from Qt. Copy them too:
      [@ src]$ pwd 
      /home/rozhkov/Qt/5.15.0/Src/qtbase/src/android/java/src
      [@ src]$ cp -R org ~/AndroidStudioProjects/MyApplication7/app/src/main/aidl/
      
      1. And resource libs.xml from qt build directory:
      [@ values]$ pwd 
      /home/rozhkov/sources/android/service_apk/build-service_apk-Android_Qt_5_15_0_Clang_Multi_Abi_369ced-Release/android-build/res/values
      [@ values]$ cp libs.xml ~/AndroidStudioProjects/MyApplication7/app/src/main/res/values/
      
      1. Pupulate AndroidManifest.xml like in official Qt guide [https://doc.qt.io/qt-5/android-services.html], but you need replace %% instractions manually. I got that values from AndroidManifest of .apk, built under Qt
        It's of mine:
      <?xml version="1.0" encoding="utf-8"?>
      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                package="rozhkov.example.myapplication">
          <uses-permission android:name="android.permission.INTERNET" />
          <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
          <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      
          <application
              android:allowBackup="true"
              android:icon="@mipmap/ic_launcher"
              android:label="@string/app_name"
              android:roundIcon="@mipmap/ic_launcher_round"
              android:supportsRtl="true"
              android:theme="@style/Theme.MyApplication"
              >
              <activity
                  android:name=".MainActivity"
                  android:label="@string/app_name"
                  android:theme="@style/Theme.MyApplication.NoActionBar" android:exported="true">
                  <intent-filter>
                      <action android:name="android.intent.action.MAIN" />
      
                      <category android:name="android.intent.category.LAUNCHER" />
                  </intent-filter>
              </activity>
      
              <service android:name="io.company.companySdk.QtAndroidService"  android:enabled="true"
                  android:exported="true" android:process=":qt">
      
                  <meta-data android:name="android.app.lib_name" android:value="service_apk"/>
                  <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="1"/>
      
                  <!-- Run with local libs -->
                  <meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
                  <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
                  <meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
                  <meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidExtras.jar"/>
                  <meta-data android:name="android.app.static_init_classes" android:value=""/>
                  <!-- Run with local libs -->
      
                  <!-- Background running -->
                  <meta-data android:name="android.app.background_running" android:value="true"/>
                  <!-- Background running -->
              </service>
      
          </application>
      
      </manifest>
      
      1. At last you should define a java class, inherits QtService. I got it in Qt docs:
      package io.company.companySdk;
      import android.content.Intent;
      import android.util.Log;
      
      import org.qtproject.qt5.android.bindings.QtService;
      
      public class QtAndroidService extends QtService
      {
          private static final String TAG = "QtAndroidService";
      
          @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);
              return ret;
          }
      
      }
      

      and start it from default AndroidStudio MainActivity:

      ...
      
      import io.company.companySdk.QtAndroidService;
      
      ...
      
      
      public class MainActivity extends AppCompatActivity {
      ...
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
      ...
              startService(new Intent(this, QtAndroidService.class));
      ...
      

      Now it should be all right ;)

      1 Reply Last reply
      0

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved