Wenn Sie eine WebView in Jetpack Compose verwenden möchten, müssen Sie sie in eine AndroidView einbinden.
In diesem Leitfaden werden gängige Anwendungsfälle und deren Unterstützung in Compose erläutert.
WebView mit AndroidView umschließen
Wenn Sie einen WebView in Compose verwenden möchten, umschließen Sie ihn 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 eine einfache URL in der App angezeigt werden soll. WebView befasst sich jedoch mit komplexen Statuslebenszyklen, die vom Android View-Lebenszyklus und vom Compose-Lebenszyklus getrennt sind. Die Einbindung von Compose kann zu komplexen WebView-Szenarien führen, die schwer zu behebende Fehler verursachen. In den folgenden Abschnitten werden Anwendungsfälle beschrieben, die möglicherweise eine spezielle Handhabung erfordern, um diese Funktionen zu unterstützen.
WebView-Status beibehalten
Der Umgang mit Konfigurationsänderungen und der Navigation in Compose ist eine Herausforderung, da WebView eine Legacy-View ist, die an ihren Host Activity gebunden ist. Es wird nicht empfohlen, dass die Instanz den Lebenszyklus von Activity überdauert.
Die Standardmethode zum Beibehalten des Status eines WebView besteht daher darin, zuzulassen, dass WebView-Instanzen zusammen mit dem Activity gelöscht und neu erstellt werden. 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 die Systemgeste „Zurück“ innerhalb des WebView zurückgehen, anstatt den Bildschirm zu verlassen.
Verwenden Sie die Compose API BackHandler, um das Systemereignis „Zurück“ abzufangen, und rufen Sie die Funktion WebView goBack() auf:
// ... @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 im Browser.
Verschachteltes Scrollen
Verschachteltes Scrollen wird bei Verwendung von WebView in Compose nicht ohne Weiteres unterstützt. Wenn Sie eine WebView in einem scrollbaren Compose-Container wie einem LazyColumn platzieren, werden möglicherweise alle Scroll-Gesten von der WebView verarbeitet.
Da WebView auf einer eigenen internen Rendering-Engine basiert, funktioniert die Verschachtelung mit LazyColumn derzeit nicht richtig.
Informationen zum Fortschritt der offiziellen Unterstützung für verschachteltes Scrollen für WebView finden Sie in diesem Problem.
Randlose Layouts und Fenster-Insets
Bei randlosen Layouts können WebView-Inhalte unter Systemleisten wie der Statusleiste angezeigt werden. Mit dem Modifikator windowInsetsPadding können Sie die gesamte WebView in den sicheren Bereich 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 korrekt gehandhabt wird.
Wenn Sie der Inhaber des Webseiteninhalts sind, können Sie die Farben mit dem Design der App synchronisieren. Verarbeiten Sie dazu die Medienabfrage prefers-color-scheme, damit 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 sich daran anpassen können, wenden Sie ein DayNight-Design auf Ihre Activity. an.
<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 des Webinhalts sind, kann die algorithmische Abdunklung 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 verwalten
Wenn eine Webseite Zugriff auf Hardware oder Daten anfordert (z. B. Kamera, Mikrofon oder Standort), löst WebView bestimmte Callbacks in der WebChromeClient aus. Sie müssen diese Callbacks verarbeiten und dafür sorgen, dass die entsprechenden Android-Laufzeitberechtigungen gewährt werden.
Kamera- und Mikrofonberechtigungen verwalten
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 } }
Erstellen Sie als Nächstes eine zusammensetzbare Funktion, die sich den WebViewPermissionHandler merkt. 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 Webinhalte in Ihrer Android-App verwenden erfahren Sie, wie Sie den richtigen Ansatz für Ihre Anwendungsfälle (z. B. Surfen oder Authentifizierung) auswählen.