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 cibles | Comportement CLI (-a, --arch) |
|---|---|---|---|
aarch64 | arm64-v8a | Smartphones 64-bit modernes (majorité du parc) | Inclus par défaut dans --arch all |
arm | armeabi-v7a | Smartphones 32-bit d'entrée de gamme | Inclus par défaut dans --arch all |
x86_64 | x86_64 | Émulateurs Android sur PC/Mac Intel | Supporté par le moteur Rust (current_arch()) |
x86 | x86 | Anciens émulateurs 32-bit | Supporté 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.apkou.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 :
- La CLI décompresse directement l'artefact final produit (
app-release.apkouapp-release.aab) en mémoire viaZipDecoder(package:archive). - 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
- Pour un APK (
- En cas de repli sur le répertoire
build/, la CLI donne une priorité stricte aux chemins contenantstripped_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 :
- Tous les fichiers DEX :
classes*.dex,base/dex/classes*.dex(bytecode Java / Kotlin compilé). - Le manifeste binaire :
AndroidManifest.xmloubase/manifest/AndroidManifest.xml(permissions, activités, services). - Toutes les bibliothèques natives C/C++ :
lib/<arch>/*.sooubase/lib/<arch>/*.so, en excluant explicitementbinaire 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),nativeCodeHashreste 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
applicationIdounamespacedansdefaultConfig. - Si
--flavor <nom>ou--env <nom>est fourni, inspecte le bloc correspondant dansproductFlavorsoubuildTypes. - Si le flavor définit un
applicationIdcomplet, il est utilisé tel quel. - Si le flavor définit un
applicationIdSuffix(ex:applicationIdSuffix ".dev"), il est concaténé à l'applicationIdde base (ex:com.saramoney.retail.dev).
5. Obfuscation Dart, ProGuard / R8 et symboles .symbols
- Obfuscation Dart (
--obfuscate) : activée par défaut dansPublisher(obfuscate = true). La CLI passe automatiquement--obfuscateet--split-debug-info=build/ota_symbols/<version>àflutter build, puis archive les fichiersapp.android-arm64.symbolsetapp.android-arm.symbolssur le serveur OTA. - R8 / ProGuard (Java/Kotlin) : l'activation de
minifyEnabled truedansbuild.gradles'applique au bytecodeclasses*.dex. Le moteur natif Rust est déjà compilé danslibflutter.soet 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.