Jetpack Compose中使用WebView加载网页

39 阅读1分钟
  • 支持选择本地文件
  • 支持返回键后退到
  • 支持http和https地址

image.png

添加权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

添加http链接支持

xml下创建network_security_config.xml文件

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

AndroidManifest.xml的application中引用network_security_config

android:networkSecurityConfig="@xml/network_security_config"

加载网页完整代码

import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import com.cwj.test20251224.ui.theme.Test20251224Theme
import androidx.compose.runtime.Composable
import androidx.compose.ui.viewinterop.AndroidView
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.ui.Modifier

class MainActivity : ComponentActivity() {
    private lateinit var filePickerLauncher: ActivityResultLauncher<Intent>
    private var webView: WebView? = null
    private var backPressedCallback: OnBackPressedCallback? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        filePickerLauncher = registerForActivityResult(
            ActivityResultContracts.StartActivityForResult()
        ) { result ->
            val resultArray = when {
                result.resultCode == RESULT_OK && result.data != null -> {
                    WebChromeClient.FileChooserParams.parseResult(result.resultCode, result.data)
                }

                else -> null
            }

            FileChooserHelper.onActivityResult(resultArray)
        }

        setContent {
            Test20251224Theme {
                Scaffold(modifier = Modifier.fillMaxSize()) { _ ->
                    WebPageContainer(
                        url = "https://juejin.cn/",
                        filePickerLauncher = filePickerLauncher,
                        onWebViewCreated = { webView ->
                            this.webView = webView
                            registerBackPressedCallback()
                        }
                    )
                }
            }
        }
    }

    // 注册返回按键处理
    private fun registerBackPressedCallback() {
        backPressedCallback?.remove()
        backPressedCallback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (webView?.canGoBack() == true) {
                    webView?.goBack()
                } else {
                    isEnabled = false
                    onBackPressedDispatcher.onBackPressed()
                }
            }
        }
        onBackPressedDispatcher.addCallback(this, backPressedCallback!!)
    }
}

// 文件选择处理
object FileChooserHelper {
    private var filePathCallback: ValueCallback<Array<Uri>>? = null

    fun setFilePathCallback(callback: ValueCallback<Array<Uri>>) {
        filePathCallback?.onReceiveValue(null)
        filePathCallback = callback
    }

    fun onActivityResult(uris: Array<Uri>?) {
        filePathCallback?.let { callback ->
            callback.onReceiveValue(uris ?: arrayOf())
            filePathCallback = null
        }
    }
}

@Composable
fun WebPageContainer(
    url: String,
    filePickerLauncher: ActivityResultLauncher<Intent>,
    onWebViewCreated: (WebView) -> Unit = {}
) {
    Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
        WebPage(
            url = url,
            modifier = Modifier
                .fillMaxSize()
                .padding(innerPadding),
            onFileChoose = { intent ->
                filePickerLauncher.launch(intent)
            },
            onWebViewCreated = onWebViewCreated
        )
    }
}

@Composable
fun WebPage(
    url: String,
    modifier: Modifier = Modifier,
    onFileChoose: (Intent) -> Unit,
    onWebViewCreated: (WebView) -> Unit = {}
) {
    Column(modifier = modifier) {
        // WebView容器
        AndroidView(
            modifier = Modifier.weight(1f),
            factory = { ctx ->
                WebView(ctx).apply {
                    webViewClient = WebViewClient()

                    settings.apply {
                        javaScriptEnabled = true
                        domStorageEnabled = true
                        allowFileAccess = true
                        allowContentAccess = true
                        mediaPlaybackRequiresUserGesture = false
                        useWideViewPort = true
                        loadWithOverviewMode = true
                    }

                    onWebViewCreated(this)

                    webChromeClient = object : WebChromeClient() {
                        override fun onShowFileChooser(
                            webView: WebView,
                            filePathCallback: ValueCallback<Array<Uri>>,
                            fileChooserParams: FileChooserParams
                        ): Boolean {
                            FileChooserHelper.setFilePathCallback(filePathCallback)
                            val intent = fileChooserParams.createIntent().apply {
                                addCategory(Intent.CATEGORY_OPENABLE)
                                type = "image/*"
                                if (fileChooserParams.mode == FileChooserParams.MODE_OPEN_MULTIPLE) {
                                    putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
                                }
                            }
                            onFileChoose(intent)
                            return true
                        }
                    }
                }
            },
            update = { webView ->
                if (webView.url != url) {
                    webView.loadUrl(url)
                }
            }
        )
    }
}