You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android WebView文件上传:如何添加相机选择选项?

解决Android WebView文件上传无法选择相机的问题

这个问题很常见,原因是你当前直接使用fileChooserParams.createIntent()启动的只是系统默认的文件选择器,并没有包含相机拍摄的选项。我们需要自定义一个包含文件选择和相机拍照的选择器,让用户可以自由切换,同时还要处理相机拍照后的文件Uri传递逻辑。

步骤1:添加必要权限和FileProvider配置

首先要确保你的App声明了相机和存储权限(Android 6.0+需要动态申请),同时为了适配Android 7.0及以上的文件Uri安全机制,需要配置FileProvider:

  1. 在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>
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 10:37:35