Wenn Sie ein WebView in Jetpack Compose verwenden möchten, müssen Sie es in ein AndroidView einfügen.
In diesem Leitfaden werden gängige Anwendungsfälle und die Unterstützung dieser Anwendungsfälle in Compose erläutert.
WebView mit AndroidView umschließen
Wenn Sie ein WebView in Compose verwenden möchten, umschließen Sie es mit einem AndroidView:
@Composable fun SimpleWebView( initialUrl: String, modifier: Modifier = Modifier ) { AndroidView( modifier = modifier.fillMaxSize(), factory = { context -> WebView(context).apply { webViewClient = WebViewClient() settings.javaScriptEnabled = true loadUrl(initialUrl) } } ) }
Das funktioniert, wenn Sie eine einfache URL in Ihrer App anzeigen möchten. WebView verarbeitet jedoch komplexe Statuslebenszyklen, die vom Android-View-Lebenszyklus und vom Compose-Lebenszyklus getrennt sind. Die Integration von Compose kann zu komplexen WebView-Szenarien führen, die schwer zu debuggen sind. In den folgenden Abschnitten werden Anwendungsfälle beschrieben, die möglicherweise eine spezielle Verarbeitung erfordern, um diese Funktionen zu unterstützen.
WebView-Status beibehalten
Die Verarbeitung von Konfigurationsänderungen und die Navigation in Compose sind schwierig, da WebView ein Legacy-View ist, das an seinen Host Activity gebunden ist. Es wird nicht empfohlen, dass seine Instanz den Lebenszyklus von Activity überdauert.
Daher besteht die Standardmethode zum Beibehalten des Status eines WebView darin, dass WebView-Instanzen zusammen mit dem Activity zerstört und neu erstellt werden können. Sie können den internen Navigationsverlauf und den Scrollstatus manuell mit einem Bundle beibehalten.
@Composable fun PersistentWebView(url: String) { val webViewStateBundle = rememberSaveable { Bundle() } AndroidView( factory = { context -> WebView(context).apply { webViewClient = WebViewClient() settings.javaScriptEnabled = true // Restore the state and history if (webViewStateBundle.containsKey("WEBVIEW_STATE")) { restoreState(webViewStateBundle.getBundle("WEBVIEW_STATE")!!) } else { loadUrl(url) } } }, onRelease = { releasedWebView -> // Save navigation history before the instance is destroyed val bundle = Bundle() releasedWebView.saveState(bundle) webViewStateBundle.putBundle("WEBVIEW_STATE", bundle) }, modifier = Modifier.fillMaxSize() ) }
Rückwärtsnavigation verarbeiten
Wenn ein WebView einen Navigationsverlauf hat, sollte mit der System-„Zurück“-Geste innerhalb des WebView zurückgegangen werden, anstatt den Bildschirm zu verlassen.
Verwenden Sie die Compose-API BackHandler, um das Systemereignis „Zurück“ abzufangen und die Funktion WebView goBack() aufzurufen:
// ... @Composable fun BackNavigationDemoScreen(onBack: () -> Unit) { // Hold a reference to the WebView to check its history state var webViewReference by remember { mutableStateOf<WebView?>(null) } // Intercept the system back press if the WebView has history BackHandler(enabled = true) { val webView = webViewReference if (webView != null && webView.canGoBack()) { webView.goBack() // Go back in history } else { onBack() // Exit screen } } Scaffold( topBar = { TopAppBar( title = { Text("Back Navigation Demo") }, navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") } } ) } ) { padding -> Column(modifier = Modifier.fillMaxSize().padding(padding)) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> WebView(context).apply { settings.javaScriptEnabled = true // Keeps link navigations internal to the WebView instead of opening Chrome webViewClient = WebViewClient() loadUrl("https://developer.android.com") webViewReference = this } }, onRelease = { webViewReference = null } ) } } }
Diese Implementierung bietet ein Navigationsverhalten wie in einem Browser.
Verschachteltes Scrollen
Verschachteltes Scrollen wird bei Verwendung von WebView in Compose nicht ohne Weiteres unterstützt. Wenn Sie ein WebView in einem scrollbaren Compose-Container wie einem LazyColumn platzieren, kann das WebView alle Scrollgesten erfassen.
Da WebView auf einer eigenen internen Rendering-Engine basiert, funktioniert das Einbetten in LazyColumn derzeit nicht richtig.
Den Fortschritt der offiziellen Unterstützung für verschachteltes Scrollen für WebView können Sie in diesem Problem verfolgen.
Edge-to-Edge-Layouts und Fenstereinzüge
Bei der Verwendung von Edge-to-Edge-Layouts können WebView-Inhalte unter Systemleisten wie der Statusleiste angezeigt werden. Mit dem Modifikator windowInsetsPadding können Sie das gesamte WebView in den Safe Area verschieben:
@Composable fun EdgeToEdgeDemo(url: String) { AndroidView( modifier = Modifier .fillMaxSize() .windowInsetsPadding(WindowInsets.systemBars), factory = { context -> WebView(context).apply { loadUrl(url) } } ) }
Weitere Informationen zu Insets finden Sie unter Fenster-Insets in WebView.
App-Design mit WebView-Inhalten synchronisieren
Wenn die Anwendung zwischen dem hellen und dem dunklen Modus wechselt, können WebView-Inhalte automatisch aktualisiert werden, ohne dass die Seite neu geladen werden muss, sofern dies richtig gehandhabt wird.
Wenn Sie die Inhalte der Webseite besitzen, können Sie die Farben mit dem Design der App synchronisieren, indem Sie die Media-Anfrage prefers-color-scheme verarbeiten. So wird dafür gesorgt, dass sich Ihre Webseite an das ausgewählte Design anpasst.
Damit native Elemente wie Drop-down-Menüs und Pop-ups das Design Ihrer App erkennen und anpassen können, müssen Sie ein DayNight-Stildesign auf Ihr Activity. anwenden.
<resources> <!-- ... <!-- Use a DayNight theme in your manifest to handle both modes automatically --> <style name="Theme.Webviewdemo.DayNight" parent="Theme.AppCompat.DayNight.NoActionBar" /> </resources>
@Composable fun ThemeSyncDemo(onBack: () -> Unit) { val context = LocalContext.current AndroidView( modifier = Modifier.fillMaxSize(), factory = { _ -> WebView(context).apply { settings.javaScriptEnabled = true webViewClient = WebViewClient() val html = """ <html> <head> // ... @media (prefers-color-scheme: dark) { body { background-color: #212121; color: #ffffff; } select { border-color: #BB86FC; background: #212121; color: #ffffff; } } </style> </head> // ... </html> """.trimIndent() loadDataWithBaseURL(null, html, "text/html", "UTF-8", null) } } ) }
Wenn die Webseite kein dunkles Design hat oder Sie nicht der Inhaber der Webinhalte sind, kann algorithmisches Abdunkeln helfen, ein dunkles Design zu erzwingen. Moderne Websites, die bereits einen dunklen Modus haben, ignorieren diesen Algorithmus und verwenden stattdessen ihre eigenen integrierten Stile.
Webberechtigungen in Compose verarbeiten
Wenn eine Webseite Hardware- oder Datenzugriff anfordert (z. B. Kamera, Mikrofon oder Standort), löst WebView bestimmte Rückrufe in der WebChromeClient aus. Sie müssen diese Rückrufe verarbeiten und dafür sorgen, dass die entsprechenden Android-Laufzeitberechtigungen erteilt werden.
Kamera- und Mikrofonberechtigungen verarbeiten
Wenn eine Webseite Zugriff auf die Kamera oder das Mikrofon anfordert (z. B. WebRTC oder Videoaufzeichnung), ruft WebView WebChromeClient.onPermissionRequest auf.
Bevor Sie grant() aufrufen, müssen Sie jedoch die folgenden Android-Laufzeitberechtigungen anfordern:
Manifest.permission.CAMERAManifest.permission.RECORD_AUDIO
Definieren Sie zuerst einen Berechtigungshandler für WebView, der die von WebView angeforderten PermissionRequest im Blick behält:
class WebViewPermissionHandler( private val launcher: ManagedActivityResultLauncher<Array<String>, Map<String, Boolean>> ) { var pendingRequest by mutableStateOf<PermissionRequest?>(null) private set fun handleRequest(request: PermissionRequest) { val isTrustedOrigin = request.origin.host == "www.trusted-domain.com" || request.origin.host == "app.local" // Always verify the origin before granting request if (!isTrustedOrigin) { Log.w("WebViewPermission", "Blocked and denied permission request from untrusted origin: ${request.origin.host}") request.deny() return } val androidPermissions = mutableListOf<String>() request.resources.forEach { resource -> when (resource) { PermissionRequest.RESOURCE_VIDEO_CAPTURE -> androidPermissions.add(Manifest.permission.CAMERA) PermissionRequest.RESOURCE_AUDIO_CAPTURE -> androidPermissions.add(Manifest.permission.RECORD_AUDIO) } } // Save the request and launch the Android system dialog pendingRequest = request launcher.launch(androidPermissions.toTypedArray()) } fun onResult(results: Map<String, Boolean>) { val allGranted = results.values.all { it } Log.d("WebViewPermission", "Kotlin: All permissions granted? $allGranted") if (allGranted) { pendingRequest?.grant(arrayOf("/* list of permissions */")) } else { pendingRequest?.deny() } pendingRequest = null } }
Als Nächstes erstellen Sie eine Composable, die sich an WebViewPermissionHandler erinnert. Verwenden Sie rememberLauncherForActivityResult, um Berechtigungen anzufordern:
@Composable fun rememberWebViewPermissionHandler(): WebViewPermissionHandler { val handlerState = remember { mutableStateOf<WebViewPermissionHandler?>(null) } val launcher = rememberLauncherForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { results -> handlerState.value?.onResult(results) } return remember { WebViewPermissionHandler(launcher).also { handlerState.value = it } } }
Verarbeite die Berechtigung über den onPermissionRequest-Callback. Dadurch wird der Berechtigungs-Launcher gestartet:
@Composable fun WebViewPermissionScreen() { val permissionHandler = rememberWebViewPermissionHandler() AndroidView( factory = { context -> WebView(context).apply { settings.javaScriptEnabled = true webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { // Simply delegate to the handler permissionHandler.handleRequest(request) } } // load a web page that needs permissions } }, modifier = Modifier.fillMaxSize() ) }
Alternative zu einer eingebetteten WebView
Wenn Sie WebView nicht einbetten möchten, bietet Android andere Optionen zum Anzeigen von Webinhalten, z. B. benutzerdefinierte Tabs in Chrome. Unter Web-Content in Ihrer Android-App verwenden erfahren Sie, wie Sie den richtigen Ansatz für Ihre Anwendungsfälle (z. B. Browsing oder Authentifizierung) auswählen.