Blog d'Obi Madu
Retour à tous les articles
MobileSystem DesignInfrastructure

Comment fonctionnent les applications mobiles

Les trois façons de construire une app mobile, ce que chacune fait réellement sous le capot, et le schéma d'architecture partagé derrière React Native, Flutter et Tauri mobile.

Comment fonctionnent les applications mobiles

Quand on commence à regarder le développement mobile, le nombre de frameworks et d'approches devient vite confus. On entend parler d'apps natives, de React Native, de Flutter, d'Expo, de Capacitor, de Cordova, d'Ionic, de Tauri mobile, et ce n'est pas toujours clair de savoir ce qui les différencie. Font-ils tous la même chose de différentes façons, ou certains sont-ils des architectures différentes à la base ?

La réponse, c'est les deux. Il y a trois façons principales de construire une app mobile, et la plupart des frameworks que tu entends tombent dans l'une d'elles. Les différences sont dans ce qui rend l'UI, ce qui exécute la logique, et comment les deux se parlent.

Les trois approches

Voici le paysage :

ApprocheL'UI est rendue avecLa logique tourne dansExemples
NatifComposants UI de la plateformeLangage de la plateformeApps Swift/Kotlin
Web wrapperWebView (navigateur)JavaScriptCapacitor, Cordova, Ionic
Cross-platformVarie (natif, canvas, ou WebView)JS, Dart, ou RustReact Native, Flutter, Tauri

La première chose à remarquer, c'est que « cross-platform » n'est pas une approche. C'est une catégorie, et à l'intérieur, la stratégie de rendu de l'UI est différente pour chaque framework. C'est de là que vient la plupart de la confusion. Les gens entendent « cross-platform » et supposent que ça veut dire une seule chose. Ce n'est pas le cas.

Parcourons chacune.

Les apps natives

C'est la ligne de base. Tu écris une app iOS en Swift ou Objective-C, avec UIKit ou SwiftUI. Tu écris une app Android en Kotlin ou Java, avec Android Views ou Jetpack Compose. Le code compile vers le binaire natif de la plateforme, l'UI est constituée des propres composants UI de la plateforme, et la logique tourne directement sur le runtime de la plateforme.

iOS:    Swift/SwiftUI  →  compilé  →  UIKit/SwiftUI rend à l'écran
Android: Kotlin/Compose →  compilé  →  Android Views/Compose rend à l'écran

L'avantage, c'est l'accès complet à tout ce que la plateforme offre. Chaque API, chaque composant UI, chaque fonctionnalité spécifique à la plateforme, chaque optimisation de performance. Il n'y a pas de pont, pas de couche de traduction, pas d'abstraction. Le code que tu écris est le code qui tourne.

Le coût, c'est que tu écris l'app deux fois. Une app iOS et une app Android sont deux bases de code complètement séparées, dans deux langages différents, avec deux frameworks UI différents, deux systèmes de build différents, et deux cibles de déploiement différentes. Si tu veux une fonctionnalité sur les deux plateformes, tu la construis deux fois.

C'est de là que viennent les deux autres approches. La catégorie web wrapper et la catégorie cross-platform existent toutes les deux parce qu'écrire tout deux fois coûte cher.

Les web wrappers

L'approche web wrapper prend une app web et la met à l'intérieur d'une coquille native. Capacitor (d'Ionic) et Cordova sont les principaux frameworks ici.

HTML/CSS/JS  →  WebView  →  coquille native  →  OS

L'UI est un WebView. C'est un moteur de navigateur, le même que le système utilise quand tu ouvres un lien à l'intérieur d'une app. Sur iOS c'est WKWebView, sur Android c'est l'Android WebView. Ton JavaScript tourne à l'intérieur de ce WebView, exactement comme il tourne à l'intérieur d'un onglet de navigateur. La coquille native autour fournit un pont pour que ton JavaScript puisse appeler des APIs de la plateforme, accéder à la caméra, lire le système de fichiers, afficher des notifications natives, et caetera.

Si tu as lu l'introduction à Tauri, cette structure devrait te sembler familière. Tauri sur le bureau utilise le même schéma : UI web à l'intérieur d'une fenêtre native, avec un pont vers l'OS. La différence, c'est le backend. Capacitor et Cordova font un pont vers le langage natif de la plateforme (Swift ou Kotlin), écrit par plateforme. Tauri fait un pont vers Rust, compilé nativement pour chaque cible mobile.

L'attrait du web wrapper est évident. Si tu as déjà une app web, tu peux la livrer comme une app mobile sans réécrire ton UI. Le même HTML, CSS et JavaScript qui tourne dans un navigateur tourne à l'intérieur du WebView. Tu écris un frontend, et il fonctionne partout.

Le compromis, c'est la performance et le ressenti natif. Un WebView est un navigateur. Il rend le HTML à l'écran, de la même façon qu'un navigateur. Les composants UI natifs sont plus rapides, plus fluides, et se sentent comme la plateforme d'une façon qu'un WebView n'atteint pas toujours. La physique du défilement, la gestion des gestes, les transitions et le comportement des entrées sont tous subtilement différents à l'intérieur d'un WebView que dans une UI native. Pour certaines apps, ça n'a pas d'importance. Pour d'autres, c'est tout le jeu.

Les frameworks cross-platform

C'est là que ça devient intéressant, parce que « cross-platform » veut dire des choses différentes pour des frameworks différents. Chacun choisit une stratégie de rendu d'UI différente et un mécanisme de pont différent.

React Native

React Native ne rend pas vers un WebView. Tes composants React décrivent une UI native, et React Native crée des vues natives iOS et Android réelles pour toi. Un View devient un UIView sur iOS et un android.view.View sur Android. Il n'y a pas de navigateur, pas de HTML, pas de DOM.

Composants React  →  pont JSI  →  composants UI natifs  →  écran

La logique tourne en JavaScript, sur un moteur JavaScript embarqué à l'intérieur de l'app. Le pont entre JavaScript et natif s'appelle JSI (JavaScript Interface). Il permet à JavaScript d'appeler directement des objets C++ natifs, ce qui est plus rapide que l'ancien pont qui sérialisait tout en JSON.

Si tu veux le plongeon complet sur React Native et Expo, j'ai écrit là-dessus ici. La version courte pour ce post : l'UI est native, la logique est JavaScript, et le pont est JSI.

Flutter

Flutter prend une approche différente pour l'UI. Il n'utilise pas de composants UI natifs, et il n'utilise pas de WebView. Flutter a son propre moteur de rendu, appelé Impeller (anciennement Skia), qui dessine directement sur un canvas. Chaque pixel à l'écran est peint par le propre moteur de Flutter.

Widgets Flutter  →  runtime Dart  →  Impeller rend au canvas  →  écran

La logique tourne en Dart, qui est compilé en code ARM natif pour la plateforme cible. Le pont entre Dart et les APIs de la plateforme s'appelle Platform Channels. Quand Flutter a besoin d'appeler une API de la plateforme, disons la caméra ou le système de fichiers, il envoie un message à travers un Platform Channel vers du code natif qui traite la requête.

L'attrait, c'est la cohérence. Parce que Flutter dessine sa propre UI, une app Flutter a l'air et se comporte de façon identique sur iOS et Android. Il n'y a pas de problème « ça ressemble à une app Android sur iOS », parce que l'UI n'utilise les composants d'aucune des deux plateformes. Le compromis, c'est qu'une app Flutter n'adopte pas automatiquement l'aspect et le ressenti de la plateforme. Si tu veux qu'elle se sente native, tu dois le construire toi-même.

Tauri mobile

Tauri mobile est le plus récent des trois, et il combine l'approche UI du web wrapper avec un backend Rust au lieu d'un backend en langage natif.

HTML/CSS/JS  →  WebView  →  Tauri IPC  →  Rust  →  OS

L'UI est un WebView, comme Capacitor et Cordova. La différence, c'est le backend. Au lieu de faire un pont vers du code Swift ou Kotlin écrit par plateforme, Tauri fait un pont vers Rust, qui est compilé nativement pour chaque cible mobile. Une base de code Rust gère le côté natif à la fois pour iOS et Android.

C'est là que le schéma à une base de code devient utile. La même stratégie d'interface et d'implémentation qui permet à une app desktop Tauri de partager du code avec un frontend web s'applique aussi au mobile. Tes interfaces de service restent les mêmes, et tu ajoutes une implémentation mobile à côté des implémentations web et desktop.

Le schéma partagé

Voici la partie qui a fait tout le paysage tenir ensemble pour moi. Les trois frameworks cross-platform suivent la même architecture :

   UI
    |
  Pont
    |
  Moteur

L'UI est la couche de présentation. Le pont est le mécanisme de communication. Le moteur est le runtime qui fait le travail réel. La technologie change, mais la forme reste la même.

FrameworkUIPontMoteur
React NativeComposants natifsJSIMoteur JavaScript
FlutterCanvas ImpellerPlatform ChannelsRuntime Dart
Tauri mobileWebViewIPCRust

Une fois que tu vois ce schéma, les différences entre les frameworks cessent d'être confuses. Ce sont toutes des variations de la même forme. React Native choisit l'UI native et JSI. Flutter choisit son propre canvas et Platform Channels. Tauri choisit un WebView et IPC vers Rust. Le choix porte sur les compromis que tu acceptes pour la couche UI et le mécanisme de pont que tu veux utiliser pour atteindre la plateforme.

Comment choisir

La question n'est pas de savoir quel framework est le meilleur. C'est de savoir quels compromis correspondent à ton app.

Natif (Swift/Kotlin) si tu as besoin d'un accès complet à la plateforme, de performances maximales, ou d'une UI qui se sent complètement native sur chaque plateforme. Le coût, c'est de maintenir deux bases de code.

React Native si tu veux des composants UI natifs pilotés par JavaScript, et que tu as déjà une équipe React ou que tu veux partager la logique avec une app web React. L'UI est native, la logique est JavaScript, et tu écris une base de code qui cible les deux plateformes.

Flutter si tu veux une cohérence pixel-perfect entre plateformes et que tu es prêt à construire l'aspect et le ressenti natifs toi-même. L'UI est le propre rendu de Flutter, la logique est Dart, et l'app a l'air identique sur iOS et Android par défaut.

Tauri mobile si tu as déjà un frontend web et un backend Rust, ou si tu veux étendre une app desktop Tauri au mobile sans changer de stack. L'UI est un WebView, le backend est Rust, et la même base de code peut cibler le bureau et le mobile.

Web wrapper (Capacitor/Cordova) si tu as déjà une app web et que tu veux la livrer sur mobile avec des changements minimaux, et que tu peux accepter les compromis du WebView autour des performances et du ressenti natif.

L'image plus grande

La plupart des frameworks cross-platform, et la plupart des architectures d'app modernes en général, suivent le schéma UI, Pont, Moteur. L'UI, c'est ce que l'utilisateur voit. Le pont, c'est comment l'UI parle au moteur. Le moteur, c'est ce qui fait le travail. Que tu construises une app mobile, une app de bureau, ou une app web, cette forme apparaît.

La technologie change. L'architecture non. Une fois que tu peux voir la forme, tu peux évaluer n'importe quel nouveau framework en posant trois questions : qu'est-ce qui rend l'UI, qu'est-ce qui exécute la logique, et comment communiquent-ils. Tout le reste, c'est du détail.