برای استفاده از WebView در Jetpack Compose، باید آن را در AndroidView بپیچید.
این راهنما موارد استفاده رایج و نحوه پشتیبانی از آنها را در Compose توضیح میدهد.
پیچیدن «وبنما» با AndroidView
برای استفاده از WebView در Compose، آن را با 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) } } ) }
این روش برای نشان دادن نشانی وب ساده در برنامه شما کار میکند. بااینحال، WebView با چرخههای حیات وضعیت پیچیده سروکار دارد که از چرخه حیات «نمای Android» و چرخه حیات Compose جدا هستند. ادغام Compose میتواند سناریوهای پیچیده
WebView ایجاد کند که منجر به اشکالات دشوار میشود. بخشهای زیر
موارد استفادهای را شرح میدهند که ممکن است برای پشتیبانی از این ویژگیها به رسیدگی خاصی نیاز داشته باشند.
حفظ وضعیت «وبنما»
مدیریت تغییرات پیکربندی و پیمایش در Compose چالشبرانگیز است زیرا
WebView یک View قدیمی است که به میزبان Activity خود محدود شده است، و
توصیه نمیشود که نمونه آن بیشتر از چرخه حیات Activity دوام بیاورد.
بنابراین، روش استاندارد برای حفظ وضعیت WebView این است که اجازه دهید
نمونههای WebView همراه با Activity ازبین بروند و دوباره ایجاد شوند. میتوانید بااستفاده از Bundle، سابقه پیمایش داخلی و وضعیت پیمایش آن را بهصورت دستی حفظ کنید.
@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() ) }
مدیریت پیمایش به عقب
وقتی WebView سابقه پیمایش داشته باشد، اشاره برگشت سیستم باید در WebView به عقب پیمایش کند، نه اینکه از صفحه خارج شود.
از Compose BackHandler API برای رهگیری رویداد برگشت سیستم استفاده کنید، و
تابع WebView goBack() را فراخوانی کنید:
// ... @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 } ) } } }
این پیادهسازی رفتار ناوبری به سبک مرورگر را ارائه میدهد.
پیمایش تودرتو
هنگام استفاده از WebView در Compose، پیمایش تودرتو بهراحتی پشتیبانی نمیشود. وقتی
WebView را در یک محفظه Compose پیمایشی، مثل
LazyColumn، قرار میدهید، WebView ممکن است همه اشارههای پیمایشی را مصرف کند.
ازآنجاییکه WebView به موتور پردازش داخلی خودش متکی است، تودرتو کردن آن با
LazyColumn درحالحاضر بهدرستی کار نمیکند.
برای پیگیری پیشرفت پشتیبانی رسمی پیمایش تودرتو برای WebView،
این مشکل را ببینید.
چیدمانهای لبهبهلبه و تورفتگیهای پنجره
هنگام استفاده از چیدمانهای لبهبهلبه، ممکن است محتوای WebView در زیر نوارهای سیستم، مانند نوار وضعیت، ظاهر شود. میتوانید از اصلاحکننده windowInsetsPadding برای
انتقال کل WebView به منطقه امن استفاده کنید:
@Composable fun EdgeToEdgeDemo(url: String) { AndroidView( modifier = Modifier .fillMaxSize() .windowInsetsPadding(WindowInsets.systemBars), factory = { context -> WebView(context).apply { loadUrl(url) } } ) }
برای اطلاعات بیشتر درباره تورفتگیها، آشنایی با تورفتگیهای پنجره در WebView را ببینید.
همگامسازی زمینه برنامه با محتوای «وبنما»
وقتی برنامه بین حالت روشن و تاریک جابهجا میشود، محتوای WebView میتواند
بهطور خودکار بدون بار کردن مجدد صفحه بهروز شود، البته اگر بهدرستی مدیریت شود.
اگر مالک محتوای صفحه وب هستید، برای همگامسازی رنگها با زمینه برنامه،
پرسوجوی رسانه prefers-color-scheme را مدیریت کنید تا مطمئن شوید صفحه وب شما با
زمینه انتخابشده سازگار میشود.
برای فعال کردن عناصر بومی مانند منوهای کرکرهای و بالاپرها برای شناسایی و مطابقت با زمینه برنامهتان، یک زمینه سبک DayNight را به Activity. خود اعمال کنید
<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) } } ) }
اگر صفحه وب زمینه تیره ندارد، یا اگر مالک محتوای وب نیستید، تیره کردن الگوریتمی ممکن است به اعمال اجباری زمینه تیره کمک کند. وبسایتهای مدرنی که ازقبل حالت تاریک دارند این الگوریتم را نادیده میگیرند و بهجای آن از سبکهای داخلی خودشان استفاده میکنند.
مدیریت اجازههای وب در Compose
وقتی صفحه وب درخواست دسترسی به سختافزار یا داده (برای مثال، دوربین،
میکروفون، یا مکان) میکند، WebView بازخوانیهای خاصی را در
WebChromeClient خود راهاندازی میکند. باید این فراخوانیهای برگشتی را مدیریت کنید و مطمئن شوید که اجازههای زمان اجرای Android مربوطه اعطا شده است.
مدیریت اجازههای دوربین و میکروفون
وقتی صفحه وب درخواست دسترسی به دوربین یا میکروفون میکند (برای مثال، WebRTC یا
ضبط ویدیو)، WebView با WebChromeClient.onPermissionRequest تماس میگیرد.
بااینحال، قبلاز فراخوانی grant()، باید اجازههای زمان اجرای Android زیر را درخواست کنید:
Manifest.permission.CAMERAManifest.permission.RECORD_AUDIO
ابتدا، یک کنترلکننده اجازه برای WebView تعریف کنید که
PermissionRequest درخواستشده از WebView را ردیابی کند:
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 } }
سپس، یک composable ایجاد کنید که WebViewPermissionHandler را بهخاطر بسپارد. از
rememberLauncherForActivityResult برای درخواست اجازهها استفاده کنید:
@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 } } }
اجازه را از فراخوانی onPermissionRequest مدیریت کنید. با این کار، راهانداز اجازه
راهاندازی میشود:
@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() ) }
جایگزین «وبنمای» جاسازیشده
اگر ترجیح میدهید از جاسازی WebView اجتناب کنید، Android گزینههای دیگری برای
نمایش محتوای وب ارائه میدهد، مانند برگههای سفارشی Chrome. برای درک نحوه انتخاب رویکرد صحیح برای موارد استفادهتان (مانند مرور یا اصالتسنجی)، استفاده از محتوای وب در برنامه Android را ببینید.