Android 13中WebView无法打开文件管理器上传图片视频问题
Android 13 WebView 文件上传无法打开文件管理器的修复方案
核心问题分析
- 权限请求后无重试逻辑:当前
file_permission()在请求权限后直接返回false,导致权限授予后文件选择逻辑不会重新触发。 - Intent 兼容性问题:Android 13 对
Intent.ACTION_GET_CONTENT的处理更严格,硬编码file_type = "*/*"可能导致文件管理器无法正确响应。 - Uri 构建方式过时:使用
Uri.fromFile()在部分Android 13设备上会触发安全限制,需适配更合规的Uri生成方式。
具体修复步骤
1. 补充权限请求回调处理
重写onRequestPermissionsResult方法,在权限授予后重新触发文件选择逻辑:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 1) { boolean allGranted = true; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { allGranted = false; break; } } if (allGranted && file_path != null) { // 重新触发文件选择逻辑 webView.setWebChromeClient(new WebChromeClient(){ @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { return MainActivity.this.onShowFileChooser(webView, filePathCallback, fileChooserParams); } }); file_path = null; } } }
2. 修复Intent与文件类型处理
将硬编码的file_type替换为fileChooserParams提供的类型,同时改用ACTION_OPEN_DOCUMENT适配Android 13:
// 从fileChooserParams获取文件类型,替代硬编码的*/* String fileType = "*/*"; if (fileChooserParams.getAcceptTypes().length > 0) { fileType = fileChooserParams.getAcceptTypes()[0]; } // 改用ACTION_OPEN_DOCUMENT替代ACTION_GET_CONTENT Intent contentSelectionIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); contentSelectionIntent.setType(fileType); // 支持多文件选择(按需开启) contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);
3. 替换过时的Uri构建方式
使用FileProvider生成安全Uri(需配置Manifest和路径文件):
// 以拍照为例,替换Uri.fromFile逻辑 if (photoFile != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { cam_file_data = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile).toString(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile)); } else { cam_file_data = "file:" + photoFile.getAbsolutePath(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); } }
在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" /> <external-files-path name="my_videos" path="Movies" /> </paths>
4. 优化权限检查方法
简化file_permission()逻辑,避免冗余判断:
public boolean file_permission(){ if(Build.VERSION.SDK_INT >=33){ boolean hasCamera = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; boolean hasImages = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED; boolean hasVideos = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VIDEO) == PackageManager.PERMISSION_GRANTED; boolean hasAudio = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO) == PackageManager.PERMISSION_GRANTED; if (!hasCamera || !hasImages || !hasVideos || !hasAudio) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_AUDIO}, 1); return false; } } else if (Build.VERSION.SDK_INT >=23){ boolean hasWrite = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; boolean hasCamera = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; if (!hasWrite || !hasCamera) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 1); return false; } } return true; }
5. 抽离文件选择逻辑为独立方法
将onShowFileChooser中的逻辑抽成独立方法,方便权限授予后复用:
private boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { if(file_permission()) { file_path = filePathCallback; Intent takePictureIntent = null; Intent takeVideoIntent = null; boolean includeVideo = false; boolean includePhoto = false; paramCheck: for (String acceptTypes : fileChooserParams.getAcceptTypes()) { String[] splitTypes = acceptTypes.split(", ?+"); for (String acceptType : splitTypes) { switch (acceptType) { case "*/*": includePhoto = true; includeVideo = true; break paramCheck; case "image/*": includePhoto = true; break; case "video/*": includeVideo = true; break; } } } if (fileChooserParams.getAcceptTypes().length == 0) { includePhoto = true; includeVideo = true; } if (includePhoto) { takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(this.getPackageManager()) != null) { File photoFile = null; try { photoFile = create_image(); } catch (IOException ex) { Log.e(TAG, "Image file creation failed", ex); } if (photoFile != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { cam_file_data = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile).toString(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile)); } else { cam_file_data = "file:" + photoFile.getAbsolutePath(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); } } else { cam_file_data = null; takePictureIntent = null; } } } if (includeVideo) { takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); if (takeVideoIntent.resolveActivity(this.getPackageManager()) != null) { File videoFile = null; try { videoFile = create_video(); } catch (IOException ex) { Log.e(TAG, "Video file creation failed", ex); } if (videoFile != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { cam_file_data = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", videoFile).toString(); takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", videoFile)); } else { cam_file_data = "file:" + videoFile.getAbsolutePath(); takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(videoFile)); } } else { cam_file_data = null; takeVideoIntent = null; } } } String fileType = "*/*"; if (fileChooserParams.getAcceptTypes().length > 0) { fileType = fileChooserParams.getAcceptTypes()[0]; } Intent contentSelectionIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); contentSelectionIntent.setType(fileType); contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE); Intent[] intentArray; if (takePictureIntent != null && takeVideoIntent != null) { intentArray = new Intent[]{takePictureIntent, takeVideoIntent}; } else if (takePictureIntent != null) { intentArray = new Intent[]{takePictureIntent}; } else if (takeVideoIntent != null) { intentArray = new Intent[]{takeVideoIntent}; } else { intentArray = new Intent[0]; } Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); chooserIntent.putExtra(Intent.EXTRA_TITLE, "File chooser"); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); startActivityForResult(chooserIntent, file_req_code); return true; } else { return false; } }
在WebChromeClient中调用该方法:
webView.setWebChromeClient(new WebChromeClient(){ @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); progress.setProgress(newProgress); progress.setVisibility(newProgress == 100 ? View.GONE : View.VISIBLE); } @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { return MainActivity.this.onShowFileChooser(webView, filePathCallback, fileChooserParams); } });
总结
通过补充权限回调逻辑、适配Android 13的Intent行为、替换安全的Uri构建方式,即可解决WebView文件上传在Android 13中无法打开文件管理器的问题。
内容的提问来源于stack exchange,提问作者Abdullah Chowdhury
相关产品推荐
相关产品推荐

