Android WebView相册上传正常但相机拍照上传失效问题咨询(含Android11适配)
问题描述
搜索本站所有相关问题帖子,仍无法定位Android 11设备上WebView调用相机拍照上传图片失效的问题,相册上传功能完全正常。在onActivityResult回调中可以获取到缓存目录下的图片路径,但网页端无法访问该文件,或是缓存目录下的图片文件为空。直接使用设备自带的谷歌Chrome浏览器可以正常拍照并上传图片,可排除网页侧问题。当前代码在Android 10及以下版本运行正常,Android 11版本拍照完成后图片无法填充到WebView的文件上传输入框中,推测是FileProvider适配或配置存在问题。
已配置权限
<uses-feature android:name="android.hardware.camera"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.CAMERA"/> <!-- for new versions api 21+--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA2" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
AndroidManifest.xml application节点配置
<application android:name="***" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:requestLegacyExternalStorage="true" android:hardwareAccelerated="true" android:theme="@style/Theme.Android"> <!-- added fileprovider support --> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
file_paths.xml配置
<paths> <cache-path name="mediaimages" path="./"> </cache-path> </paths>
核心业务代码
private var mUploadCallbackAboveL: ValueCallback<Array<Uri>>? = null private var mUploadCallbackBelow: ValueCallback<Uri?>? = null private var imageUri: Uri? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // webview definition myWebView.webChromeClient = initChromeWebViewClient() myWebView.webViewClient = initWebViewClient() myWebView.settings.javaScriptEnabled = true myWebView.settings.domStorageEnabled = true myWebView.settings.allowFileAccess = true myWebView.settings.allowContentAccess = true myWebView.settings.supportZoom() myWebView.visibility = View.INVISIBLE myWebView.settings.userAgentString = "android" myWebView.setLayerType(View.LAYER_TYPE_HARDWARE, null) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { myWebView.settings.mixedContentMode = 0 } } private fun initChromeWebViewClient() : WebChromeClient { webChromeViewClient = object : WebChromeClient() { /** * API >= 21 (Android 5.0.1) calls back this method */ override fun onShowFileChooser( webView: WebView?, valueCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams? ): Boolean { mUploadCallbackAboveL = valueCallback takePhoto() return true } } return webChromeViewClient } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE) { val file: File = File(imageUri!!.toString()) val file_size = (file.length() / 1024).toString().toInt() if (mUploadCallbackBelow != null) { chooseBelow(resultCode, data); } else if (mUploadCallbackAboveL != null) { chooseAbove(resultCode, data); } else { Toast.makeText (this, "an error occurred.", Toast.LENGTH_SHORT).show(); } } }
拍照Intent创建逻辑
private fun takePhoto() { var photoFile : File? = null val authorities : String = applicationContext.packageName + ".provider" try { photoFile = createImageFile() imageUri = FileProvider.getUriForFile(this, authorities, photoFile) } catch(e: IOException) { e.printStackTrace() } val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri) val Photo = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) val chooserIntent = Intent.createChooser(Photo, "Image Chooser") chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf<Parcelable>(captureIntent)) startActivityForResult(chooserIntent, REQUEST_CODE) } @Throws(IOException::class) private fun createImageFile(): File { val imageFileName = "JPEG_" + SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) return File(applicationContext.cacheDir.toString() + File.separator + imageFileName + ".jpg") }
结果处理逻辑
private fun chooseAbove(resultCode : Int, data : Intent?) { if (RESULT_OK == resultCode) { updatePhotos() if (data != null) { val results : Array<Uri> val uriData : Uri? = data.data if (uriData != null) { results = arrayOf(uriData) mUploadCallbackAboveL!!.onReceiveValue(results) } else { mUploadCallbackAboveL!!.onReceiveValue(null) } } else { if (imageUri != null) { mUploadCallbackAboveL!!.onReceiveValue(arrayOf(imageUri!!)) } } } else { mUploadCallbackAboveL!!.onReceiveValue(null) } mUploadCallbackAboveL = null }
解决方案
按以下步骤依次修改即可修复Android 11适配问题:
- 清理并更新权限配置
删除无效的WRITE_INTERNAL_STORAGE、READ_INTERNAL_STORAGE、重复的CAMERA权限,新增Android 11必要配置:
<uses-feature android:name="android.hardware.camera" android:required="false"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/> <!-- Android 11+ 媒体访问权限 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:maxSdkVersion="32"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
- 更新FileProvider路径配置
修改res/xml/file_paths.xml,新增外部缓存路径适配:
<paths> <cache-path name="internal_media" path="." /> <external-cache-path name="external_media" path="." /> </paths>
- 补充相机Intent的Uri授权
Android 11对跨应用Uri访问限制更严格,需要给相机应用显式授予读写权限,修改takePhoto方法:
private fun takePhoto() { var photoFile : File? = null val authorities : String = applicationContext.packageName + ".provider" try { // 优先使用外部缓存,避免部分机型相机无法写入内部缓存 val saveDir = if (externalCacheDir != null) externalCacheDir else cacheDir val imageFileName = "JPEG_" + SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) photoFile = File(saveDir, "$imageFileName.jpg") imageUri = FileProvider.getUriForFile(this, authorities, photoFile) } catch(e: IOException) { e.printStackTrace() } val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri) // 给所有可处理拍照请求的应用授予Uri读写权限 val resolveInfos = packageManager.queryIntentActivities(captureIntent, PackageManager.MATCH_DEFAULT_ONLY) for (info in resolveInfos) { grantUriPermission(info.activityInfo.packageName, imageUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } val pickIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) val chooserIntent = Intent.createChooser(pickIntent, "选择图片") chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf<Parcelable>(captureIntent)) startActivityForResult(chooserIntent, REQUEST_CODE) }
- 修复onActivityResult错误逻辑
删除错误的File转换代码,imageUri是content://协议的Uri,不能直接转为File路径:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE) { if (mUploadCallbackBelow != null) { chooseBelow(resultCode, data) } else if (mUploadCallbackAboveL != null) { chooseAbove(resultCode, data) } else { Toast.makeText (this, "发生错误", Toast.LENGTH_SHORT).show() } } }
- 补充WebView权限配置
在onCreate的WebView初始化部分新增跨域访问配置:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { myWebView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW myWebView.settings.allowFileAccessFromFileURLs = true myWebView.settings.allowUniversalAccessFromFileURLs = true }
内容的提问来源于stack exchange,提问作者Vario
相关产品推荐
相关产品推荐

