Загрузка файлов в MAUI Android WebView
У меня есть WebView в приложении MAUI, которое обычно работает, но всякий раз, когда я нажимаю ссылку на Android, которая должна загрузить файл (ссылка возвращает заголовок Content-Disposition), ничего не происходит.
Как это должно быть реализовано? Я не могу найти никакой документации.
<WebView
x:Name="WebView"
Source=".." />
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override bool OnBackButtonPressed()
{
base.OnBackButtonPressed();
if (WebView.CanGoBack)
{
WebView.GoBack();
return true;
}
else
{
base.OnBackButtonPressed();
return true;
}
}
}
Связанный вопрос для iOS: загрузка файлов в MAUI iOS WebView
2 ответа
Для всех, кому интересно, вот мое полное решение для загрузки файла из WebView в MAUI на Android и его открытия в соответствии сContent-Disposition
заголовок.
Я загружаю файлы в общедоступную папку «Загрузки», что позволяет мне избежать работы с FileProvider.DownloadManager.GetUriForDownloadedFile()
уже возвращаетcontent://
URI, который может использоваться намерением.
MainPage.xaml.cs, в котором есть WebView:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnHandlerChanged()
{
base.OnHandlerChanged();
#if ANDROID
var androidWebView = WebView.Handler.PlatformView as Android.Webkit.WebView;
// If this is not disabled then download links that open in a new tab won't work
androidWebView.Settings.SetSupportMultipleWindows(false);
// Custom download listener for Android
androidWebView.SetDownloadListener(new Platforms.Android.MyDownloadListener());
#endif
}
}
Платформы\Android\MyDownloadListener.cs:
using Android.App;
using Android.Content;
using Android.Webkit;
using Android.Widget;
using System.Text.RegularExpressions;
public class MyDownloadListener : Java.Lang.Object, IDownloadListener
{
private readonly Regex _fileNameRegex = new("filename\\*?=['\"]?(?:UTF-\\d['\"]*)?([^;\\r\\n\"']*)['\"]?;?", RegexOptions.Compiled);
public void OnDownloadStart(string url, string userAgent, string contentDisposition, string mimetype, long contentLength)
{
if (!TryGetFileNameFromContentDisposition(contentDisposition, out var fileName))
{
// GuessFileName doesn't work well, use it as a fallback
fileName = URLUtil.GuessFileName(url, contentDisposition, mimetype);
}
var text = $"Downloading {fileName}...";
var uri = global::Android.Net.Uri.Parse(url);
var context = Platform.CurrentActivity?.Window?.DecorView.FindViewById(global::Android.Resource.Id.Content)?.RootView?.Context;
try
{
var request = new DownloadManager.Request(uri);
request.SetTitle(fileName);
request.SetDescription(text);
request.SetMimeType(mimetype);
request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
// File should be saved in public downloads so that it can be opened without extra effort
request.SetDestinationInExternalPublicDir(global::Android.OS.Environment.DirectoryDownloads, fileName);
// Cookies have to be copied, otherwise authorized files won't download
var cookie = CookieManager.Instance.GetCookie(url);
request.AddRequestHeader("Cookie", cookie);
var downloadManager = (DownloadManager)Platform.CurrentActivity.GetSystemService(Context.DownloadService);
var downloadId = downloadManager.Enqueue(request);
if (ShouldOpenFile(contentDisposition))
{
// Receiver will open the file after the download has finished
context.RegisterReceiver(new MyBroadcastReceiver(downloadId), new IntentFilter(DownloadManager.ActionDownloadComplete));
}
Toast
.MakeText(
context,
text,
ToastLength.Short)
.Show();
}
catch (Java.Lang.Exception ex)
{
Toast
.MakeText(
context,
$"Unable to download file: {ex.Message}",
ToastLength.Long)
.Show();
}
}
private bool TryGetFileNameFromContentDisposition(string contentDisposition, out string fileName)
{
if (string.IsNullOrEmpty(contentDisposition))
{
fileName = null;
return false;
}
var match = _fileNameRegex.Match(contentDisposition);
if (!match.Success)
{
fileName = null;
return false;
}
// Use first match even though there could be several matched file names
fileName = match.Groups[1].Value;
return true;
}
private bool ShouldOpenFile(string contentDisposition)
{
if (string.IsNullOrEmpty(contentDisposition))
{
return false;
}
return contentDisposition.StartsWith("inline", StringComparison.InvariantCultureIgnoreCase);
}
}
Платформы\Android\MyBroadcastReceiver.cs:
using Android.App;
using Android.Content;
using Android.Widget;
public class MyBroadcastReceiver : BroadcastReceiver
{
private readonly long _downloadId;
public MyBroadcastReceiver(long downloadId)
{
_downloadId = downloadId;
}
public override void OnReceive(Context context, Intent intent)
{
// Only handle download broadcasts
if (intent.Action == DownloadManager.ActionDownloadComplete)
{
var downloadId = intent.GetLongExtra(DownloadManager.ExtraDownloadId, 0);
// Only handle specific download ID
if (downloadId == _downloadId)
{
OpenFile(context, downloadId);
context.UnregisterReceiver(this);
}
}
}
private void OpenFile(Context context, long downloadId)
{
var downloadManager = (DownloadManager)context.GetSystemService(Context.DownloadService);
var fileUri = downloadManager.GetUriForDownloadedFile(downloadId);
var fileMimeType = downloadManager.GetMimeTypeForDownloadedFile(downloadId);
if (fileUri == null || fileMimeType == null)
{
return;
}
var viewFileIntent = new Intent(Intent.ActionView);
viewFileIntent.SetDataAndType(fileUri, fileMimeType);
viewFileIntent.SetFlags(ActivityFlags.GrantReadUriPermission);
viewFileIntent.AddFlags(ActivityFlags.NewTask);
try
{
context.StartActivity(viewFileIntent);
}
catch (Java.Lang.Exception ex)
{
Toast
.MakeText(
context,
$"Unable to open file: {ex.Message}",
ToastLength.Long)
.Show();
}
}
}
Для Android вы можете попробовать добавить DownloadListener в веб-просмотр. Я проверил это, и файл может быть успешно загружен.
Создайте собственный класс прослушивателя загрузки в папке \Platforms\Android:
public class MyDownLoadListener : Java.Lang.Object, IDownloadListener
{
public void OnDownloadStart(string url, string userAgent, string contentDisposition, string mimetype, long contentLength)
{
var manager = (DownloadManager)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity.GetSystemService(global::Android.App.Application.DownloadService);
var uri = global::Android.Net.Uri.Parse(url);
DownloadManager.Request downloadrequest = new DownloadManager.Request(uri);
downloadrequest.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
manager.Enqueue(downloadrequest);
}
}
В xaml страницы:
<WebView x:Name="webview" Source="https://www.myfreemp3.com.cn/" HeightRequest="500"/>
И установите прослушиватель для веб-просмотра в Page.cs:
protected override void OnHandlerChanged()
{
base.OnHandlerChanged();
#if ANDROID
(webview.Handler.PlatformView as Android.Webkit.WebView).SetDownloadListener(new Platforms.Android.MyDownLoadListener());
(webview.Handler.PlatformView as Android.Webkit.WebView).Settings.JavaScriptEnabled = true;
(webview.Handler.PlatformView as Android.Webkit.WebView).Settings.DomStorageEnabled = true;
#endif
}