【问题标题】:QApplication on Android, started from JNI leads up to SEGFAULTAndroid 上的 QApplication,从 JNI 开始,一直到 SEGFAULT
【发布时间】:2021-10-19 10:24:09
【问题描述】:

主要目标是在 Android Studio 中开发一个带有基于 qt 的共享库 (.so) 的 .apk,并在其中启动隐藏的 qt 事件循环 (QCoreApplication)。


首先,

我担心如何在 QtCreator 中制作一个 Android 存档 (.aar) 以便在任何目标 .apk 中使用它 - 但这是错误的方式,因为 QtCreator 不支持 Android 存档。顺便说一下,要创建一个.aar 而不是.apk,我们应该在cmake 构建目录中更改build.gradle,如图所示

但现在无论我们从哪里获得.so 将它们带入 Android Studio 项目。

Android Studio 项目

然后我们应该创建一个基于活动的AndroidStudio 项目,然后创建一个带有本地方法initSdk 的SdkService 类,应该调用它来启动我们隐藏的本地处理。这里是提到的类的路径:AndroidStudioProjects/MyApplication7/app/src/main/java/io/company/companySdk/SdkService.java

AndroidStudio 项目概览

JNI 标头

然后生成我使用javac的JNI头:

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

这是initSdk 的定义,将其包含在io_company_companySdk_SdkService.h 的qtcreator 项目中:

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

我创建了 Qt Widgets Application,因为 qtcreator 会为此类项目生成 androiddeployqt 步骤。 CMAKE 类型的项目对我来说是最常见的。

重要的是不要取消与 Widgets qt 库的链接

下面是简单的CMakeLists.txt 和main.cpp:

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

Qtcreator 的构建结果是 cmake 构建目录中的android-build-debug.apk。从那里我们应该将所有相关的.so 复制到jniLibs 特殊目录中,共享库应该作为目标.apk 的一部分从该目录加载。

以下说明了解如何完成:

$ 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 调用原生代码

下一步是从 Java 加载共享库并调用提到的initSdk。我通过以下方式扩展了MainActivity 类:

从 java 调用本地人

运行 Android 应用程序 - SEGFAULT

最后我们的 Android 应用示例在 x86_64 模拟器设备上运行。 总是在示例 .apk 运行时,libsample_service.so 中的 QCoreApplication 构造函数中发生 SEGFAULT。

在堆栈跟踪中,我们可以看到它恰好发生在QJniEnvironmentPrivate:

堆栈跟踪

不管它使用的是 Qt 6.1.2 还是 5.1.15 - 段错误总是发生。

问题

还有一些我没有及时做出的选择。

例如,可能是由于未设置应用版本而发生的。

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

或者可能是QtAndroidService 更适合这种任务。

或者可能是其他任何东西......

有人知道应该怎么做吗?

【问题讨论】:

    标签: android-studio qt java-native-interface aar


    【解决方案1】:

    以下步骤将指导您以正确的方式管理 Android Studio 项目,以便在 android 应用程序的本机端使用一些服务活动启动 QtService。

    Qt 和 QtCreator:
    1. Qt 5.15中有QtAndroidExtras,所以你应该在这个版本下构建项目。

    2. 在您的构建配置中,您应该为您需要的任何架构启用一些构建选项。

    3. 要启动 QtService,您可以使用简单的代码:

    #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();
    }
    
    然后在 Android Studio 项目中你需要:
    1. 创建jniLibs文件夹并复制所有.so架构相关的集合,qt这样放在android-build/libs中:
    [@ 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. 复制所有.jar文件:
    [@ libs]$ cp QtAndroid*.jar ~/AndroidStudioProjects/MyApplication7/app/src/main/java/jar/
    
    1. 您需要来自 Qt 的相关 .aidls。也复制它们:
    [@ 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. 以及来自 qt 构建目录的资源 libs.xml:
    [@ 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. 像官方 Qt 指南 [https://doc.qt.io/qt-5/android-services.html] 中一样输入 AndroidManifest.xml,但您需要手动替换 %% 说明。我从.apk 的 AndroidManifest 中获得了这些值,该值是在 Qt 下构建的 这是我的:
    <?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. 最后你应该定义一个java类,继承QtService。我在 Qt 文档中得到了它:
    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;
        }
    
    }
    

    并从默认的 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));
    ...
    

    现在应该没问题了;)

    【讨论】:

      猜你喜欢
      • 2022-11-20
      • 2021-08-26
      • 2017-11-29
      • 2017-02-01
      • 2022-11-26
      • 1970-01-01
      • 1970-01-01
      • 2021-01-02
      • 1970-01-01
      相关资源
      最近更新 更多