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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 12:48:08