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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 20:23:07