Android WebView实现input type=file分文件浏览器与相机功能
解决方案:WebView中适配HTML文件上传的相机/相册选择
核心思路
要让WebView自动区分HTML中input[type=file]的capture属性,直接唤起对应功能,需要自定义WebChromeClient重写文件选择逻辑,同时处理动态权限申请(Android 6+要求)。
完整实现步骤
1. 调整AndroidManifest权限(保留必要项)
移除冗余权限,适配Android 8+及更高版本的权限规范:
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" android:required="true"/>
注:Android 13+ 用
READ_MEDIA_IMAGES替代旧版存储权限,无需保留WRITE_EXTERNAL_STORAGE等冗余权限。
2. MainActivity完整Kotlin代码
package com.example.testapp import android.Manifest import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.provider.MediaStore import android.webkit.* import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat class MainActivity : AppCompatActivity() { private lateinit var myWebView: WebView private var filePathCallback: ValueCallback<Array<Uri>>? = null private val REQUEST_CAMERA_PERMISSION = 1001 private val REQUEST_IMAGE_CAPTURE = 1002 private val REQUEST_IMAGE_PICK = 1003 override fun onCreate(savedInstanceState: Bundle?) { supportActionBar?.hide() super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) myWebView = findViewById(R.id.wv) setupWebViewConfig() myWebView.loadUrl("https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/capture") } private fun setupWebViewConfig() { val webSettings = myWebView.settings webSettings.javaScriptEnabled = true webSettings.allowFileAccess = true webSettings.allowContentAccess = true webSettings.javaScriptCanOpenWindowsAutomatically = true webSettings.mediaPlaybackRequiresUserGesture = false myWebView.webViewClient = WebViewClient() // 自定义WebChromeClient处理文件选择逻辑 myWebView.webChromeClient = object : WebChromeClient() { override fun onShowFileChooser( webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams? ): Boolean { this@MainActivity.filePathCallback = filePathCallback // 读取HTML input的capture属性,判断是否直接调用相机 val captureAttr = fileChooserParams?.parameters?.get("capture") return if (captureAttr == "environment") { checkCameraPermission() true } else { openImagePicker() true } } } } // 检查相机权限,无权限则动态申请 private fun checkCameraPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_IMAGES), REQUEST_CAMERA_PERMISSION ) } else { startCameraCapture() } } // 启动系统相机 private fun startCameraCapture() { val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) takePictureIntent.resolveActivity(packageManager)?.let { startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE) } } // 打开系统图片选择器 private fun openImagePicker() { val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) intent.type = "image/*" startActivityForResult(intent, REQUEST_IMAGE_PICK) } // 权限申请结果回调 override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_CAMERA_PERMISSION) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startCameraCapture() } } } // 相机/相册返回结果回调 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { REQUEST_IMAGE_CAPTURE -> { if (resultCode == Activity.RESULT_OK && data != null) { val imageUri = data.extras?.get("data") as? Uri imageUri?.let { filePathCallback?.onReceiveValue(arrayOf(it)) } } else { filePathCallback?.onReceiveValue(null) } filePathCallback = null } REQUEST_IMAGE_PICK -> { if (resultCode == Activity.RESULT_OK && data != null) { val imageUri = data.data imageUri?.let { filePathCallback?.onReceiveValue(arrayOf(it)) } } else { filePathCallback?.onReceiveValue(null) } filePathCallback = null } } } override fun onBackPressed() { if (myWebView.canGoBack()) { myWebView.goBack() } else { super.onBackPressed() } } }
关键细节说明
- 自动区分capture属性:通过
fileChooserParams.parameters["capture"]获取HTML input的属性值,直接对应唤起相机或相册 - 权限合规:动态申请相机和媒体读取权限,符合Android 6+权限要求
- 回调闭环:将拍摄/选择的图片Uri通过
filePathCallback返回给WebView,确保HTML表单能正常接收文件 - 无冗余逻辑:完全适配Android 8+,无需兼容旧版本的冗余代码
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

