Android WebView文件上传:如何添加相机选择选项?
解决Android WebView文件上传无法选择相机的问题
这个问题很常见,原因是你当前直接使用fileChooserParams.createIntent()启动的只是系统默认的文件选择器,并没有包含相机拍摄的选项。我们需要自定义一个包含文件选择和相机拍照的选择器,让用户可以自由切换,同时还要处理相机拍照后的文件Uri传递逻辑。
步骤1:添加必要权限和FileProvider配置
首先要确保你的App声明了相机和存储权限(Android 6.0+需要动态申请),同时为了适配Android 7.0及以上的文件Uri安全机制,需要配置FileProvider:
- 在
AndroidManifest.xml中添加FileProvider:
<application ...> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
- 在
res/xml目录下创建file_paths.xml文件:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="my_images" path="Pictures" /> </paths>
步骤2:修改WebView的文件选择逻辑
更新你的MainActivity代码,添加相机Intent的创建、临时文件Uri管理,以及权限检查:
private var filePathCallback: ValueCallback<Array<Uri>>? = null private var cameraImageUri: Uri? = null private val CHOOSE_FILE_REQUEST_CODE = 1001 private val REQUEST_CAMERA_PERMISSION = 1002 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) webView.webChromeClient = object : WebChromeClient() { override fun onShowFileChooser( webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams? ): Boolean { // 先处理之前未完成的请求 this@MainActivity.filePathCallback?.onReceiveValue(null) this@MainActivity.filePathCallback = filePathCallback // 检查相机权限 if (ContextCompat.checkSelfPermission( this@MainActivity, Manifest.permission.CAMERA ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( this@MainActivity, arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_CAMERA_PERMISSION ) return true } // 创建相机拍照的Intent val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) // 创建临时文件保存拍照结果 val photoFile: File? = try { createImageFile() } catch (ex: IOException) { null } photoFile?.also { cameraImageUri = FileProvider.getUriForFile( this@MainActivity, "${packageName}.fileprovider", it ) takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraImageUri) // 授予Uri权限给相机应用 takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } // 获取默认的文件选择Intent val fileChooserIntent = fileChooserParams?.createIntent() // 构建可选的Intent列表,过滤掉无法被系统处理的Intent val intentList = mutableListOf<Intent>() takePictureIntent.resolveActivity(packageManager)?.let { intentList.add(takePictureIntent) } fileChooserIntent?.resolveActivity(packageManager)?.let { intentList.add(fileChooserIntent) } if (intentList.isNotEmpty()) { val chooserIntent = Intent.createChooser( fileChooserIntent, "选择图片来源" ) chooserIntent.putExtra( Intent.EXTRA_INITIAL_INTENTS, intentList.toTypedArray() ) startActivityForResult(chooserIntent, CHOOSE_FILE_REQUEST_CODE) } else { // 如果没有可用的Intent,回调null filePathCallback?.onReceiveValue(null) this@MainActivity.filePathCallback = null } return true } } } // 创建临时图片文件 @Throws(IOException::class) private fun createImageFile(): File { val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES) return File.createTempFile( "JPEG_${timeStamp}_", /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ).apply { // 保存文件路径用于后续删除(可选) absolutePath } } // 处理权限请求结果 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) { // 权限申请成功后,重新触发文件选择(可以通过重新调用WebView的上传按钮逻辑,或者这里手动处理) // 这里简单处理,直接回调null让WebView重新发起请求 filePathCallback?.onReceiveValue(null) filePathCallback = null } else { // 权限拒绝,回调null filePathCallback?.onReceiveValue(null) filePathCallback = null } } } // 处理选择结果 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { CHOOSE_FILE_REQUEST_CODE -> { if (resultCode == Activity.RESULT_OK) { val results: Array<Uri>? = when { // 相机拍照返回的情况,直接用之前保存的cameraImageUri data == null && cameraImageUri != null -> { arrayOf(cameraImageUri!!) } // 文件选择器返回的情况 else -> { WebChromeClient.FileChooserParams.parseResult(resultCode, data) } } filePathCallback?.onReceiveValue(results) cameraImageUri = null // 重置相机Uri } else { // 取消选择,回调null filePathCallback?.onReceiveValue(null) } filePathCallback = null } } }
关键说明
- 权限处理:Android 6.0+必须动态申请相机和存储权限,否则无法启动相机。
- FileProvider:解决Android 7.0以上的
FileUriExposedException,确保文件Uri的安全传递。 - 临时文件管理:相机拍照需要指定输出文件,这里创建的临时文件保存在App的外部私有目录,不需要额外的存储权限(Android 10+)。
- Intent过滤:通过
resolveActivity检查Intent是否能被系统处理,避免启动不存在的组件。
这样修改后,用户点击WebView的上传按钮时,就会弹出包含“相机”和“文件管理器”的选择器,两种方式的结果都能正确传递给WebView的文件上传回调。
内容的提问来源于stack exchange,提问作者bnotion thoughts
相关产品推荐
相关产品推荐

