Wie handhabt man Deep Linking und benutzerdefinierte Schemata in Android WebViews? Die Handhabung von Deep Links in einem Android-WebView erfordert die Erweiterung von WebViewClient, das Überschreiben von shouldOverrideUrlLoading zur Prüfung von isForMainFrame, die Validierung benutzerdefinierter Schemata anhand einer Positivliste sowie das sichere Starten autorisierter externer Intents, um zu verhindern, dass Nicht-Web-Schemata im WebView-Ladeprozess verbleiben.
Die Interzeption von Android WebView-Deep-Links ist die programmatische Praxis, Navigations-Callbacks innerhalb eines benutzerdefinierten WebViewClient zu überschreiben, um Nicht-HTTP-Protokolle wie benutzerdefinierte URI-Schemata und Chrome Intent-URIs abzufangen. Durch die Bewertung von Anforderungs-Metadaten und die Validierung von Ziel-Payloads führt die Host-Anwendung native Intent-Aufrufe aus und verhindert gleichzeitig Abstürze durch nicht behandelte Protokolle.
| Begriff | Definition | Zugehörige Entität | Suchintention |
|---|---|---|---|
| WebView | Eine eingebettete, Chromium-basierte View-Komponente, die Webinhalte in Android-Apps rendert. | Android SDK | Informationell / Kommerziell |
| Benutzerdefiniertes URL-Schema | Ein proprietäres URI-Protokoll, das es Weblinks ermöglicht, native Anwendungen anzusteuern. | Deep-Link-Routing | Technisch / Informationell |
| Web-to-App | Der Prozess des Übergangs von Nutzern von Webseiten zu nativen Anwendungsoberflächen. | Mobile Deep Linking | Informationell |

Warum Android WebViews den Fehler net ERR UNKNOWN URL SCHEME auslösen
Chromium-Protokolleinschränkungen: Warum WebViews nativ nur Standard-Web-Schemata erkennen
Die Android WebView-Komponente basiert auf einer Chromium-Rendering-Engine, die hauptsächlich für die Analyse und Anzeige von Standard-Webprotokollen konfiguriert ist: http://, https://, file://, data://, about: und javascript:.
Standardmäßig delegiert das Android-Framework nicht behandelte URLs an den Activity Manager des Systems, sofern kein benutzerdefinierter WebViewClient zugewiesen ist. Sobald eine Anwendung jedoch einen benutzerdefinierten WebViewClient bereitstellt, werden Nicht-HTTP(S)-Schemata (wie myapp://product/detail/1024, alipay:// oder whatsapp://) direkt in die Navigations-Callbacks des Clients geleitet. Wenn die Host-Anwendung zulässt, dass ein benutzerdefiniertes Schema innerhalb der WebView-Ladepipeline verbleibt, anstatt es abzufangen, versucht die Chromium-Engine, das Schema als gewöhnliche Web-Ressource aufzulösen. Da nicht standardmäßige Protokolle nicht über DNS oder Web-Server-Transportschichten aufgelöst werden können, meldet die Engine einen Fehler auf niedriger Ebene.
Die Anatomie des Absturzes von net::ERR_UNKNOWN_URL_SCHEME: Vom Nutzertipp zum Ladefehler der Web-Ressource
Der Übergang von einer Benutzerinteraktion zu einer Fehlerseite erfolgt in einer schnellen, deterministischen Abfolge:
- Benutzeraktion: Ein Benutzer tippt auf eine Call-to-Action-Schaltfläche oder einen Hyperlink innerhalb einer eingebetteten H5-Landingpage.
- Navigationsanforderung: Die Web-Engine erstellt eine Navigationsanforderung, die den Ziel-URI-String enthält (z. B.
myapp://promo/summer). - Pipeline-Auswertung: Wenn
shouldOverrideUrlLoading()den Wertfalsezurückgibt oder die Nicht-HTTP(S)-URI nicht abfängt, behandelt die Chromium-Engine die URL als Standard-Web-Ressourcen-Ladevorgang. - Fehlerdispatch: Da kein interner Handler für das Protokoll gefunden wird, löst die Engine
onReceivedError()aus und ersetzt den Inhalt der WebView durch die standardmäßige Chromium-Fehlervorlage:net::ERR_UNKNOWN_URL_SCHEME.
Diese Fehlerseite unterbricht den User Journey, verhindert das Conversion-Tracking und mindert die wahrgenommene Stabilität der Host-Anwendung.
Das Risiko von unbehandelten ActivityNotFoundException-Abstürzen bei fehlender Zielanwendung
Wenn Entwickler versuchen, net::ERR_UNKNOWN_URL_SCHEME zu beheben, besteht ein häufiger Fehler darin, den URI-String zu analysieren und context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) ohne angemessene Fehlerbehandlung aufzurufen.
Wenn der Benutzer die Zielanwendung nicht auf seinem Gerät installiert hat, kann das Android-Betriebssystem keine geeignete Activity finden, um den Intent aufzulösen. In der Standard-Android-Ausführung führt der Aufruf von startActivity() ohne passenden Handler zu einer unbehandelten android.content.ActivityNotFoundException. Wenn diese Ausnahme nicht in einem strukturierten try-catch-Block abgefangen wird, wird der gesamte Prozess der Host-Anwendung abrupt beendet, was zu einem fatalen Absturz führt.
Wie fängt shouldOverrideUrlLoading Deep-Link-Protokolle ab?
API-Entwicklung: Vergleich der Overloads von WebResourceRequest ab API 24+ mit Legacy-String-Callbacks
Der WebViewClient von Android stellt den Callback shouldOverrideUrlLoading bereit, damit Host-Anwendungen Navigationsanforderungen prüfen und abfangen können, bevor die Web-Engine sie ausführt. Die Signatur und die Funktionen dieser Methode haben sich über Android-API-Level hinweg weiterentwickelt:
- API 24+ (Android 7.0 und höher): Überschreibt
shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean. DieWebResourceRequest-Schnittstelle liefert umfassende Kontext-Metadaten, einschließlich der Ziel-URL (request.url), der HTTP-Anforderungsmethode (request.method), Anforderungs-Header und kritische Frame-Hierarchie-Flags (request.isForMainFrame,request.hasGesture()). - Legacy-Overload (veraltet ab API 24): Überschreibt
shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean. Dieser Legacy-Callback empfängt nur den rohen URL-String ohne Metadaten bezüglich des anfordernden Frames oder des Benutzerinteraktionsstatus.
Gemäß der Android-Entwicklerreferenz zu WebViewClient shouldOverrideUrlLoading müssen moderne Anwendungen den WebResourceRequest-Overload implementieren. Bei der Unterstützung älterer Android-Versionen sollte der veraltete String-Callback konservativ konfiguriert werden, um Sicherheitslücken zu vermeiden.
Die sicherheitskritische Bedeutung von request.isForMainFrame: Blockieren von Subframe- und Iframe-Hijacking

Gemäß dem Android-Entwicklerhandbuch zu WebResourceRequest kann der Callback shouldOverrideUrlLoading für Subframes, Iframes und Hintergrund-Ressourcen-Redirects sowie für Navigationen auf der obersten Dokumentenebene ausgelöst werden.
Die Durchsetzung von request.isForMainFrame ist für die Anwendungssicherheit unerlässlich:
- Frame-Boundary-Isolierung: Die Prüfung von
request.isForMainFramestellt sicher, dass Subframes und Iframes keine externen App-Starts oder benutzerdefinierten Schema-Intents auslösen können. Wenn ein Subframe eine Navigation versucht, sollten Standard-HTTP(S)-Ladevorgängefalsezurückgeben, um das In-Frame-Rendering fortzusetzen, während Nicht-HTTP(S)-Schematatruezurückgeben müssen, um die Ausführung zu unterdrücken, ohne Fehler zu verursachen. - Unterscheidung nach Benutzergesten: Während
isForMainFramebestätigt, dass sich die Anforderung auf das primäre Dokument bezieht, beweist dies keine Benutzeraktivierung. Wenn Ihre Architektur eine Verifizierung der Benutzergeste erfordert, werten Sierequest.hasGesture()aus oder erzwingen Sie Bestätigungsdialoge auf Anwendungsebene, wobei zu beachten ist, dass die Android-Dokumentation bestimmte gültige Benutzerflüsse hervorhebt, bei denenhasGesture()den Wert false zurückgeben kann.
Unterscheidung zwischen normaler Web-Navigation und Deep-Link-Handoffs: Wann false vs. true zurückgeben?
Der boolesche Rückgabewert von shouldOverrideUrlLoading steuert den Rendering-Lebenszyklus der WebView:
- Rückgabe
false: Informiert das Android-Framework darüber, dass die Host-Anwendung die URL nicht behandelt hat. DieWebViewsetzt die Standard-Web-Navigation fort und lädt die URL in ihrem internen Chromium-Container. Dieser Rückgabewert ist für Standard-http://- undhttps://-Webseiten zwingend erforderlich, die im eingebetteten Browser angezeigt werden sollen. - Rückgabe
true: Informiert das Framework darüber, dass die Host-Anwendung die URL abgefangen und behandelt hat. DieWebViewbricht sofort ihren internen Ladevorgang ab. Dieser Rückgabewert ist zwingend erforderlich, wenn ein externerIntentgestartet, zu einer nativen Activity geroutet oder ein nicht autorisiertes Schema unterdrückt wird.
Handhabung von App-verifizierten App Links innerhalb des eingebetteten Containers
Wenn ein Link innerhalb der WebView auf die eigene verifizierte HTTPS-Domain der Host-Anwendung verweist (z. B. https://app.example.com/product/1024), führt die Standardnavigation dazu, dass die WebView die Webversion der Produktseite lädt.
Um eine native Erfahrung zu bieten, sollte der WebViewClient eingehende HTTPS-URLs prüfen. Wenn der Host mit der zugehörigen Domain der Anwendung übereinstimmt, fängt der Client die Anforderung ab, validiert die Pfad- und Abfrageparameter anhand eines internen Routen-Validators (AppLinkRouteValidator) und sendet die Daten direkt an den nativen Router (internalRouter(validatedRoute)), wobei true zurückgegeben wird, um redundantes Web-Rendering abzubrechen.
Strukturierung sicherer Intent-Handler für benutzerdefinierte Schemata und Intent-URIs
Analyse der Chrome Intent-URI-Syntax: Schemata, Pakete und S.browser_fallback_url
Auf Android verwenden Webseiten häufig Chrome Intent-URIs, um native Deep Links mit integrierten Browser-Fallback-Mechanismen zu strukturieren. Eine Intent-URI verwendet das intent://-Schema und kodiert Routing-Parameter, Paket-Ziele und Fallback-URLs in einen serialisierten String:
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
Wie im Chrome für Entwickler-Handbuch zu Android Intent-URIs dokumentiert, gliedert sich die URI in verschiedene Komponenten:
- Host und Pfad (
product/detail/1024): Definiert das interne Anwendungsziel. scheme=myapp: Spezifiziert das zugrunde liegende benutzerdefinierte Protokoll.package=com.example.app: Beschränkt die Intent-Auflösung exklusiv auf das spezifizierte Android-Paket.S.browser_fallback_url=...: Ein optionales String-Extra, das eine explizite Web- oder Store-URL definiert, die geladen werden soll, falls die Zielanwendung nicht installiert ist.
Die Gefahr beim Ausführen von rohen geparsten Intents: Eindämmung von Intent-Injection-Schwachstellen
Ein häufiges Anti-Pattern bei der Implementierung ist das Parsen einer eingehenden URI mit Intent.parseUri(url, Intent.URI_INTENT_SCHEME) und das direkte Übergeben des resultierenden Intent an context.startActivity(intent).
Gemäß der OWASP Mobile Application Security Testing Guide-Anleitung zu unsicheren Deep Links setzt die Ausführung von rohen geparsten Intents die Anwendung Intent-Injection-Angriffen aus. Ein Angreifer, der die Webseite kontrolliert, könnte eine Intent-URI mit bösartigen expliziten Komponentenzielen (component=com.example.app/.InternalAdminActivity), sensiblen Aktionen oder privilegierten Intent-Extras (S.is_admin=true) erstellen. Die WebView würde diese Parameter deserialisieren und nicht exportierte oder interne Aktivitäten starten, wodurch Authentifizierungsbarrieren umgangen werden.

Sichere Intent-Rekonstruktion: Entfernen von Komponenten und Einschränkung von Aktionen auf ACTION_VIEW
Um Intent-Injection-Schwachstellen zu eliminieren, dürfen Host-Anwendungen niemals einen rohen deserialisierten Intent direkt starten. Stattdessen muss der WebViewClient den URI parsen, nur die verifizierte Daten-Payload extrahieren und einen sauberen, dedizierten Intent rekonstruieren:
- Daten extrahieren: Parsen Sie den URI und extrahieren Sie das verifizierte Zielpaket und den Daten-URI.
- Paket validieren: Verifizieren Sie, dass das deklarierte Paket mit einer expliziten Positivliste autorisierter Partner- oder interner Pakete übereinstimmt.
- Rekonstruieren: Instanziieren Sie einen neuen
Intent(Intent.ACTION_VIEW, validatedDataUri). - Isolieren: Wenden Sie
intent.addCategory(Intent.CATEGORY_BROWSABLE)an, um sicherzustellen, dass nur browsable Aktivitäten die Anforderung auflösen können, setzen Sie explizite Flags (FLAG_ACTIVITY_NEW_TASK) und stellen Sie sicher, dassintent.componentundintent.selectornull bleiben.
Durchsetzung strenger Positivlisten für Schemata, Hosts, Pfade und Abfrageparameter
Sowohl benutzerdefinierte Schemata (myapp://) als auch rekonstruierte Intent-URIs müssen vor der Ausführung eine strenge Validierung durchlaufen.
Diese Referenzimplementierung handhabt speziell das myapp://-Schema der Host-Anwendung und fixiert die Intent-Ausführung explizit auf com.example.app. Für Partner-Ökosysteme mit mehreren Apps sollten Entwickler eine strikte Schema-zu-Paket-Zuordnung beibehalten oder verifizierte HTTPS-App-Links priorisieren.
Der Validator prüft:
- Schema: Stimmt mit autorisierten benutzerdefinierten Schemata (
myapp) überein. - Host: Stimmt mit vordefinierten Routing-Kategorien (
open,product,event) überein. - Pfad-Präfix: Entspricht den erwarteten Routenstrukturen (
/detail/,/view/,/main/). - Abfrageparameter: Erzwingt Abfrage-Schlüssel auf der Positivliste (
target,id,promo,utm_source) und validiert, dass Parameterwerte Längenbeschränkungen (z. B.Zeichen) und alphanumerische Zeichensätze einhalten.
[Benutzer tippt auf Link innerhalb der Android WebView]
│
▼
[WebViewClient.shouldOverrideUrlLoading]
│
┌─────────────┴─────────────┐
▼ ▼
[!request.isForMainFrame] [request.isForMainFrame]
│ │
▼ ▼
[Subframe-Schema prüfen] [URL-Schema inspizieren]
├─ HTTP(S) -> return false │
└─ Non-Web -> return true ┌────┴───────────────────────────┐
(Fehler unterdrücken) ▼ ▼
[HTTP / HTTPS] [Non-HTTP(S) Schema]
│ │
▼ ▼
[Host-Domain prüfen] [CustomSchemeValidator]
├─ Interner App Link ├─ Gültig -> Pinned Intent starten
│ -> Interner Router └─ Intent -> Rekonstruieren & Fallback
└─ Externes Web
-> Return false
Wie geht man mit der Paket-Sichtbarkeit unter Android 11 und App Store-Fallbacks um?

Die Verschiebung der Paket-Sichtbarkeit unter Android 11: Warum Preflight-Abfragen eine Deklaration erfordern
Ab Android 11 (API-Level 30) hat Google die Filterung der Paket-Sichtbarkeit eingeführt, um die Privatsphäre der Benutzer zu verbessern. Unter diesem Framework ist die Sicht einer Anwendung auf andere installierte Anwendungen standardmäßig eingeschränkt.
Es ist entscheidend, den Unterschied zwischen Intent-Start und Intent-Abfrage zu verstehen:
- Direkter Intent-Start: Standardaufrufe von
context.startActivity(intent)erfordern keine Deklarationen zur Paket-Sichtbarkeit. Wenn die Zielanwendung installiert ist, leitet das Betriebssystem die Anfrage weiter und startet sie erfolgreich, unabhängig davon, ob sie für Paketabfragen sichtbar ist. - Preflight-Intent-Abfragen: Wenn Ihre
WebViewClient-Architektur eine Preflight-Inspektion durchführt – wie den Aufruf vonpackageManager.resolveActivity(intent, 0)oderpackageManager.queryIntentActivities(intent, 0), um die Anwesenheit der Anwendung vor dem Start zu prüfen – gibt die Abfragenulloder eine leere Liste zurück, sofern das Zielpaket oder das Schema nicht imAndroidManifest.xmldeklariert ist.
Deklaration von <queries>-Elementen im AndroidManifest.xml für benutzerdefinierte Schemata und Zielpakete
Gemäß dem Android-Entwicklerhandbuch zur Paket-Sichtbarkeit in Android 11 sollten Anwendungen, die Preflight-Prüfungen vor der Intent-Ausführung durchführen, ihre Zielpakete oder Intent-Signaturen im Manifest deklarieren:
<manifest package="com.example.app">
<!-- Optionale Preflight-Abfrage-Deklarationen für explizite Paket-Entdeckung -->
<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>
Wenn Ihre Architektur Standard-Startmuster bevorzugt, bietet das Ausführen von startActivity() innerhalb eines try-catch-Blocks, der die ActivityNotFoundException abfängt, eine belastbare, abfrageunabhängige Implementierung.
Implementierung von Fallback-Ketten: Anmutiges Store-Routing bei Abwesenheit der Zielanwendung
Wenn ein abgefangenes benutzerdefiniertes Schema oder eine Intent-URI nicht gestartet werden kann, weil die Zielanwendung nicht auf dem Gerät des Benutzers installiert ist, muss der WebViewClient eine explizite Fallback-Kette ausführen:
- Fallback-URL extrahieren und binden: Extrahieren Sie für
intent://-URIs den String-ParameterS.browser_fallback_urlund validieren Sie, dass er auf ein autorisiertes Ziel verweist. Verifizieren Sie bei Google Play-URLs, dass derid-Parameter strikt mit dem Zielpaketnamen (id=targetPackage) übereinstimmt, um Open-Redirect-Exploits zu verhindern. - Market-URI-Generierung: Falls keine Fallback-URL vorhanden ist, konstruieren Sie einen Market-Intent:
market://details?id=com.example.app. - Web Play Store-Fallback: Wenn Google Play deaktiviert oder nicht vorhanden ist, fangen Sie die
ActivityNotFoundExceptionbeim Market-Intent ab und laden Siehttps://play.google.com/store/apps/details?id=com.example.app.
Binden kontextueller Parameter über Fallback-Umleitungen mit OpoInstall
Wenn deinstallierte Benutzer an Google Play weitergeleitet werden, verwerfen statische Store-Links dynamische Parameter. OpoInstall, eine Plattform für Mobile Attribution und Deep Linking, überbrückt diese Lücke, indem Referral-Token und Kampagnenkontext auf dem Attribution-Backend vor der Store-Weiterleitung erfasst werden. Überprüfen Sie das Verhalten beim Abrufen verzögerter Parameter und die Initialisierungs-APIs anhand der OpoInstall Android SDK-Version, die in der Produktion bereitgestellt wird.
Nach der anschließenden Installation und dem ersten App-Start ruft das OpoInstall Android SDK die zwischengespeicherten Parameter ab, wodurch die Host-App die Zielszene des Benutzers wiederherstellen kann. Überprüfen Sie die SDK-Integrationsdokumentation auf Anweisungen zum Empfang verzögerter Parameter nach der Installation.
Produktions-Implementierung des WebViewClient für sichere Schema-Interzeption
Strukturierung der benutzerdefinierten WebViewClient-Architektur in Kotlin
Ein produktiver WebViewClient koordiniert die Frame-Verifizierung, die Zulassungslisten-Validierung, die Intent-Rekonstruktion, die Fallback-Extraktion und das interne Routing. Die Implementierung kapselt Validierungsregeln innerhalb dedizierter Validator-Objekte (AppLinkRouteValidator und CustomSchemeValidator), um Navigations-Callbacks sauber und wartbar zu halten.
Implementierung der AppLinkRouteValidator- und CustomSchemeValidator-Modelle
Die Validator-Objekte erzwingen eine strikte Sicherheits-Standardeinstellung („Fail-Closed“):
AppLinkRouteValidatorvalidiert interne HTTPS-Domains, zulässige Pfad-Präfixe und autorisierte Abfrage-Schlüssel (target,id,promo,utm_source) und gibt eine strukturierteValidatedAppRoute-Datenklasse zurück.CustomSchemeValidatorprüft autorisierte benutzerdefinierte Schemata (myapp), validiert zulässige Hosts (open,product,event), erzwingt Pfad-Präfixe und bereinigt Werte von Abfrageparametern anhand alphanumerischer Zeichensätze und Längenbeschränkungen (Zeichen).
Die unten stehende Code-Implementierung zeigt, wie optionale Paket-Sichtbarkeitsdeklarationen im AndroidManifest.xml konfiguriert werden und ein threadsicherer, sicherer CustomWebViewClient in Kotlin mit automatisierter Fallback-Behandlung implementiert wird. Zertifizierte SDK-Binärdateien und Integrations-Wrapper können über das OpoInstall SDK-Download-Center bezogen werden.
<!-- AndroidManifest.xml: Optionale Paket-Sichtbarkeitsdeklarationen für Preflight-Intent-Abfragen (Android 11+) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<!-- Externe Pakete und benutzerdefinierte Schemata nur deklarieren, wenn resolveActivity-Preflight-Abfragen verwendet werden -->
<queries>
<!-- Preflight-Entdeckung für spezifische Partner- oder Zielpakete autorisieren -->
<package android:name="com.example.partner" />
<!-- Preflight-Auflösung benutzerdefinierter Deep-Linking-Schemata autorisieren -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="myapp" />
</intent>
<!-- Preflight-Auflösung verifizierter HTTPS-App-Link-Domains autorisieren -->
<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">
<!-- Anwendungskomponenten -->
</application>
</manifest>
// Android: Benutzerdefinierter WebViewClient mit Frame-Boundary-Sicherheit, Positivlisten-Validierung und Fallback-Routing
// Beispiel für Integrationsreferenz; verifizieren Sie Methodensignaturen gegen die bereitgestellte Android-Architektur.
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) {
// Strenge Sicherheit: Anfrage ablehnen, wenn unbekannte Abfrage-Schlüssel existieren
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
// Sicherheitsanforderung: Externes App-Routing nur für Main-Frame-Navigationen einschränken
if (!request.isForMainFrame) {
val scheme = request.url.scheme?.lowercase()
// Standard-HTTP(S)-Subframe-Laden erlauben, aber Nicht-Web-Schemata unterdrücken, um Iframe-Hijacking & Fehler zu verhindern
return if (scheme == "http" || scheme == "https") {
false
} else {
Log.w("WebViewClient", "Nicht-Web-Schema im Subframe/Iframe unterdrückt: $scheme")
true // Verbrauchen/abbrechen, ohne externen Intent zu starten oder Chromium-Fehler anzuzeigen
}
}
val url = request.url.toString()
return handleUrlNavigation(view, url)
}
@Deprecated("Veraltet 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()
// Legacy-Callback fehlt zuverlässige isForMainFrame-Metadaten; keine externen Schemata routen
return if (scheme == "http" || scheme == "https") {
false // Standard-Web-Inhalt laden erlauben
} else {
Log.w("WebViewClient", "Externes Schema bei Legacy-Callback blockiert: $scheme")
true // Unbehandelte Schema-Fehler unterdrücken, ohne externe Intents zu starten
}
}
private fun handleUrlNavigation(view: WebView?, url: String): Boolean {
val uri = Uri.parse(url)
val scheme = uri.scheme?.lowercase() ?: return false
// 1. HTTP / HTTPS und interne App-Links handhaben
if (scheme == "http" || scheme == "https") {
// Prüfen, ob URL mit der App-Link-Domain der Host-App übereinstimmt
if (scheme == "https" && uri.host?.lowercase() == "app.example.com") {
val validatedRoute = AppLinkRouteValidator.validate(uri)
if (validatedRoute != null) {
Log.i("WebViewClient", "Validierte interne HTTPS-Route. An internen Router senden.")
internalRouter(validatedRoute)
} else {
Log.w("WebViewClient", "Fehlerhafter oder nicht autorisierter interner App-Link abgelehnt: $uri")
}
return true // Intern behandelt; WebView-Ladevorgang abbrechen
}
return false // WebView erlauben, Standard-Web-Inhalt nativ zu laden
}
// 2. Benutzerdefinierte Schemata mit strenger Positivliste und Paket-Pinning validieren, um Hijacking zu verhindern
val validatedUri = CustomSchemeValidator.validate(uri)
if (validatedUri != null) {
val pinnedIntent = Intent(Intent.ACTION_VIEW, validatedUri).apply {
`package` = "com.example.app" // Host-App-Paket explizit fixieren
addCategory(Intent.CATEGORY_BROWSABLE)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
return launchSafeIntent(pinnedIntent, fallbackAction = {
handleStoreFallback(view, "com.example.app")
})
}
// 3. Chrome Intent-URIs mit sicherer Rekonstruktion und Fallback-Extraktion handhaben
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) {
// Rekonstruiere einen sauberen, dedizierten ACTION_VIEW-Intent anstatt das rohe geparste Objekt auszuführen
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 mit ungültigem Daten-URI abgelehnt: $rawDataUri")
true
}
} else {
Log.w("WebViewClient", "Intent fehlte Daten-URI-Payload")
true
}
} else {
Log.w("WebViewClient", "Intent abgelehnt, der nicht verifiziertes Paket ansteuert: $targetPackage")
true // Intent für nicht verifiziertes Paket abbrechen
}
} catch (e: Exception) {
Log.e("WebViewClient", "Intent-URI konnte nicht geparst werden: $url", e)
true // Unbehandelte Schema-Fehler unterdrücken
}
}
// Unbekannte Schemata ablehnen, um net::ERR_UNKNOWN_URL_SCHEME-Fehler zu vermeiden
Log.w("WebViewClient", "Navigation zu unverifiziertem Schema blockiert: $scheme")
return true
}
private fun launchSafeIntent(intent: Intent, fallbackAction: () -> Unit): Boolean {
return try {
context.startActivity(intent)
true
} catch (e: ActivityNotFoundException) {
Log.w("WebViewClient", "Zielanwendung nicht installiert: ${intent.data}. Fallback auslösen.")
fallbackAction()
true
} catch (e: SecurityException) {
Log.e("WebViewClient", "Security-Exception beim Starten des Intents: ${intent.data}", e)
true
}
}
private fun handleIntentFallback(view: WebView?, fallbackUrl: String?, targetPackage: String) {
if (!fallbackUrl.isNullOrBlank() && isAuthorizedFallbackUrl(fallbackUrl, targetPackage)) {
Log.i("WebViewClient", "Routing zu autorisierter Browser-Fallback-URL: $fallbackUrl")
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"
Log.i("WebViewClient", "Play Store-App fehlt. Lade Web-Store-URL: $webStoreUrl")
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" -> {
// Erzwingen, dass Google Play-Fallback-URL spezifisch auf das Zielpaket verweist
uri.path == "/store/apps/details" && uri.getQueryParameter("id") == targetPackage
}
"app.example.com", "www.example.com" -> {
// Eigene Domain-Fallback-Route validieren
AppLinkRouteValidator.validate(uri) != null
}
else -> false
}
}
}
Threadsichere Ausführung: Senden interner Routen und Verwalten von Lebenszyklus-Zuständen
Wenn AppLinkRouteValidator eine gültige interne Route bestätigt, müssen UI-Übergänge im Android-UI-Haupt-Thread ausgeführt werden. Wenn internalRouter Fragmente, Activity-Übergänge oder Compose-Navigation auslöst, kapseln Sie den Dispatch-Aufruf innerhalb von runOnUiThread oder einer Coroutine im Haupt-Thread-Lebenszyklus, um Threading-Abstürze zu verhindern.
Fehler bei Android WebView-Umleitungen und Diagnoselösungsmatrix
Umfassender Leitfaden zur Fehlerbehebung bei Android WebView-Deep-Linking
Die nachstehende Matrix beschreibt häufige Fehlersignaturen, die beim Umgang mit benutzerdefinierten Schemata in Android WebViews auftreten, zusammen mit primären Ursachen und technischen Abhilfemaßnahmen:
| Fehlersignatur / Symptom | Primäre Ursache | Betroffene API-Level | Diagnose-Prüfpunkt | Empfohlene Abhilfe |
|---|---|---|---|---|
net::ERR_UNKNOWN_URL_SCHEME |
Nicht-Web-Schema im WebView-Loader unbehandelt | Alle API-Level | shouldOverrideUrlLoading fehlt oder gibt false zurück |
Delegate überschreiben, Schema abfangen, true zurückgeben |
ActivityNotFoundException |
Ziel-App auf Gerät nicht vorhanden | Alle API-Level | Unbehandeltes context.startActivity() |
Ausnahme abfangen und S.browser_fallback_url oder Play Store-Route ausführen |
| Iframe-Intent-Hijacking | Subframe löst externen Intent aus | API 24+ | request.isForMainFrame ungeprüft |
Mit if (!request.isForMainFrame) schützen und Nicht-Web-Schemata unterdrücken |
| Preflight-Abfrage gibt Null zurück | Filter für Android 11+ Paket-Sichtbarkeit | API 30+ (Android 11+) | resolveActivity-Aufruf ohne Manifest-Deklaration |
<queries> im Manifest deklarieren oder startActivity try-catch direkt verwenden |
| Interner App-Link-Reload-Loop | Host-App-Link zu WebView umgeleitet | Alle API-Level | Host-Domain im Delegate unbehandelt | Eigene Domain abfangen, Route validieren, an nativen Router senden |
Häufig gestellte Fragen (FAQ)
Warum stürzt meine Android-WebView beim Öffnen benutzerdefinierter URL-Schemata ab?
Wie behandle ich `intent://`-URLs sicher in einer WebView ohne Sicherheitslücken?
Wie beeinflusst die Paket-Sichtbarkeit unter Android 11 die Deep-Link-Interzeption in WebViews?
Zusammenfassung und Entscheidungs-Framework
Die Handhabung von Deep Linking und benutzerdefinierten Schemata innerhalb von Android WebViews erfordert das Überbrücken der Lücke zwischen Web-Rendering-Sandboxes und nativem Betriebssystem-Intent-Routing. Das Verlassen auf das Standard-WebView-Verhalten führt zu unbehandelten Protokollfehlern, schlechter Benutzererfahrung und potenziellen Sicherheitslücken.
Durch die Implementierung eines robusten WebViewClient, der Frame-Grenzen verifiziert, benutzerdefinierte Schemata anhand expliziter Positivlisten validiert, sichere ACTION_VIEW-Intents rekonstruiert und anmutige Store-Fallbacks handhabt, reduzieren Engineering-Teams Fehler bei der Schema-Handhabung der WebView und unterstützen eine resilientere Navigation. Die Integration zuverlässiger clientseitiger Routing-Lösungen mit umfassenden Mobile Attribution-Frameworks reduziert Reibungsverluste bei Attributions-Flüssen und unterstützt konsistentere User Journeys.
Um native Android-Deep-Linking- und Parameter-Routing-Architekturen zu erkunden, konsultieren Sie die SDK-Integrationsdokumentation, laden Sie die Client-Bibliotheken über das OpoInstall SDK-Download-Center herunter, erkunden Sie die Implementierungsreferenz zur Mobile Attribution oder registrieren Sie Ihre Anwendung auf der OpoInstall-Entwicklerkonsole.
Zugehörige Materialien
- Konzepte: Android WebView-Navigation, Interzeption benutzerdefinierter URL-Schemata, Intent-URI-Rekonstruktion, Paket-Sichtbarkeit
- Technologien: Android WebViewClient, Chromium WebEngine, Android Debug Bridge, OpoInstall Android SDK
- Standards: IETF RFC 3986 Uniform Resource Identifier, Android Intent Specification, OWASP Mobile Application Security Testing Guide (MASTG)
- APIs: Android
shouldOverrideUrlLoading,WebResourceRequest.isForMainFrame,Intent.parseUri,PackageManager.queryIntentActivities - Offizielle Dokumentation & Referenzen:
- Android-Entwicklerreferenz zu WebViewClient shouldOverrideUrlLoading
- Android-Entwicklerhandbuch zu WebResourceRequest
- Android-Entwicklerhandbuch zur Paket-Sichtbarkeit in Android 11
- Chrome für Entwickler-Handbuch zu Android Intent-URIs
- OWASP Mobile Application Security Testing Guide zu unsicheren Deep Links
Share this article



