Aller au contenu principal

Spécificités Flutter Android

Sur Android, une application Flutter compilée en mode Release exécute son code Dart sous forme de code machine AOT natif empaqueté dans la bibliothèque partagée ELF binaire d'application, chargée par le moteur libflutter.so. Cette page détaille le fonctionnement technique du SDK et de la CLI sur Android.

1. Architectures ABI supportées (aarch64, arm, x86_64)​

Le moteur Rust (updater/library/src/config.rs) et le Publisher Dart (lib/src/cli/publisher.dart) associent chaque architecture OTA à son dossier ABI Android standard :

Identifiant d'architecture OTA (arch)Dossier ABI Android (lib/<abi>/binaire d'application)Appareils ciblesComportement CLI (-a, --arch)
aarch64arm64-v8aSmartphones 64-bit modernes (majorité du parc)Inclus par défaut dans --arch all
armarmeabi-v7aSmartphones 32-bit d'entrée de gammeInclus par défaut dans --arch all
x86_64x86_64Émulateurs Android sur PC/Mac IntelSupporté par le moteur Rust (current_arch())
x86x86Anciens émulateurs 32-bitSupporté par le moteur Rust (current_arch())

Lorsque vous lancez tk-ota-push release -p android ou tk-ota-push patch -p android avec --arch all (valeur par défaut), la CLI compile l'archive Android en une seule passe puis extrait et publie successivement les binaires aarch64 (arm64-v8a) et arm (armeabi-v7a).

2. APK vs Android App Bundle (.aab) et Zéro hash mismatch​

Lors d'une compilation Gradle en mode Release, le système de build génère plusieurs copies de binaire d'application dans build/ :

  • Des copies intermédiaires non strippées (ex: build/app/intermediates/merged_native_libs/...).
  • Des copies finales strippées (stripped_native_libs) empaquetées dans l'archive .apk ou .aab.

Si le serveur OTA scellait un binaire intermédiaire non strippé alors que le téléphone exécute le binaire strippé de l'APK/AAB, le moteur Rust rejetterait le patch avec l'erreur Update rejected: hash mismatch.

Pour éliminer ce risque à 100 %, Publisher._extractLibAppFromPackage (lib/src/cli/publisher.dart) applique la règle d'extraction directe :

  1. La CLI décompresse directement l'artefact final produit (app-release.apk ou app-release.aab) en mémoire via ZipDecoder (package:archive).
  2. Elle extrait le fichier exact :
    • Pour un APK (--artifact apk, défaut) : lib/<abi>/binaire d'application
    • Pour un App Bundle Google Play (--artifact aab) : base/lib/<abi>/binaire d'application
  3. En cas de repli sur le répertoire build/, la CLI donne une priorité stricte aux chemins contenant stripped_native_libs.

Exemple de publication au format Google Play App Bundle (.aab)​

# 1. Verrouiller la Release à partir de l'App Bundle (.aab)
tk-ota-push release -p android --artifact aab

# 2. Générer un patch compatible avec l'App Bundle distribué sur le Play Store
tk-ota-push patch -p android --artifact aab -m "Correctif affichage solde"

3. Garde-fou d'intégrité native Android (NativeIntegrityScanner)​

Sur Android, un patch OTA remplace binaire d'application (code Dart compilé), mais ne peut pas modifier le bytecode Java/Kotlin ni le manifeste Android déjà installés sur le téléphone.

Le NativeIntegrityScanner.scanAndroidArchive (lib/src/cli/native_integrity.dart) inspecte l'archive .apk ou .aab et calcule une empreinte SHA-256 déterministe (nativeCodeHash) sur trois catégories de fichiers :

  1. Tous les fichiers DEX : classes*.dex, base/dex/classes*.dex (bytecode Java / Kotlin compilé).
  2. Le manifeste binaire : AndroidManifest.xml ou base/manifest/AndroidManifest.xml (permissions, activités, services).
  3. Toutes les bibliothèques natives C/C++ : lib/<arch>/*.so ou base/lib/<arch>/*.so, en excluant explicitement binaire d'application.

Lors d'un tk-ota-push patch -p android :

  • Si vous avez uniquement modifié du code Dart (ou mis à jour un package 100 % Dart dans pubspec.lock), nativeCodeHash reste identique et le patch est publié immédiatement sans nécessiter --force.
  • Si vous avez ajouté un plugin natif Android, modifié du code Kotlin/Java ou ajouté une permission dans AndroidManifest.xml, localNativeHash != remoteRel.nativeCodeHash : la CLI bloque la publication et vous invite à créer une nouvelle Release Store (tk-ota-push release).

4. Product Flavors Gradle et détection de l'applicationId​

FlutterProject._detectAndroidApplicationId (lib/src/cli/project.dart) analyse automatiquement android/app/build.gradle.kts (Kotlin DSL) ou android/app/build.gradle (Groovy) ainsi qu'AndroidManifest.xml :

  • Extrait applicationId ou namespace dans defaultConfig.
  • Si --flavor <nom> ou --env <nom> est fourni, inspecte le bloc correspondant dans productFlavors ou buildTypes.
  • Si le flavor définit un applicationId complet, il est utilisé tel quel.
  • Si le flavor définit un applicationIdSuffix (ex: applicationIdSuffix ".dev"), il est concaténé à l'applicationId de base (ex: com.saramoney.retail.dev).

5. Obfuscation Dart, ProGuard / R8 et symboles .symbols​

  • Obfuscation Dart (--obfuscate) : activée par défaut dans Publisher (obfuscate = true). La CLI passe automatiquement --obfuscate et --split-debug-info=build/ota_symbols/<version> à flutter build, puis archive les fichiers app.android-arm64.symbols et app.android-arm.symbols sur le serveur OTA.
  • R8 / ProGuard (Java/Kotlin) : l'activation de minifyEnabled true dans build.gradle s'applique au bytecode classes*.dex. Le moteur natif Rust est déjà compilé dans libflutter.so et n'est pas affecté par la minification R8 des classes Java, tant que les classes standard du moteur Flutter (io.flutter.**) sont préservées par les règles par défaut de Flutter.

6. Permissions Android requises​

Seule la permission android.permission.INTERNET est requise dans android/app/src/main/AndroidManifest.xml :

<uses-permission android:name="android.permission.INTERNET"/>

Le téléchargement et le stockage des patchs s'effectuent intégralement dans le répertoire interne privé de l'application (/data/user/0/<package>/files et code_cache/downloads) : aucune permission READ_EXTERNAL_STORAGE ou WRITE_EXTERNAL_STORAGE n'est nécessaire.