在Xamarin中如何通过WebView内的下载元素实现文件下载?
Xamarin.Forms WebView 页面内置下载元素触发下载实现方案
该场景可以实现下载功能,无需修改前端页面代码,核心通过自定义WebView渲染器在Android、iOS平台层分别监听下载事件、接管下载逻辑即可。
实现核心思路
Xamarin.Forms 官方封装的WebView仅提供基础页面加载能力,所有页面触发的下载请求(包括无显式链接、点击元素动态生成的下载请求)都会被默认丢弃,必须通过自定义渲染器调用原生WebView的下载监听接口捕获请求。
Android端实现
- 自定义WebView渲染器,为原生
Android.Webkit.WebView设置DownloadListener监听 - 当页面触发下载时,
OnDownloadStart回调会返回下载所需的全量参数:下载地址、请求头、文件名、文件类型、文件大小等,即使是动态生成的无显式链接的下载也能正常捕获 - 调用系统
DownloadManager或自定义下载逻辑执行下载即可,注意适配不同Android版本的存储权限:- Android 6.0~9.0 需要申请
WRITE_EXTERNAL_STORAGE权限 - Android 13及以上需要申请
POST_NOTIFICATIONS权限用于展示下载通知
- Android 6.0~9.0 需要申请
示例代码片段:
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(CustomWebViewRenderer))] namespace YourNamespace.Droid.Renderers { public class CustomWebViewRenderer : WebViewRenderer { public CustomWebViewRenderer(Context context) : base(context) { } protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e) { base.OnElementChanged(e); if (Control != null) { Control.SetDownloadListener(new CustomDownloadListener(Context)); } } } public class CustomDownloadListener : Java.Lang.Object, IDownloadListener { private readonly Context _context; public CustomDownloadListener(Context context) => _context = context; public void OnDownloadStart(string url, string userAgent, string contentDisposition, string mimeType, long contentLength) { // 解析文件名 var fileName = Android.Webkit.URLUtil.GuessFileName(url, contentDisposition, mimeType); // 构造下载请求 DownloadManager.Request request = new DownloadManager.Request(Android.Net.Uri.Parse(url)); request.SetMimeType(mimeType); request.AddRequestHeader("User-Agent", userAgent); request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted); request.SetDestinationInExternalPublicDir(Android.OS.Environment.DirectoryDownloads, fileName); // 发起下载 DownloadManager dm = (DownloadManager)_context.GetSystemService(Context.DownloadService); dm.Enqueue(request); } } }
iOS端实现
- 自定义WebView渲染器,为原生
WKWebView设置WKNavigationDelegate,监听页面导航响应 - 在
DecidePolicy回调中判断响应的MIME类型,如果是可下载的文件类型(非页面/图片等可直接渲染的类型),则拦截该请求,转为下载逻辑 - 对于前端通过Blob动态生成的下载场景,可提前注入JS代码拦截Blob生成逻辑,将Blob数据转为Base64字符串传递到原生层后写入本地文件
示例代码片段:
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(CustomWebViewRenderer))] namespace YourNamespace.iOS.Renderers { public class CustomWebViewRenderer : WkWebViewRenderer { protected override void OnElementChanged(VisualElementChangedEventArgs e) { base.OnElementChanged(e); if (e.NewElement != null) { NavigationDelegate = new CustomWKNavigationDelegate(); } } } public class CustomWKNavigationDelegate : WKNavigationDelegate { public override void DecidePolicy(WKWebView webView, WKNavigationResponse navigationResponse, Action<WKNavigationResponsePolicy> decisionHandler) { var response = navigationResponse.Response as NSHttpUrlResponse; // 判断是否为需要下载的文件类型 if (response != null && !response.MimeType.StartsWith("text/") && !response.MimeType.StartsWith("image/")) { // 拦截导航,执行下载 decisionHandler(WKNavigationResponsePolicy.Cancel); // 使用NSUrlSession下载文件到本地 NSUrlSession.SharedSession.CreateDownloadTask(response.Url, (location, _, error) => { if (error == null && location != null) { // 移动文件到下载目录 var destPath = Path.Combine(NSSearchPath.GetDirectories(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomain.User)[0], response.SuggestedFilename ?? "unknown"); NSFileManager.DefaultManager.Move(location, NSUrl.FromFilename(destPath), out _); // 可选:通知用户下载完成 } }).Resume(); return; } decisionHandler(WKNavigationResponsePolicy.Allow); } } }
注意事项
- 针对前端通过JS生成Blob触发的下载场景,Android端的
DownloadListener可以正常捕获,iOS端需要额外注入JS做Blob拦截处理 - 下载路径可根据业务需求调整,保存到App沙盒内或系统公共下载目录
- 下载完成后可调用原生弹窗/通知告知用户,也可直接调用系统应用打开下载的文件
内容的提问来源于stack exchange,提问作者navid
相关产品推荐
相关产品推荐

