Xamarin.Android WebView实现点击文件输入弹出相机/文件选择框
问题原因
Xamarin.Android 原生WebView默认没有内置和系统Chrome一致的文件选择交互逻辑:当网页中<input type="file">控件未显式声明capture属性时,默认回调只会直接拉起系统文件管理器,不会弹出包含「拍照上传」「选择本地文件」的选项弹窗,和Chrome浏览器的完整实现逻辑存在差异。
实现步骤
1. 自定义WebChromeClient重写文件选择回调
5.0及以上安卓版本的WebView文件选择事件统一走OnShowFileChooser回调,不要使用已废弃的旧版OpenFile方法。
首先在承载WebView的Activity中定义全局变量:
private IValueCallback _filePathCallback; private Android.Net.Uri _cameraImageUri; private const int RequestChooseFile = 1001; private const int RequestTakePhoto = 1002;
然后自定义WebChromeClient子类,重写选择逻辑:
public class CustomWebChromeClient : WebChromeClient { private readonly MainActivity _activity; public CustomWebChromeClient(MainActivity activity) { _activity = activity; } public override bool OnShowFileChooser(WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams) { // 清空上一次遗留的回调,避免重复点击失效 _activity._filePathCallback?.OnReceiveValue(null); _activity._filePathCallback = filePathCallback; // 构建选择方式弹窗 AlertDialog.Builder builder = new AlertDialog.Builder(_activity); builder.SetTitle("选择上传方式"); builder.SetItems(new string[] { "拍照上传", "选择本地文件" }, (sender, args) => { switch (args.Which) { case 0: // 拉起相机 Intent takePhotoIntent = new Intent(MediaStore.ActionImageCapture); if (takePhotoIntent.ResolveActivity(_activity.PackageManager) != null) { // 创建拍照临时存储文件 Java.IO.File photoFile = new Java.IO.File( _activity.GetExternalFilesDir(Android.OS.Environment.DirectoryPictures), $"upload_{Guid.NewGuid()}.jpg" ); _activity._cameraImageUri = FileProvider.GetUriForFile( _activity, $"{_activity.PackageName}.fileprovider", photoFile ); takePhotoIntent.PutExtra(MediaStore.ExtraOutput, _activity._cameraImageUri); takePhotoIntent.AddFlags(ActivityFlags.GrantReadUriPermission); takePhotoIntent.AddFlags(ActivityFlags.GrantWriteUriPermission); _activity.StartActivityForResult(takePhotoIntent, RequestTakePhoto); } else { // 设备无可用相机应用,返回空值 _activity._filePathCallback.OnReceiveValue(null); _activity._filePathCallback = null; } break; case 1: // 拉起文件选择器 Intent chooseFileIntent = new Intent(Intent.ActionGetContent); chooseFileIntent.AddCategory(Intent.CategoryOpenable); chooseFileIntent.SetType("*/*"); chooseFileIntent.PutExtra(Intent.ExtraAllowMultiple, fileChooserParams.Mode == FileChooserMode.OpenMultiple); _activity.StartActivityForResult( Intent.CreateChooser(chooseFileIntent, "选择文件"), RequestChooseFile ); break; } }); builder.SetCancelable(true); builder.SetOnCancelListener(dialog => { _activity._filePathCallback?.OnReceiveValue(null); _activity._filePathCallback = null; }); builder.Show(); return true; } }
2. 配置FileProvider适配Android 7.0+
安卓7.0以上禁止直接向外部应用传递file://类型URI,必须通过FileProvider生成 content 类型URI,否则会抛出FileUriExposedException。
- 在
AndroidManifest.xml的<application>节点内添加Provider配置:
<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>
- 在
Resources/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>
3. 重写OnActivityResult处理选择结果
在Activity中重写结果回调,将拍照/选文件的结果回传给WebView:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (_filePathCallback == null) return; Android.Net.Uri[] results = null; if (resultCode == Result.Ok) { switch (requestCode) { case RequestTakePhoto: // 拍照返回,直接传递预生成的图片URI results = new[] { _cameraImageUri }; break; case RequestChooseFile: // 文件选择返回,兼容单选/多选场景 if (data != null) { if (data.ClipData != null) { results = new Android.Net.Uri[data.ClipData.ItemCount]; for (int i = 0; i < data.ClipData.ItemCount; i++) { results[i] = data.ClipData.GetItemAt(i).Uri; } } else if (data.Data != null) { results = new[] { data.Data }; } } break; } } // 结果回传给WebView _filePathCallback.OnReceiveValue(results); _filePathCallback = null; _cameraImageUri = null; }
4. 绑定自定义Client到WebView
初始化WebView时完成配置绑定:
webView.SetWebChromeClient(new CustomWebChromeClient(this)); webView.Settings.JavaScriptEnabled = true; webView.Settings.AllowFileAccess = true;
注意事项
- 必须在
AndroidManifest.xml中声明相机权限:<uses-permission android:name="android.permission.CAMERA" />,Android 6.0及以上版本需要在运行时动态申请相机权限,否则拍照功能会触发权限异常。 - 如果需要扩展支持录制视频、录制音频的上传场景,只需在选择弹窗中新增对应选项,调整Intent动作类型和临时文件存储逻辑即可。
- 务必在选择取消、结果回调完成后将
_filePathCallback置空,否则会出现首次点击正常、后续点击文件输入控件无响应的问题。
内容的提问来源于stack exchange,提问作者Salkov
相关产品推荐
相关产品推荐

