如何使用Java在Android WebView中启用文件上传下载功能
Android WebView 文件上传、下载功能Java配置方案
前置准备:清单权限配置
先在AndroidManifest.xml中声明功能必需的权限:
<!-- 基础网络权限 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Android 12及以下读取本地文件权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Android 13+ 媒体文件读取权限 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!-- Android 9及以下写入存储权限,用于保存下载文件 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
Android 6.0及以上版本的危险权限需要在代码中动态申请,必须拿到权限后再触发上传、下载逻辑。
基础WebView配置
先初始化WebSettings,打开基础能力开关:
WebSettings webSettings = webView.getSettings(); // 启用JavaScript,绝大多数网页的上传、下载交互依赖JS webSettings.setJavaScriptEnabled(true); // 开启本地文件访问权限 webSettings.setAllowFileAccess(true); // 开启DOM存储,避免网页本地存储异常导致功能失效 webSettings.setDomStorageEnabled(true); // 兼容HTTPS页面加载HTTP资源 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); }
启用文件下载能力
WebView默认不会接管下载事件,需要设置DownloadListener自定义处理逻辑,以下是对接系统下载管理器的实现:
webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> { DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); // 解析下载文件名 String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype); request.setMimeType(mimetype); request.addRequestHeader("User-Agent", userAgent); request.setTitle(fileName); request.setDescription("文件下载中"); // 允许移动网络、WiFi环境下下载 request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI); // 下载全程在通知栏显示进度 request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); // 文件保存到系统公共下载目录 request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName); DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); downloadManager.enqueue(request); Toast.makeText(this, "已开始下载文件", Toast.LENGTH_SHORT).show(); });
- 简单场景可以直接替换为跳转系统浏览器处理下载,无需自行申请存储权限
- Android 10及以上使用分区存储,写入公共下载目录不需要额外申请存储权限
启用文件上传能力
WebView的文件选择能力通过WebChromeClient的隐藏回调实现,需要重写对应方法接收选择的文件并回传给网页:
- 先在Activity中定义回调持有变量
// 持有网页的文件选择回调 private ValueCallback<Uri[]> filePathCallback; private static final int FILE_CHOOSER_REQ_CODE = 10001;
- 给WebView设置自定义WebChromeClient
webView.setWebChromeClient(new WebChromeClient() { // Android 5.0及以上版本走该回调 @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> callback, FileChooserParams params) { // 清空上一次未处理的回调,避免重复点击无响应 if (filePathCallback != null) { filePathCallback.onReceiveValue(null); } filePathCallback = callback; // 构造文件选择Intent Intent chooseIntent = new Intent(Intent.ACTION_GET_CONTENT); chooseIntent.addCategory(Intent.CATEGORY_OPENABLE); // 支持多选 chooseIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, params.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE); // 匹配网页要求的文件类型 String[] acceptTypes = params.getAcceptTypes(); chooseIntent.setType("*/*"); if (acceptTypes != null && acceptTypes.length > 0 && !TextUtils.isEmpty(acceptTypes[0])) { chooseIntent.setType(String.join(" ", acceptTypes)); } startActivityForResult(Intent.createChooser(chooseIntent, "选择上传文件"), FILE_CHOOSER_REQ_CODE); return true; } // Android 4.4及以下版本兼容回调,可根据目标版本按需实现 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {} });
- 重写
onActivityResult接收文件选择结果,回传给WebView
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == FILE_CHOOSER_REQ_CODE) { if (filePathCallback == null) return; Uri[] resultUris = null; if (resultCode == RESULT_OK && data != null) { // 处理多选文件 if (data.getClipData() != null) { int count = data.getClipData().getItemCount(); resultUris = new Uri[count]; for (int i = 0; i < count; i++) { resultUris[i] = data.getClipData().getItemAt(i).getUri(); } } // 处理单选文件 else if (data.getData() != null) { resultUris = new Uri[]{data.getData()}; } } // *必须调用该方法回传结果,即使用户取消选择也要传null,否则后续点击上传按钮会无响应* filePathCallback.onReceiveValue(resultUris); filePathCallback = null; } }
常见问题排查
- 上传按钮点击无反应:检查是否漏写
onReceiveValue回调,是否动态申请了文件读取权限 - 下载无响应:检查是否设置了
DownloadListener,是否声明了网络权限 - 选择文件后网页无反应:检查是否正确解析了返回的Uri,不要自定义修改Uri结构直接回传即可
内容的提问来源于stack exchange,提问作者vishvajith abegunasekara
相关产品推荐
相关产品推荐

