Auf dieser Seite

Verwendung des Play Feature Delivery

Zeigt die Verwendung von Google Play Feature Delivery auf Qt.

Dieses Dokument beschreibt die Funktionalität des Feature Delivery Beispiels. Das Beispiel verwendet Funktionen, die seit Qt 6.11 unterstützt werden.
Play Feature Delivery kann auch auf älteren Qt-Versionen verwendet werden, aber die Erstellung der Anwendung erfordert das manuelle Hinzufügen des Android-Projekts und das Kopieren der Qt-Binärdateien. Eine Anleitung dazu finden Sie im Kapitel Feature Delivery auf Vor-Qt 6.11.

Was ist Feature Delivery?

Play Feature Delivery ist eine von Google angebotene Funktion, die es Entwicklern im Wesentlichen ermöglicht, ihre Projekte so zu strukturieren, dass der Google Play Store den Inhalt ihrer App in mehrere herunterladbare Pakete aufteilen kann. Außerdem haben die Entwickler so die Kontrolle darüber, wie die Inhalte an die Nutzer geliefert werden. Diese aufgeteilten Software- und Inhaltspakete werden über Android App Bundles (AAB) an den Google Play Store geliefert. In der Entwicklerdokumentation von Google wird diese Funktion ausführlich beschrieben.

Beispielprojekt: Feature Delivery Map Loader

Diese einfache App verwendet Play Feature Delivery, um einem Nutzer auf Anfrage Bilder zu liefern. Die App kann leicht modifiziert werden, um eine App zu erstellen, die mehr als 200 MB groß ist, um die Größenbeschränkungen der App und den Download mit einem großen Feature Delivery-Modul zu testen.

Die App

Die Anwendung besteht aus einer verschiebbaren Ansicht und vier Schaltflächen.

  • Beim Starten sind die Schaltflächen Load Map und Show Map Info aktiviert.
  • Beim Anklicken der Schaltfläche Show Map Info wird lediglich angezeigt, dass keine Informationen verfügbar sind.
  • Load Map initiiert das Laden des Funktionsmoduls:
    1. Es wird ein Download-Pop-up angezeigt, in dem der Download abgebrochen werden kann.
    2. Wenn der Download abgeschlossen ist, wird das Popup-Fenster entfernt und die Schaltflächen
    3. Change Map und die Schaltflächen Remove Map sind aktiviert.
  • Remove Map Schaltfläche fordert die Deinstallation des Funktionsmoduls an, wodurch die aktivierten Schaltflächen deaktiviert werden.
  • Wenn Change Map angeklickt wird, öffnet sich eine Ansicht, in der eine angezeigte Karte geändert werden kann. In diesem Beispiel besteht das Funktionsmodul nur aus einem Kartenbild mit Wintermotiven.
Einrichtung des Quellordners
  • fdwintermapmodule: Feature-Modul
  • fdmaploader: Die Hauptanwendung
  • fdmaploader/storeloader: Feature Delivery JNI-Schnittstelle
Feature Delivery-Schnittstelle

Der Ordner fdmaploader/storeloader enthält die Schnittstellenklasse PlayStoreLoader für die Feature Delivery API. Die API ist nicht vollständig, enthält aber relevante Funktionen zum Laden und Entfernen von Feature-Modulen. Das Laden von Modulen wird mit einem Aufruf an PlayStoreLoader::loadModule eingeleitet. Der Status des Prozesses kann mit Signalen überwacht werden, die von PlayStoreLoaderHandler bereitgestellt werden. Ein Handle auf die Callbacks kann mit der Funktion PlayStoreLoader::getHandler erhalten werden. Die Beispiel-API bietet auch eine Möglichkeit zur Überprüfung bereits installierter Module mit der Funktion PlayStoreLoader::getInstalledModules und eine Option zum Entfernen installierter Module mit PlayStoreLoader::uninstallModules.

Dieses Beispiel ist so konzipiert, dass ein Entwickler leicht eigene Inhalte hinzufügen kann, um die Funktionsbereitstellung zu testen. Um die maximale Paketgröße im Play Store zu überschreiten, können Kartenbilder (es müssen nicht unbedingt Kartenbilder sein, aber es passt zum Thema des Beispiels) zu den Bilderordnern in fdmaploader und fdwintermapmodule hinzugefügt werden, die Bildnamen müssen auch zur Datei images.qrc hinzugefügt werden.

Unter der Haube

Das API-Modul besteht aus zwei Teilen. Qt-Schnittstelle (PlayStoreLoader und PlayStoreLoaderHandler) und Java-Klassen, die für den Aufruf von Android zuständig sind: Google Split Install Interfaces. Die Qt-Schnittstelle dient hauptsächlich als Durchleitung für die Java-Klassen. Die Qt-Schnittstelle vereinfacht die API, so dass beim Laden des Feature-Moduls die Google SplitCompat und SplitInstall Klassen und Listener automatisch erstellt und freigegeben werden. In diesem Beispiel werden Teile der APIs weggelassen, wie deferredInstall und Sprachunterstützung.
Qt creates and builds package suitable for Google Play deployment using qt6_add_android_dynamic_features when it is defined CMakeLists.

qt6_add_android_dynamic_features(${target_name}
    FEATURE_TARGETS fdwintermapmodule)

Die CMake-Funktion qt6_add_android_dynamic_features fügt die spezifische dynamische Bibliothek als dynamisches Merkmal für das Android-Anwendungsziel hinzu. Dazu muss das Merkmal QT_USE_ANDROID_MODERN_BUNDLE aktiviert sein. Dies kann entweder als Flag zur Kompilierzeit oder in CMakeLists gesetzt werden. Der Ordner, in dem sich der Java-Teil der Schnittstelle befindet, wird dem Build mit qt_add_android_dynamic_feature_java_source_dir hinzugefügt.

In der Beispielanwendung werden die Module über die Schnittstelle storeloader geladen. Die Funktionen von Qt PlayStoreLoader und die Klasse PlayStoreLoaderHandler fungieren als Vermittler zwischen dem Beispielcode und Java.

void PlayStoreLoader::loadModule(const QString & callId, const QString &moduleName) { if (callId.isEmpty() || moduleName.isEmpty()) return; if (!loaderInstance->registerNatives()) return; if (!loaderInstance->loader().isValid()) {        qCritical("StoreLoader not constructed");
       return; }  loaderInstance->loader().callMethod<void>("installModuleFromStore", moduleName, callId); }

Java-Klassen verarbeiten Aufrufe an die Split Install API.

        m_splitInstallManager.startInstall(request)
                .addOnSuccessListener(sessionId -> {
                    PlayStoreLoaderListener listener = m_listeners.get(callId);
                    if (listener != null)
                        listener.setSessionId(sessionId);
                })
Erstellen von Binärdateien

Über die Kommandozeile kann ein lokal testbares AAB-Paket erstellt werden.

Erstellen Sie ein Build-Verzeichnis auf der gleichen Ebene wie das Quellverzeichnis und geben Sie es ein:

mkdir build-feature-delivery/ ; cd build-feature-delivery/

Konfigurieren:

path-to-qt-version/path-to-abi/bin/qt-cmake -GNinja -B . -S ../feature-delivery/ -DQT_USE_TARGET_ANDROID_BUILD_DIR=ON -DCMAKE_BUILD_TYPE=Debug

Bauen:

ninja aab
Prüfung

Das erstellte AAB kann lokal getestet werden, indem bundletool mit dem Parameter --local-testing verwendet wird. Android: Bundletool Dokumentation Der Befehl bundletool build-apks erstellt eine apks-Datei, die dann mit dem Befehl install-apks auf einem Gerät oder Emulator installiert werden kann.

Verwendete Bundletool-Befehle

Erzeugen von APK:s aus einem Bundle:

bundletool build-apks --bundle=/path/to/bundle.aab --output=/path/to/apk/package.apks --local-testing

App auf dem Gerät installieren:

bundletool install-apks --apks=/path/to/apk/package.apks
Übergabe an den Play Store

Um das erstellte AAB-Paket in den Google Play Store hochladen zu können, muss das Paket signiert werden. Hierfür kann jarsigner verwendet werden. Unten ist ein Beispiel für einen jarsigner-Befehl zum Signieren des AAB-Pakets. Siehe Android: Jarsigner-Dokumentation

jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 -keystore [path-to-keystore-file].keystore [path-to-aab-file].aab [alias]

Feature-Auslieferung vor Qt 6.11

Versionen vor Qt 6.11 unterstützen keine Google Play Store kompatible Paketerstellung. Wenn Sie nicht in der Lage sind, eine Qt-Version 6.11 oder höher zu verwenden, gibt es eine Möglichkeit, Google Play Feature Delivery zu nutzen, aber es erfordert die manuelle Erstellung des Android-Projekts und das Kopieren der Qt-Binärdateien in dieses Projekt. Der Rest des Dokuments enthält Anweisungen, wie man das erreicht. Die Anweisungen sind möglicherweise nicht 1 zu 1 kompatibel für alle Umgebungen, sollten aber gute Hinweise für eine erfolgreiche Feature Delivery-Implementierung geben. Es wird empfohlen, das Beispiel als Grundlage zu verwenden.

Feature-Modul

Feature-Module werden wie normale Bibliotheken erstellt.

  • Verwenden Sie Qt Creator, um eine gemeinsame C++-Bibliothek zu erstellen.
  • Implementieren Sie Features und fügen Sie Ressourcen hinzu.
  • Bauen Sie, um .so-Binärdateien zu erstellen.

Feature Delivery behandelt C++-Bibliotheken wie normale Shared Libraries, die zur Laufzeit verfügbar sein können oder auch nicht. Vor dem Aufruf einer Bibliothek muss die Verfügbarkeit einer Bibliothek geprüft werden.

Hauptanwendung (Qt)
  • Verwenden Sie Qt Creator, um eine App zu erstellen (Qt Quick Projektvorlage wurde hier verwendet).
  • Implementieren Sie den Zugriff auf die Feature Delivery Bibliothek. Die zentrale Klasse in der Google Play Feature Delivery Java-Bibliothek ist Android: SplitInstallManager.
  • Android-Vorlagendateien können über die Schaltfläche "Create Templates" im QtCreator Projects -> Build&Run -> [target ABI] -> Build Steps -> Build Android APK erstellt werden. Vorlagen werden im Ordner 'android' im Projekt erstellt.
  • Fügen Sie Java-Dateien zum Ordner .../android/src/java/[package...] und Dateipfade zu CMakeLists.txt:
    qt_add_executable...
    ...[path]/[java-filename.java]
    ...
    hinzu
  • . Im Beispiel wurde eine Java-Klasse erstellt, um die Aufrufe und Rückrufe zu behandeln. Auf die Java-Klasse würde dann von Qt aus mit JNI zugegriffen werden. Android: Request an on demand module section in the Android documentation has a simple description of how to request a module.
  • When adding Java files under the android folder in the project, QT_ANDROID_PACKAGE_SOURCE_DIR property must be added to the CMakeLists.txt
  • :
  • ...
    set_property(TARGET appFDMainApp APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
    ...
  • Außerdem muss die Hauptanwendung build.gradle Abhängigkeiten für die Feature-API haben: in the dependencies block, replace
    implementation 'androidx.core:core:1.13.1'

    with

    implementation("com.google.android.play:feature-delivery:2.1.0")
  • Implementieren Sie den Zugriff auf die vom Feature-Modul bereitgestellte Bibliothek. Da das Feature-Modul für die Hauptanwendung verfügbar sein kann oder auch nicht, werden die Module nicht zur Erstellungszeit verlinkt und die Aufrufe an das Modul müssen zur Laufzeit aufgelöst werden. Beispiel:
    QString MapLoader::loadMapInfo() { QScopedPointer<QString> resultStr; typedef  void*(*LoadMapInfoFunc)(); //Finden, ob Wintermap-Bibliothek existiertmWintermapLibrary.setFileName("fdwintermapmodule"); if (!mWintermapLibrary.load()) {        qWarning() << Q_FUNC_INFO << "Failed to load library";
           return QString(); } LoadMapInfoFunc loadMapInfo = (LoadMapInfoFunc) mWintermapLibrary.resolve("loadMapInfo"); if (loadMapInfo) {  void* result = loadMapInfo(); resultStr.reset(static_cast<QString*>(result)); } else        qWarning() << Q_FUNC_INFO << "Function loadMapInfo not loaded";
    
       return *resultStr.data(); }
  • Implementieren Sie die Benutzeroberfläche und andere erforderliche Teile der Hauptanwendung.
Funktionsmodul (Qt)
  • Verwenden Sie Qt Creator, um eine Anwendung zu erstellen (die Projektvorlage Qt C++ Library wurde verwendet).
  • Implementieren Sie die Funktionen, die das Modul bietet.
Android-Projekt (Android)

Die Erstellung eines Projekts zur Erstellung eines Android-App-Bundles für Feature Delivery basiert hauptsächlich auf der Dokumentation von Android:

Erstellen Sie ein Android-Projekt manuell oder mit Android Studio (unter Verwendung der Vorlage "No Activity"). Das Projekt wird so modifiziert, dass es ein Top-Level-Projekt und zwei Unterprojekte enthält, app und feature-module. Die Android Studio-Vorlage erstellt das Unterprojekt app und feature-module kann mit der Vorlage File -> New -> New Module hinzugefügt werden.

Das Vorlagenprojekt erfordert mehrere Änderungen:

  • Fügen Sie das Feature Delivery Plugin auf der Hauptebene build.gradle hinzu:
    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
    }
  • Fügen Sie das Feature-Modul zur settings.gradle hinzu, ändern Sie rootProject.name falls erforderlich:
    ...
    rootProject.name = "name-of-the-root-project"
    include(:app)
    include(:name-of-the-feature-module)
app - Unterprojekt
  • Das Android-Projekt benötigt Qt-Binärdateien aus dem App-Hauptprojekt:
    • Kopieren Sie die nativen Bibliotheken in Qt build: [build directory]/android-build/libs/[target ABI] nach app/src/main/jniLibs/[target ABI]
    • Kopieren Sie die Jars in [build directory]/android-build/libs/ nach app/libs/
  • Aus dem Qt-Build werden auch die Inhalte der Ordner res, AndroidManifest.xml und local.properties an die entsprechenden Stellen im Android-Projekt kopiert.
  • Fügen Sie die Datei feature_names.xml zum Ordner app/src/main/res/values hinzu, die einen String für das Feature-Modul enthält:
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="feature_module_name">name-of-the-feature-module-here</string>
    </resources>
  • Fügen Sie die Datei keep.xml zum Ordner app/src/main/res/raw hinzu, die Folgendes enthält:
    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@string/feature_module_winter_map"
        tools:discard="" />
Änderungen an den Build-Dateien des App-Unterprojekts

Die in das Android-Projekt kopierten Build-Dateien benötigen einige Änderungen.

app - Unterprojekt
build.gradle
  • Entfernen Sie die Blöcke buildScript und repositories.
  • Der Android-Block in der Haupt-App build.gradle erfordert einige Änderungen:
    • 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
...
}

Fügen Sie dem Android-Block auch eine Signierkonfiguration hinzu:

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 hat Projektvariablen zu gradle.properties hinzugefügt. Ändern Sie bei Bedarf den Wert von androidPackageName.

AndroidManifest.xml
  • Entfernen Sie package:
    ...
    <manifest
    ...
      android:package... <--remove
    ...
    >
    ...
  • Ändern Sie label und android.app.lib_name, falls erforderlich:
    ...
    <application ...
      android:label=" ...
      <activity ... >
        <meta-data android:name="android.app.lib_name" android:value=" ...
        />
    ...
feature-module - Unterprojekt

App- und Feature-Module werden als Unterprojekte für das übergeordnete Android-Projekt erstellt. Die Ordner- und Dateistruktur ist ähnlich wie die des App-Unterprojekts.

  • Feature-Modul-Binärdateien aus dem Qt-Build werden in den Ordner [name-of-feature-module]/src/main/jniLibs/
  • Wie in der Haupt-App sollte der Ordner src/main/res/ die Ordner xml und values haben, die qtprovider_paths.xml bzw. libs.xml enthalten. Beide Dateien können aus dem App-Projekt kopiert werden.
  • Wenn der Ordner src/main/res/ Drawable- oder Mipmap-Ordner enthält und das Feature diese nicht benötigt, können sie entfernt werden.
  • Im Feature-Modul sollte src/main/res/values nicht das Feld app_name enthalten. In einfachen Projekten, in denen strings.xml nicht für andere Zwecke benötigt wird, kann es entfernt werden.
  • libs.xml enthält nur den Namen des Merkmalsmoduls:
    ...
        <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 wird dem Verzeichnis src/main/ hinzugefügt:
    <?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>
  • Das Feature-Modul build.gradle ist dem des App-Projekts recht ähnlich, mit einigen Änderungen. Hier ist ein Beispiel:
    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 Die Datei kann aus dem app-Unterprojekt kopiert werden, wobei das androidPackageName in das Feature-Modul-Paket geändert wird.
Bauen und Bereitstellen

Das AAB-Bündel kann über die Befehlszeile mit dem gradle-Wrapper erstellt werden: ./gradlew bundle Das erstellte AAB befindet sich im Ordner build/outputs/bundle/release (oder debug). Das AAB kann dann in den Google Play Store kopiert und zum Testen freigegeben werden. Das Testen kann auch lokal durchgeführt werden, indem bundletool mit dem Parameter --local-testing verwendet wird.

Beispielprojekt @ code.qt.io

© 2026 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.