Comment gérer le deep linking et les schémas personnalisés dans une WebView Android ? La gestion des liens profonds (deep links) dans une WebView Android nécessite d'étendre WebViewClient, de surcharger shouldOverrideUrlLoading pour vérifier isForMainFrame, de valider les schémas personnalisés via une liste autorisée, et de lancer en toute sécurité les intentions externes autorisées afin d'empêcher les schémas non-web de rester dans le pipeline de chargement de la WebView.
L'interception des deep links dans une WebView Android est une pratique de programmation consistant à remplacer les rappels de navigation au sein d'un
WebViewClientpersonnalisé pour capturer les protocoles non-HTTP, tels que les schémas d'URI personnalisés et les URI Chrome Intent. En évaluant les métadonnées de la requête et en validant les charges utiles de destination, l'application hôte exécute les lancements d'intentions natives tout en évitant les plantages liés aux protocoles non gérés.
| Terme | Définition | Entité associée | Rôle de l'intention de recherche |
|---|---|---|---|
| WebView | Composant d'affichage basé sur Chromium qui rend le contenu web au sein des applications Android. | Android SDK | Informationnel / Commercial |
| Schéma d'URL personnalisé | Protocole d'URI propriétaire permettant aux liens web de cibler des applications natives. | Routage de deep link | Technique / Informationnel |
| Web to App | Processus de transition des utilisateurs depuis des pages web vers des interfaces d'applications natives. | Deep Linking Mobile | Informationnel |

Pourquoi les WebView Android génèrent des erreurs net ERR UNKNOWN URL SCHEME
Limites du protocole Chromium : Pourquoi les WebView ne reconnaissent nativement que les schémas web standards
Le composant WebView d'Android repose sur un moteur de rendu Chromium configuré principalement pour analyser et afficher les protocoles de ressources web standards : http://, https://, file://, data://, about: et javascript:.
Par défaut, lorsqu'aucun WebViewClient personnalisé n'est assigné, le framework Android délègue les URL non gérées au gestionnaire d'activités du système. Cependant, dès qu'une application fournit un WebViewClient personnalisé, les schémas non-HTTP(S) (tels que myapp://product/detail/1024, alipay:// ou whatsapp://) sont routés directement vers les rappels de navigation du client. Si l'application hôte laisse un schéma personnalisé dans le pipeline de chargement de la WebView au lieu de l'intercepter, le moteur Chromium tente de le résoudre comme une ressource web ordinaire. Comme les protocoles non standards ne peuvent être résolus via DNS ou des couches de transport web, le moteur renvoie une erreur de navigation de bas niveau.
Anatomie du plantage net::ERR_UNKNOWN_URL_SCHEME : Du clic utilisateur à l'échec de chargement
La transition d'une interaction utilisateur à une page d'erreur suit une séquence déterministe et rapide :
- Action utilisateur : L'utilisateur appuie sur un bouton d'appel à l'action ou un lien hypertexte dans une page de destination H5 intégrée.
- Requête de navigation : Le moteur web construit une requête de navigation contenant la chaîne d'URI cible (ex :
myapp://promo/summer). - Évaluation du pipeline : Si
shouldOverrideUrlLoading()retournefalseou ne parvient pas à intercepter l'URI non-HTTP(S), le moteur Chromium traite l'URL comme un chargement de ressource web standard. - Envoi de l'erreur : Ne trouvant aucun gestionnaire interne pour ce protocole, le moteur déclenche
onReceivedError()et remplace le contenu de la WebView par le modèle d'erreur Chromium par défaut :net::ERR_UNKNOWN_URL_SCHEME.
Cette page d'erreur interrompt le parcours utilisateur, empêche le suivi de conversion et dégrade la stabilité perçue de l'application hôte.
Le risque de plantages ActivityNotFoundException en l'absence de l'application cible
Lorsque les développeurs tentent de résoudre net::ERR_UNKNOWN_URL_SCHEME, une erreur courante consiste à analyser la chaîne d'URI et à appeler context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) sans gestion d'exception adéquate.
Si l'utilisateur n'a pas l'application cible installée sur son appareil, le système d'exploitation Android ne peut trouver aucune Activity éligible pour résoudre l'Intent. Dans une exécution Android standard, appeler startActivity() sans gestionnaire correspondant provoque une android.content.ActivityNotFoundException non gérée. Si cette exception n'est pas interceptée dans un bloc try-catch, le processus de l'application hôte se termine brutalement, entraînant un plantage fatal.
Comment shouldOverrideUrlLoading intercepte les protocoles de deep linking
Évolution de l'API : Comparaison des surcharges WebResourceRequest (API 24+) avec les rappels de chaîne hérités
Le rappel shouldOverrideUrlLoading de WebViewClient permet aux applications hôtes d'inspecter et d'intercepter les requêtes de navigation avant leur exécution par le moteur web. La signature et les capacités de cette méthode ont évolué selon les niveaux d'API Android :
- API 24+ (Android 7.0 et supérieur) : Remplace
shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean. L'interfaceWebResourceRequestfournit des métadonnées contextuelles riches, incluant l'URL cible (request.url), la méthode de requête HTTP (request.method), les en-têtes et les indicateurs de hiérarchie de trame (request.isForMainFrame,request.hasGesture()). - Surcharge héritée (Dépréciée à partir de l'API 24) : Remplace
shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean. Ce rappel hérité ne reçoit que la chaîne d'URL brute sans métadonnées concernant la trame requérante ou l'état de l'interaction utilisateur.
Conformément à la documentation de référence Android pour WebViewClient shouldOverrideUrlLoading, les applications modernes doivent implémenter la surcharge WebResourceRequest. Pour le support des anciennes versions d'Android, le rappel hérité doit être configuré de manière conservatrice pour éviter les failles de sécurité.
Criticité de la sécurité avec request.isForMainFrame : Bloquer le détournement d'iframe

Conformément au guide Android Developers sur WebResourceRequest, le rappel shouldOverrideUrlLoading peut être déclenché par des sous-trames, des iframes et des redirections de ressources en arrière-plan, ainsi que par les navigations de documents de premier niveau.
L'application forcée de request.isForMainFrame est essentielle pour la sécurité de l'application :
- Isolation des limites de trame : Vérifier
request.isForMainFramegarantit que les sous-trames et les iframes ne peuvent pas déclencher de lancements d'applications externes ou d'intentions de schémas personnalisés. Si une sous-trame tente une navigation, les chargements HTTP(S) standards doivent retournerfalsepour continuer le rendu dans la trame, tandis que les schémas non-HTTP(S) doivent retournertruepour supprimer l'exécution sans erreur. - Distinction des gestes utilisateur : Bien que
isForMainFrameconfirme que la requête s'applique au document primaire, elle ne prouve pas l'activation par l'utilisateur. Si votre architecture exige une vérification des gestes utilisateur, évaluezrequest.hasGesture()ou imposez des modales de confirmation au niveau de l'application, en notant que la documentation Android souligne certains flux utilisateur valides oùhasGesture()peut renvoyer faux.
Différencier la navigation web normale des transferts vers des deep links : Quand retourner false vs true
La valeur booléenne retournée par shouldOverrideUrlLoading contrôle le cycle de vie du rendu de la WebView :
- Retourner
false: Informe le framework Android que l'application hôte n'a pas géré l'URL. LaWebViewpoursuit la navigation web standard, chargeant l'URL dans son conteneur Chromium interne. Cette valeur est obligatoire pour les pages web standardhttp://ethttps://devant s'afficher dans le navigateur intégré. - Retourner
true: Informe le framework que l'application hôte a intercepté et géré l'URL. LaWebViewannule immédiatement son processus de chargement interne. Cette valeur est obligatoire lors du lancement d'uneIntentexterne, du routage vers une activité native ou de la suppression d'un schéma non autorisé.
Gestion des App Links vérifiés dans le conteneur intégré
Lorsqu'un lien dans la WebView pointe vers le domaine HTTPS vérifié de l'application hôte (ex : https://app.example.com/product/1024), l'exécution de la navigation par défaut conduit la WebView à charger la version web de la page produit.
Pour offrir une expérience native, le WebViewClient doit inspecter les URL HTTPS entrantes. Si l'hôte correspond au domaine associé de l'application, le client intercepte la requête, valide le chemin et les paramètres de requête via un validateur de route interne (AppLinkRouteValidator), et envoie les données directement au routeur natif (internalRouter(validatedRoute)), en retournant true pour annuler le rendu web redondant.
Structurer des gestionnaires d'intentions sécurisés pour les schémas personnalisés et les Intent URIs
Analyse de la syntaxe des URI Chrome Intent : Schémas, Packages et S.browser_fallback_url
Sur Android, les pages web utilisent fréquemment des URI Chrome Intent pour structurer des deep links natifs avec des mécanismes de repli (fallback) intégrés. Une URI Intent utilise le schéma intent:// et encode les paramètres de routage, les cibles de package et les URL de repli dans une chaîne sérialisée :
intent://product/detail/1024#Intent;scheme=myapp;package=com.example.app;S.browser_fallback_url=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dcom.example.app;end
Comme documenté dans le guide Chrome for Developers sur les URI Android Intent, l'URI se décompose en composants distincts :
- Hôte et chemin (
product/detail/1024) : Définit la destination interne de l'application. scheme=myapp: Spécifie le protocole personnalisé sous-jacent.package=com.example.app: Restreint la résolution de l'intention exclusivement au package Android spécifié.S.browser_fallback_url=...: Un paramètre optionnel String définissant une URL web ou de boutique explicite à charger si l'application cible n'est pas installée.
Le danger de l'exécution d'intentions analysées brutes : Atténuation des vulnérabilités par injection
Un anti-patron d'implémentation courant consiste à analyser une URI entrante avec Intent.parseUri(url, Intent.URI_INTENT_SCHEME) et à transmettre l'Intent résultante directement à context.startActivity(intent).
Conformément aux conseils du guide OWASP Mobile Application Security Testing concernant les deep links non sécurisés, l'exécution d'intentions analysées brutes expose l'application à des attaques par injection d'intention. Un attaquant contrôlant la page web pourrait créer une URI Intent contenant des cibles de composants explicites malveillantes (component=com.example.app/.InternalAdminActivity), des actions sensibles ou des extras privilégiés (S.is_admin=true). La WebView pourrait désérialiser ces paramètres et lancer des activités internes ou non exportées, contournant ainsi les barrières d'authentification.

Reconstruction sécurisée d'intentions : Suppression des composants et restriction des actions à ACTION_VIEW
Pour éliminer les vulnérabilités d'injection, les applications hôtes ne doivent jamais lancer directement une Intent désérialisée brute. Au lieu de cela, le WebViewClient doit analyser l'URI, extraire uniquement la charge utile de données vérifiée, et reconstruire une nouvelle Intent dédiée et propre :
- Extraction des données : Analyser l'URI et extraire le package cible vérifié et l'URI de données.
- Validation du package : Vérifier que le package déclaré correspond à une liste autorisée explicite de partenaires ou de packages internes.
- Reconstruction : Instancier une nouvelle
Intent(Intent.ACTION_VIEW, validatedDataUri). - Isolation : Appliquer
intent.addCategory(Intent.CATEGORY_BROWSABLE)pour garantir que seules les activités browsables peuvent résoudre la requête, définir des drapeaux explicites (FLAG_ACTIVITY_NEW_TASK), et s'assurer queintent.componentetintent.selectorrestent nuls.
Application de listes autorisées strictes pour les schémas, hôtes, chemins et paramètres
Les schémas personnalisés (myapp://) et les URI Intent reconstruites doivent passer par un validateur strict avant exécution.
Cette implémentation de référence gère spécifiquement le schéma myapp:// de l'application hôte, en ancrant explicitement l'exécution de l'intention à com.example.app. Pour les écosystèmes multi-partenaires, les développeurs doivent maintenir un mappage schéma-vers-package strict ou privilégier les App Links HTTPS vérifiés.
Le validateur vérifie :
- Schéma : Correspond aux schémas personnalisés autorisés (
myapp). - Hôte : Correspond aux catégories de routage prédéfinies (
open,product,event). - Préfixe de chemin : Conforme aux structures de route attendues (
/detail/,/view/,/main/). - Paramètres de requête : Applique des clés autorisées (
target,id,promo,utm_source) et valide que les valeurs respectent les limites de longueur (ex :caractères) et les jeux de caractères alphanumériques.
[L'utilisateur appuie sur un lien dans la WebView Android]
│
▼
[WebViewClient.shouldOverrideUrlLoading]
│
┌─────────────┴─────────────┐
▼ ▼
[!request.isForMainFrame] [request.isForMainFrame]
│ │
▼ ▼
[Vérifier schéma sous-trame] [Inspecter le schéma URL]
├─ HTTP(S) -> retourner false │
└─ Non-Web -> retourner true ┌────┴───────────────────────────┐
(Supprimer erreur) ▼ ▼
[HTTP / HTTPS] [Schéma non-HTTP(S)]
│ │
▼ ▼
[Vérifier domaine hôte] [CustomSchemeValidator]
├─ Lien App interne ├─ Valide -> Lancer intention ancrée
│ -> Routeur interne └─ Intention -> Reconstruire & Repli
└─ Web externe
-> Retourner false
Comment gérer la visibilité des packages Android 11 et les replis vers l'App Store

Le changement de visibilité des packages sous Android 11
À partir d'Android 11 (niveau API 30), Google a introduit le filtrage de la visibilité des packages pour renforcer la confidentialité des utilisateurs. Dans ce cadre, la vision d'une application sur les autres applications installées est limitée par défaut.
Il est crucial de comprendre la distinction entre le lancement d'intention et la requête d'intention :
- Lancement d'intention direct : Les appels standards à
context.startActivity(intent)ne requièrent pas de déclaration de visibilité de package. Si l'application cible est installée, le système d'exploitation la route et la lance avec succès, qu'elle soit visible ou non pour les requêtes de package. - Requêtes d'intention de vérification (Preflight) : Si votre architecture
WebViewClienteffectue une inspection préalable (comme appelerpackageManager.resolveActivity(intent, 0)oupackageManager.queryIntentActivities(intent, 0)pour vérifier la présence de l'application), la requête renvoienullou une liste vide à moins que le package ou le schéma cible ne soit déclaré dansAndroidManifest.xml.
Déclarer les éléments <queries> dans AndroidManifest.xml
Conformément au guide Android Developers sur la visibilité des packages sous Android 11, les applications effectuant des vérifications préalables doivent déclarer leurs packages cibles ou signatures d'intention dans le manifeste :
<manifest package="com.example.app">
<!-- Déclarations optionnelles pour la découverte explicite de packages -->
<queries>
<package android:name="com.example.partner" />
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="myapp" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" android:host="app.example.com" />
</intent>
</queries>
</manifest>
Si votre architecture préfère les modèles de lancement standard, exécuter startActivity() dans un bloc try-catch interceptant ActivityNotFoundException fournit une implémentation résiliente, indépendante des requêtes de visibilité.
Chaînes de repli : Routage vers la boutique en cas d'absence de l'application
Lorsqu'un schéma personnalisé ou une URI Intent intercepté ne peut être lancé parce que l'application cible n'est pas installée, le WebViewClient doit exécuter une chaîne de repli explicite :
- Extraire et lier l'URL de repli : Pour les URI
intent://, extraire le paramètreS.browser_fallback_urlet vérifier qu'il pointe vers une destination autorisée. Pour les URL Google Play, vérifier que le paramètreidcorrespond strictement au nom du package cible (id=targetPackage) pour prévenir les exploits de redirection ouverte. - Génération d'URI Market : Si aucune URL de repli n'est présente, construire une intention market :
market://details?id=com.example.app. - Repli vers le Web Play Store : Si Google Play est désactivé ou absent, intercepter
ActivityNotFoundExceptionsur l'intention market et chargerhttps://play.google.com/store/apps/details?id=com.example.app.
Lier les paramètres contextuels à travers les redirections avec OpoInstall
Lorsque les utilisateurs sans l'application sont redirigés vers Google Play, les liens de boutique statiques perdent les paramètres dynamiques. OpoInstall, plateforme d'attribution et de deep linking mobile, comble ce fossé en capturant les jetons de parrainage et le contexte de campagne sur l'infrastructure d'attribution avant la redirection. Vérifiez le comportement de récupération des paramètres différés et les API d'initialisation par rapport à la version du SDK Android OpoInstall déployée en production.
Après l'installation et le premier lancement, le SDK Android OpoInstall récupère les paramètres mis en cache, permettant à l'application de restaurer la scène cible de l'utilisateur. Consultez la documentation d'intégration du SDK pour les instructions sur la réception des paramètres différés après installation.
Implémentation d'un WebViewClient de production pour l'interception sécurisée
Structurer l'architecture du WebViewClient personnalisé en Kotlin
Un WebViewClient de production coordonne la vérification des trames, la liste blanche de schémas, la reconstruction d'intentions, l'extraction de replis et le routage interne. L'implémentation encapsule les règles de validation dans des objets validateurs dédiés (AppLinkRouteValidator et CustomSchemeValidator) pour maintenir les rappels de navigation propres et maintenables.
Implémentation des modèles AppLinkRouteValidator et CustomSchemeValidator
Les objets validateurs appliquent une sécurité stricte :
AppLinkRouteValidatorvalide les domaines HTTPS internes, les préfixes de chemin autorisés et les clés de paramètres autorisées (target,id,promo,utm_source), retournant une classe de donnéesValidatedAppRoute.CustomSchemeValidatorvérifie les schémas personnalisés autorisés (myapp), valide les hôtes autorisés (open,product,event), impose des préfixes de chemin et nettoie les valeurs des paramètres de requête par rapport aux jeux de caractères alphanumériques et contraintes de longueur (chars).
Le code ci-dessous démontre comment configurer les déclarations optionnelles AndroidManifest.xml et implémenter un CustomWebViewClient thread-safe en Kotlin avec une gestion automatisée des replis. Les binaires SDK certifiés et wrappers d'intégration peuvent être obtenus depuis le centre de téléchargement SDK OpoInstall.
<!-- AndroidManifest.xml: Déclarations de visibilité de package optionnelles pour les requêtes préalables (Android 11+) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<!-- Déclarer les packages externes et schémas uniquement si vous utilisez des requêtes preflight -->
<queries>
<!-- Autoriser la découverte preflight pour des partenaires spécifiques -->
<package android:name="com.example.partner" />
<!-- Autoriser la résolution preflight de schémas de deep linking personnalisés -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="myapp" />
</intent>
<!-- Autoriser la résolution preflight de domaines App Link HTTPS vérifiés -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" android:host="app.example.com" />
</intent>
</queries>
<application
android:allowBackup="true"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</application>
</manifest>
// Android: WebViewClient personnalisé avec sécurité des limites de trame et validation
// Exemple d'intégration ; vérifiez les signatures de méthodes avec votre architecture.
package com.example.app.webview
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
data class ValidatedAppRoute(val path: String, val queryParams: Map<String, String>)
object AppLinkRouteValidator {
private val allowedPrefixes = setOf("/open/", "/product/", "/promo/")
private val allowedQueryKeys = setOf("target", "id", "promo", "utm_source")
fun validate(uri: Uri): ValidatedAppRoute? {
val path = uri.path ?: ""
if (allowedPrefixes.none { path.startsWith(it) }) {
return null
}
val sanitizedParams = mutableMapOf<String, String>()
for (name in uri.queryParameterNames) {
if (name !in allowedQueryKeys) {
return null
}
val value = uri.getQueryParameter(name) ?: ""
if (value.length <= 64 && value.matches(Regex("^[A-Za-z0-9_-]+$"))) {
sanitizedParams[name] = value
} else {
return null
}
}
return ValidatedAppRoute(path, sanitizedParams)
}
}
object CustomSchemeValidator {
private val allowedSchemes = setOf("myapp")
private val allowedHosts = setOf("open", "product", "event")
private val allowedPathPrefixes = setOf("/detail/", "/view/", "/main/")
private val allowedQueryKeys = setOf("target", "id", "promo", "utm_source")
fun validate(uri: Uri): Uri? {
val scheme = uri.scheme?.lowercase() ?: return null
val host = uri.host?.lowercase() ?: return null
val path = uri.path ?: ""
if (!allowedSchemes.contains(scheme) || !allowedHosts.contains(host)) {
return null
}
if (allowedPathPrefixes.none { path.startsWith(it) }) {
return null
}
for (name in uri.queryParameterNames) {
if (name !in allowedQueryKeys) return null
val value = uri.getQueryParameter(name) ?: ""
if (value.length > 128 || !value.matches(Regex("^[A-Za-z0-9_-]+$"))) {
return null
}
}
return uri
}
}
class CustomWebViewClient(
private val context: Context,
private val internalRouter: (ValidatedAppRoute) -> Unit
) : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
request ?: return false
// Exigence de sécurité : Restreindre le routage externe aux navigations de trame principale
if (!request.isForMainFrame) {
val scheme = request.url.scheme?.lowercase()
// Autoriser le chargement HTTP(S) standard en sous-trame
return if (scheme == "http" || scheme == "https") {
false
} else {
Log.w("WebViewClient", "Schéma non-web supprimé dans la sous-trame: $scheme")
true
}
}
val url = request.url.toString()
return handleUrlNavigation(view, url)
}
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
if (url == null) return false
val uri = Uri.parse(url)
val scheme = uri.scheme?.lowercase()
return if (scheme == "http" || scheme == "https") {
false
} else {
Log.w("WebViewClient", "Schéma externe bloqué sur rappel hérité: $scheme")
true
}
}
private fun handleUrlNavigation(view: WebView?, url: String): Boolean {
val uri = Uri.parse(url)
val scheme = uri.scheme?.lowercase() ?: return false
if (scheme == "http" || scheme == "https") {
if (scheme == "https" && uri.host?.lowercase() == "app.example.com") {
val validatedRoute = AppLinkRouteValidator.validate(uri)
if (validatedRoute != null) {
Log.i("WebViewClient", "Route HTTPS interne validée.")
internalRouter(validatedRoute)
} else {
Log.w("WebViewClient", "App Link interne non autorisé: $uri")
}
return true
}
return false
}
val validatedUri = CustomSchemeValidator.validate(uri)
if (validatedUri != null) {
val pinnedIntent = Intent(Intent.ACTION_VIEW, validatedUri).apply {
`package` = "com.example.app"
addCategory(Intent.CATEGORY_BROWSABLE)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
return launchSafeIntent(pinnedIntent, fallbackAction = {
handleStoreFallback(view, "com.example.app")
})
}
if (scheme == "intent") {
return try {
val parsed = Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
val targetPackage = parsed.`package`
val allowedPackages = setOf("com.example.app", "com.example.partner")
val fallbackUrl = parsed.getStringExtra("browser_fallback_url")
if (targetPackage != null && allowedPackages.contains(targetPackage)) {
val rawDataUri = parsed.data
if (rawDataUri != null) {
val validatedDataUri = CustomSchemeValidator.validate(rawDataUri)
if (validatedDataUri != null) {
val safeIntent = Intent(Intent.ACTION_VIEW, validatedDataUri).apply {
`package` = targetPackage
addCategory(Intent.CATEGORY_BROWSABLE)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
launchSafeIntent(safeIntent, fallbackAction = {
handleIntentFallback(view, fallbackUrl, targetPackage)
})
} else {
Log.w("WebViewClient", "Intent avec URI invalide rejeté: $rawDataUri")
true
}
} else {
Log.w("WebViewClient", "Intent sans charge utile rejeté")
true
}
} else {
Log.w("WebViewClient", "Intent ciblant un package non vérifié: $targetPackage")
true
}
} catch (e: Exception) {
Log.e("WebViewClient", "Analyse d'intention échouée: $url", e)
true
}
}
Log.w("WebViewClient", "Navigation de schéma non vérifié bloquée: $scheme")
return true
}
private fun launchSafeIntent(intent: Intent, fallbackAction: () -> Unit): Boolean {
return try {
context.startActivity(intent)
true
} catch (e: ActivityNotFoundException) {
Log.w("WebViewClient", "Application non installée. Repli activé.")
fallbackAction()
true
} catch (e: SecurityException) {
Log.e("WebViewClient", "Exception de sécurité lors du lancement: ${intent.data}", e)
true
}
}
private fun handleIntentFallback(view: WebView?, fallbackUrl: String?, targetPackage: String) {
if (!fallbackUrl.isNullOrBlank() && isAuthorizedFallbackUrl(fallbackUrl, targetPackage)) {
view?.loadUrl(fallbackUrl)
} else {
handleStoreFallback(view, targetPackage)
}
}
private fun handleStoreFallback(view: WebView?, packageName: String) {
val marketUri = Uri.parse("market://details?id=$packageName")
val marketIntent = Intent(Intent.ACTION_VIEW, marketUri).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
try {
context.startActivity(marketIntent)
} catch (e: ActivityNotFoundException) {
val webStoreUrl = "https://play.google.com/store/apps/details?id=$packageName"
view?.loadUrl(webStoreUrl)
}
}
private fun isAuthorizedFallbackUrl(url: String, targetPackage: String): Boolean {
val uri = Uri.parse(url)
val scheme = uri.scheme?.lowercase()
val host = uri.host?.lowercase()
if (scheme != "https" || host == null) return false
return when (host) {
"play.google.com" -> {
uri.path == "/store/apps/details" && uri.getQueryParameter("id") == targetPackage
}
"app.example.com", "www.example.com" -> {
AppLinkRouteValidator.validate(uri) != null
}
else -> false
}
}
}
Exécution thread-safe : Routage interne et cycle de vie
Lorsque AppLinkRouteValidator confirme une route interne, les transitions d'UI doivent s'exécuter sur le thread UI principal d'Android. Si internalRouter déclenche des fragments ou des transitions d'activité, encapsulez l'appel dans runOnUiThread pour éviter les plantages liés aux threads.
Matrice de diagnostic et solutions aux erreurs de redirection WebView Android
Guide de dépannage complet du deep linking dans les WebView
| Symptôme / Erreur | Cause première | Niveaux API | Point de contrôle | Remédiation |
|---|---|---|---|---|
net::ERR_UNKNOWN_URL_SCHEME |
Schéma non-web non géré | Tous | shouldOverrideUrlLoading manquant |
Intercepter le schéma et retourner true |
ActivityNotFoundException |
App absente sur l'appareil | Tous | startActivity() non géré |
Utiliser try-catch et gérer le repli |
| Détournement d'intent iframe | Sous-trame lançant une intention | API 24+ | isForMainFrame non vérifié |
Bloquer les schémas non-web en iframe |
| Requête Preflight renvoie Null | Visibilité package Android 11+ | API 30+ | resolveActivity sans déclaration |
Ajouter <queries> dans le manifeste |
| Boucle de rechargement App Link | App Link routé vers WebView | Tous | Domaine non intercepté | Valider et router en interne |
Questions fréquemment posées (FAQ)
Pourquoi ma WebView Android plante-t-elle lors de l'ouverture de schémas d'URL personnalisés ?
Comment gérer les URL intent:// en toute sécurité dans une WebView ?
Comment la visibilité des packages sous Android 11 affecte-t-elle l'interception des deep links ?
Résumé et cadre de décision
La gestion du deep linking dans les WebView Android nécessite de créer un pont entre les environnements de rendu web et le routage natif. S'en remettre au comportement par défaut de la WebView conduit inévitablement à des erreurs de protocole, une expérience utilisateur médiocre et des failles de sécurité.
En implémentant un WebViewClient robuste qui vérifie les limites de trame, valide les schémas, reconstruit des intentions ACTION_VIEW sécurisées et gère les replis vers les boutiques, les équipes d'ingénierie réduisent les échecs de routage. Intégrer un routage côté client fiable avec des frameworks d'attribution mobile améliore la cohérence des parcours utilisateur.
Pour explorer les architectures de deep linking natif Android, consultez la documentation d'intégration SDK, téléchargez les bibliothèques depuis le centre de téléchargement SDK OpoInstall, ou enregistrez votre application sur la console développeur OpoInstall.
Matériels connexes
- Concepts : Navigation WebView Android, Interception de schémas personnalisés, Reconstruction d'URI Intent, Visibilité des packages
- Technologies : Android WebViewClient, Moteur Chromium, Android Debug Bridge, SDK Android OpoInstall
- Standards : IETF RFC 3986 (URI), Spécification Android Intent, Guide OWASP MASTG
- API :
shouldOverrideUrlLoading,WebResourceRequest.isForMainFrame,Intent.parseUri - Références et documentation :
Share this article



