使用播放功能交付

什么是功能交付?

谷歌的开发者文档详细介绍了这一功能,它主要是允许开发者以 Google Play 商店可以将应用内容拆分成多个可下载包的方式来构建项目。它还能让开发者控制向用户交付内容的方式。这些分割的软件和内容包通过 Android 应用程序包(AAB)传送到 Google Play 商店。

示例项目:FDMapLoader

此应用程序使用播放功能交付,在用户请求时向其提供图片。可以轻松修改该应用程序,创建一个超过 200MB 的应用程序,以测试应用程序的大小限制,并使用大型功能交付模块进行下载。

构建设置

  • fdwintermapmodule:功能模块项目,用于Qt Creator
  • fdmaploader:的主应用程序项目Qt Creator
  • fdmaploader-android-project:用于编译 Android 应用程序包的 Android 项目。

编译 FDWintermapModule - 功能模块

加载fdwintermapmodule项目到Qt Creator ,为目标平台进行配置,然后构建。

编译 FDMapLoader - 主应用程序

加载fdmaploaderQt Creator ,根据目标平台进行配置,然后构建。

修改 Android 项目

  • 将功能模块和主应用程序构建目录中的已构建二进制文件(.so)复制到 Android 项目中相应的库目录。(app/src/main/jniLibs/[target ABI]/fdwintermapmodule/src/main/jniLibs/[target ABI]/ )
  • 复制其他已更改的文件,如资源文件和 Java 类。(.../res/....../src/main/java/... 文件夹)
  • 使用 Android Studio 或通过命令行使用 Gradle 封装程序(./gradlew bundle)构建 Android 项目
  • 要进行测试,可以使用bundletool 从 bundle 创建 APK 并将本地测试版本安装到设备上。Bundletool 是 Google 提供的一个工具,我们将在本文档的稍后部分介绍它的使用。为了测试和发布,您可以将捆绑包上传到 Google Play 控制台。

添加自己的内容

本示例旨在让开发者可以轻松添加自己的内容来测试功能交付。为超出 Play Store 最大软件包大小,可在 FDMapLoader 和 FDWintermapModule 中的图片文件夹中添加地图图片(不一定是地图图片,但要符合本例的主题),图片名称也必须添加到images.qrc 文件中。

创建您自己的

以下部分将介绍如何创建自己的项目来使用播放功能交付。建议以示例为基础。在该项目中,使用的是 Qt 6.7.2。

功能模块

功能交付会像处理普通共享库一样处理 C++ 库,这些库在运行时可能可用,也可能不可用。在调用库之前,必须检查库的可用性。

  • 使用Qt Creator 创建 C++ 共享库。
  • 实现功能并添加资源。
  • 构建以创建 .so 二进制文件。

主应用程序(Qt)

  • 使用Qt Creator 创建应用程序(此处使用Qt Quick 项目模板)。
  • 实现对功能交付库的访问。Google Play 功能交付 Java 库的中心类是 SplitInstallManager.
  • Android 模板文件可通过 QtCreatorProjects -> Build&Run -> [target ABI] -> Build Steps -> Build Android APK 上的 "创建模板 "按钮创建。模板创建在项目的 "android "文件夹中。
  • 将 Java 文件添加到.../android/src/java/[package...] 文件夹,并将文件路径添加到CMakeLists.txt
    qt_add_executable...
    ...[path]/[java-filename.java]
    ...
  • 在示例中,创建了一个 Java 类来处理调用和回调。然后将使用 JNI 从 Qt 访问 Java 类。Android 文档简单介绍了如何请求模块。
  • 在项目的 android 文件夹下添加 Java 文件时,必须在CMakeLists.txt 中添加QT_ANDROID_PACKAGE_SOURCE_DIR属性:
    ...
    set_property(TARGET appFDMainApp APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
    ...
  • 此外,主应用程序build.gradle 必须有功能 API 的依赖项:在依赖项块中,替换为
    implementation 'androidx.core:core:1.13.1'

    替换为

    implementation("com.google.android.play:feature-delivery:2.1.0")
  • 实现对功能模块提供的库的访问。由于功能模块可能对主应用程序可用,也可能不可用,因此模块不会在构建时链接,必须在运行时解决对模块的调用。示例
    typedef void* (*LoadModuleRulesFunc)();
    LoadModuleRulesFunc loadModuleRules =
        (LoadModuleRulesFunc) mFDModuleLibrary.resolve("loadModuleRules");
    if (loadModuleRules) {
        void* result = loadModuleRules();
        QScopedPointer<QString> resultStr{static_cast<QString*>(result)};
    }
  • 实现用户界面和主程序的其他必要部分。

功能模块(Qt)

  • 使用Qt Creator 创建应用程序(使用了 Qt C++ 库项目模板)。
  • 实现模块提供的功能。

安卓项目(安卓)

创建一个用于构建 Android 应用程序捆绑包的项目以交付功能,主要是基于 Android 的文档:

手动或使用 Android Studio(使用 "无活动 "模板)创建一个 Android 项目。修改后的项目包含一个顶级项目和两个子项目:appfeature-module 。Android Studio 模板创建了app 子项目,feature-module 可使用File -> New -> New Module 模板添加。

模板项目需要进行几处修改:

  • 在主层中添加功能交付插件build.gradle
    plugins {
        id 'com.android.application' version '8.5.2' apply false
        id 'com.android.dynamic-feature' version '8.5.2' apply false
        id 'com.android.library' version '8.5.2' apply false
    }
  • settings.gradle 中添加功能模块,必要时修改rootProject.name
    ...
    rootProject.name = "name-of-the-root-project"
    include(:app)
    include(:name-of-the-feature-module)

应用程序 - 子项目

  • Android 项目需要主应用程序项目中的 Qt 二进制文件:
    • 将 Qt build:[build directory]/android-build/libs/[target ABI] 中的本地库复制到app/src/main/jniLibs/[target ABI]
    • [build directory]/android-build/libs/ 中的 jars 复制到app/libs/
  • 从 Qt 构建中,还将res 文件夹、AndroidManifest.xmllocal.properties 中的内容复制到 Android 项目中的相应位置。
  • app/src/main/res/values 文件夹中添加包含功能模块字符串的文件feature_names.xml
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="feature_module_name">name-of-the-feature-module-here</string>
    </resources>
  • 将文件keep.xml 添加到app/src/main/res/raw 文件夹,其中包含:
    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@string/feature_module_winter_map"
        tools:discard="" />

修改应用程序子项目构建文件

复制到 Android 项目的构建文件需要进行一些修改。

应用程序 - 子项目

build.gradle
  • 删除buildScriptrepositories 块。
  • 主应用程序build.gradle 中的 Android 块需要进行一些修改:
    • defaultConfig
    • packagingOptions
    • dynamicFeatures
    • sourceSets
    • aaptOptions
    • dependencies
android {
...
  defaultConfig {
  ...
    applicationId "your-project-name-here"
  ...
  }
  packagingOptions.jniLibs.useLegacyPackaging true

  dynamicFeatures = [":your-dynamic-feature-name-here"]

  sourceSets {
    main {
      manifest.srcFile 'src/main/AndroidManifest.xml'
      java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
      aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
      res.srcDirs = [qtAndroidDir + '/res', 'res']
      resources.srcDirs = ['resources']
      renderscript.srcDirs = ['src']
      assets.srcDirs = ['assets']
      jniLibs.srcDirs = ['src/main/jniLibs/']
    }
  }

  // Do not compress Qt binary resources file
  aaptOptions {
    noCompress 'rcc'
  }
...
}

dependencies {
...
  implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
  implementation 'com.google.android.play:feature-delivery:2.1.0'
  implementation libs.material
...
}

同时在 Android 代码块中添加签名配置:

android {
...
  signingConfigs {
    release {
      store

      \code
android {
...
  signingConfigs {
    release {
      storeFile file("/absolute/path/to/the/keystore.jks")
      storePassword "myStorePassword"
      keyAlias "myKeyAlias"
      keyPassword "myKeyPassword"
    }
  }
  buildTypes {
    release {
      signingConfig signingConfigs.release
      ...
    }
  }
...
}
gradle.properties

Qt XML 已在gradle.properties 中添加了项目变量。如有需要,请更改androidPackageName 的值。

AndroidManifest.xml
  • 移除package
    ...
    <manifest
    ...
      android:package... <--remove
    ...
    >
    ...
  • 如有需要,更改labelandroid.app.lib_name
    ...
    <application ...
      android:label=" ...
      <activity ... >
        <meta-data android:name="android.app.lib_name" android:value=" ...
        />
    ...

功能模块 - 子项目

应用程序和功能模块作为顶级 Android 项目的子项目创建。文件夹和文件结构与应用程序子项目类似。

  • Qt 构建的功能模块二进制文件会复制到[name-of-feature-module]/src/main/jniLibs/
  • 与主应用程序一样,src/main/res/ 文件夹也应包含xmlvalues 文件夹,其中分别包含qtprovider_paths.xmllibs.xml 。这两个文件都可以从应用程序项目中复制。
  • 如果src/main/res/ 文件夹包含 drawable 或 mipmap 文件夹,而功能不需要它们,则可将其删除。
  • 在功能模块src/main/res/values 中不应包含app_name 字段。在不需要 strings.xml 的简单项目中,可以删除 strings.xml。
  • libs.xml strings.xml 仅包含功能模块的名称:
    ...
        <array name="load_local_libs">
            <item>name-of-the-feature-module-here</item>
        </array>
    
        <string name="static_init_classes"></string>
        <string name="use_local_qt_libs">0</string>
        <string name="bundle_local_qt_libs">0</string>
    ...
  • AndroidManifest.xml 添加到 目录中:src/main/
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:dist="http://schemas.android.com/apk/distribution">
    
        <dist:module
            dist:instant="false"
            dist:title="@string/feature_module_title_string">
            <dist:delivery>
                <dist:on-demand />
            </dist:delivery>
            <dist:fusing dist:include="false" />
        </dist:module>
        <!-- This feature module does contain code. -->
        <application android:hasCode="true"/>
    </manifest>
  • 功能模块build.gradle 与应用程序项目中的功能模块十分相似,但有一些变化。下面是示例项目中的一个例子:
    plugins {
        id 'com.android.dynamic-feature'
    }
    
    dependencies {
        implementation project(':app')
        implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
        implementation 'com.google.android.play:feature-delivery:2.1.0'
    }
    
    android {
    
        namespace = androidPackageName
        compileSdk = androidCompileSdkVersion
        ndkVersion androidNdkVersion
    
        // Extract native libraries from the APK
        packagingOptions.jniLibs.useLegacyPackaging true
    
        defaultConfig {
            resConfig "en"
            minSdkVersion qtMinSdkVersion
            targetSdkVersion qtTargetSdkVersion
        }
    
        sourceSets {
            main {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                resources.srcDirs = ['resources']
                renderscript.srcDirs = ['src']
                assets.srcDirs = ['assets']
                jniLibs.srcDirs = ['src/main/jniLibs/']
           }
        }
    
        tasks.withType(JavaCompile) {
            options.incremental = true
        }
    
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
        lintOptions {
            abortOnError false
        }
    
        // Do not compress Qt binary resources file
        aaptOptions {
            noCompress 'rcc'
        }
    }
  • gradle.properties 文件可以从应用程序子项目中复制过来,将 改为功能模块包。androidPackageName

构建和部署

可使用 gradle 封装器在命令行中构建 AAB 包:./gradlew bundle 生成的 AAB 将放在build/outputs/bundle/release (或debug )文件夹中。然后可将 AAB 复制到 Google Play 商店并发布测试。也可以通过bundletool--local-testing 参数在本地进行测试。捆绑工具文档

使用的 Bundletool 命令

  • 从捆绑包生成 APK:s:
    bundletool build-apks --bundle=/path/to/bundle.aab --output=/path/to/apk/package.apks --local-testing
  • 将应用程序安装到设备上:
    bundletool install-apks --apks=/path/to/apk/package.apks

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.