Android App Links en 2026 : pourquoi vos liens https:// s'ouvrent encore dans le navigateur
La vérification Digital Asset Links, les pièges d'assetlinks.json et les commandes adb qui expliquent pourquoi Android ne confie pas le lien à votre app.
Un schéma d’URI personnalisé comme myapp://item/42 est facile à mettre en place, et facile à mal faire d’une autre manière : n’importe quelle app peut enregistrer le même schéma, donc le système ne peut pas garantir que la vôtre est bien celle qui l’ouvre. Un Android App Link — une vraie URL https://votredomaine.com/... qui ouvre directement l’app plutôt qu’un onglet de navigateur — corrige ça, mais seulement si le système peut vérifier que votre app possède réellement le domaine. La plupart du temps, un lien comme celui-ci retombe silencieusement sur Chrome, et le manifest a l’air parfaitement correct. La pièce manquante est presque toujours la vérification Digital Asset Links, pas l’intent filter.
Les deux éléments qui doivent concorder
Un App Link ne se vérifie automatiquement que lorsque deux éléments indépendants s’accordent : l’intent filter de l’app, et un fichier JSON servi par le domaine lui-même. Que ni l’un ni l’autre ne connaisse l’existence de l’autre, c’est précisément le but — c’est ce qui empêche une app quelconque de revendiquer votre domaine.
L’intent filter va dans le manifest :
<activity android:name=".MainActivity" android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="mfkapps.com"
android:pathPrefix="/apps" />
</intent-filter>
</activity>
android:autoVerify="true" est ce qui déclenche la vérification réelle de la propriété par Android à l’installation, plutôt qu’un simple filtrage sur le motif de l’URL. Omettez-le et le filtre continue de matcher les liens, mais seulement après que l’utilisateur a explicitement choisi votre app une fois dans la feuille de désambiguïsation — il n’est jamais promu au niveau « toujours ouvrir automatiquement ».
Côté domaine, il s’agit d’un fichier statique à un chemin fixe, non négociable :
https://mfkapps.com/.well-known/assetlinks.json
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.mfk.granyn",
"sha256_cert_fingerprints": [
"14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A3:88:1E:22:0B:99:19:33:4B:C0:5B:0B:64:0C:1D"
]
}
}]
package_name et sha256_cert_fingerprints doivent tous les deux correspondre au build exact installé. Ce détail de fingerprint est là où presque toute configuration cassée se casse réellement.
L’écart de fingerprint qui casse tout silencieusement
sha256_cert_fingerprints a besoin du SHA-256 du certificat de signature de l’APK que l’utilisateur a installé — et si l’app est distribuée via Play App Signing, c’est la clé de signature de Google, pas votre clé d’upload signée localement. Mettez le mauvais fingerprint dans assetlinks.json, et la vérification échoue sans qu’aucune erreur ne soit jamais visible pour l’utilisateur ; le lien s’ouvre dans un navigateur, pour toujours, et rien dans Logcat ne pointe vers ce fichier.
Récupérez le bon fingerprint depuis la Play Console, pas depuis votre keystore local :
Play Console → Setup → App integrity → App signing key certificate → SHA-256
Pour un build de debug pendant le développement, utilisez le fingerprint du keystore de debug, et listez les deux dans le tableau si vous voulez que debug et release se vérifient contre le même assetlinks.json :
keytool -list -v -keystore ~/.android/debug.keystore \
-alias androiddebugkey -storepass android -keypass android
sha256_cert_fingerprints accepte un tableau, donc les deux fingerprints peuvent coexister — la vérification vérifie si une entrée quelconque correspond à la signature de l’app installée.
Vérifier que ça a réellement fonctionné
Android exécute la vérification à l’installation et met le résultat en cache, donc le moyen le plus rapide de voir le vrai statut est de demander directement au système plutôt que de deviner d’après le comportement :
adb shell pm get-app-links com.mfk.granyn
Une configuration qui fonctionne rapporte le domaine comme verified :
com.mfk.granyn:
ID: d3a1f0b2-...
Signatures: [14:6D:E9:...]
Domain verification state:
mfkapps.com: verified
Si ça indique legacy_failure ou none, le fingerprint ou le JSON est incorrect — corrigez assetlinks.json, puis forcez une nouvelle vérification sans réinstaller :
adb shell pm verify-app-links --re-verify com.mfk.granyn
adb shell pm get-app-links com.mfk.granyn
Pour tester le comportement réel au moment du tap une fois la vérification passée, déclenchez l’intent comme le ferait un navigateur ou une autre app :
adb shell am start -a android.intent.action.VIEW \
-d "https://mfkapps.com/apps/granyn"
Si l’app s’ouvre sans feuille de sélection, la vérification fonctionne. Si une boîte de dialogue de désambiguïsation apparaît, la vérification a échoué et le système traite ça comme un simple lien non vérifié.
Trois choses qui la cassent silencieusement après le lancement
Le JSON doit être servi avec le bon type de contenu et sans redirection. Un CDN ou un reverse proxy qui redirige en 301 /.well-known/assetlinks.json vers une URL canonique, ou qui le sert en text/html, fait échouer la vérification même si un navigateur l’affiche correctement. Vérifiez les deux directement avec curl :
curl -sI https://mfkapps.com/.well-known/assetlinks.json | grep -i content-type
Re-signer l’app change le fingerprint. Faire tourner une clé de signature, passer d’un keystore local à Play App Signing, ou intégrer un nouveau pipeline de build cassent tous silencieusement chaque lien précédemment vérifié jusqu’à ce que assetlinks.json soit mis à jour avec le nouveau fingerprint. Ça mérite une ligne dans une checklist de release, parce que le mode de défaillance — les liens qui retombent silencieusement sur le navigateur — ne produit ni crash ni rapport d’erreur.
Plusieurs apps partageant un domaine ont besoin de plusieurs déclarations. Si plus d’une app de la même équipe revendique des chemins sous le même host, assetlinks.json est un tableau JSON — un objet par app, chacun avec son propre package_name et sa liste de fingerprints. Un fichier ne contenant que la déclaration d’une seule app fait échouer silencieusement la vérification pour toutes les autres apps de ce domaine.
Ce que ça apporte au-delà d’un tap plus agréable
Une fois un domaine vérifié, le même intent filter permet aussi à l’app d’agir comme gestionnaire par défaut de ce domaine partout où le système transmet une URL — une action de notification, un QR code, un lien partagé depuis une autre app, un flux d’identifiants Smart Lock. Rien de tout ça n’a besoin d’un câblage séparé ; c’est le même filtre autoVerify et le même assetlinks.json, qui font simplement plus une fois que le système fait réellement confiance à ce qu’il y a derrière.
Si un lien vers l’app s’ouvre encore dans un navigateur après tout ça, vérifiez pm get-app-links avant de retoucher au manifest — en pratique, l’intent filter n’est presque jamais la moitié cassée.
// À lire aussi
D’autres notes du journal
WorkManager en 2026 : travail unique, chaînage et test des tâches d'arrière-plan sur Android
Un guide pratique de WorkManager sur Android — politiques de travail unique, chaînage, travail accéléré, et comment tester réellement une tâche d'arrière-plan avant sa mise en production.
L'API In-App Review d'Android : demander une note sans être pénible
Un guide pratique de l'API In-App Review de Google — comment elle fonctionne réellement, quand la déclencher, et pourquoi la popup classique « notez-nous » nuit discrètement à votre note sur le Play Store.
Raccourcis d'application et tuiles de Paramètres rapides sur Android : enregistrer de l'eau sans ouvrir l'application
Un guide pratique de l'API dynamique ShortcutManager et de TileService sur Android — comment permettre à une action en un tap de contourner complètement l'application, avec les pièges qui font trébucher la plupart des implémentations.