如何在 Android 上使用 Xamarin WebView 像本机浏览器一样下载文件?
How to download files like the native browser with Xamarin WebView on Android?
我想下载在我的应用程序中实现了 WebView 的任何类型的文件。
在 iOS 上,我注意到 WebView 大多只能显示其内部的文件。
在 Android,WebView 以某种方式陷入循环或加载过程中。它可能会尝试下载该文件,但不能。
iOS 的本机行为,因此 Safari -> 在 WebView 中显示 pdf、doc 或 html。
Android 的本机行为,即 Chrome -> 下载所有内容,然后尝试在下载后尝试打开它时使用正确的应用程序查看它,或者在下载后立即打开它(在 Huawei P30 Pro 上测试过Android11.0.0.153).
为什么我的 WebView 不能这样做。谁能帮我解决这个问题?
我不想在 Android 的资源中创建自定义布局,我不能采用 Android git 示例中实现的方法,因为它会由于会话劫持而在我的网站上被阻止。如果您想查看 android WebView 无限加载,只需在 MainPage.xaml.cs:
的“if”子句中注释所有内容
if (Device.RuntimePlatform.Equals(Device.Android))
MainPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WebViewExample.MainPage"
xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
xmlns:customhybridwebview="clr-namespace:WebViewExample.View.Custom">
<ContentPage.Content>
<StackLayout
BackgroundColor="#000"
Spacing="0"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<ActivityIndicator x:Name="loadingIndicator"
HorizontalOptions="Center"
VerticalOptions="Center"
Color="Red"
BackgroundColor="Black">
<ActivityIndicator.HeightRequest>
<OnPlatform x:TypeArguments="x:Double" iOS="60" Android="40"/>
</ActivityIndicator.HeightRequest>
<ActivityIndicator.WidthRequest>
<OnPlatform x:TypeArguments="x:Double" iOS="60" Android="40"/>
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
<customhybridwebview:HybridWebView x:Name="hybridWebView"
Source="{Binding SourceUrl}"
HeightRequest="1000"
WidthRequest="1000"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Navigating="HybridWebView_Navigating"
Navigated="HybridWebView_Navigated"
android:WebView.DisplayZoomControls="False"
android:WebView.EnableZoomControls="True"
android:WebView.MixedContentMode="AlwaysAllow">
</customhybridwebview:HybridWebView>
<Button Text="Back"
TextColor="Red"
BackgroundColor="Black"
HorizontalOptions="CenterAndExpand"
Clicked="ClickedWebViewGoBack"
IsVisible="{Binding BackButtonIsVisible}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MainPage.xaml.cs:
using System;
using System.Diagnostics;
using WebViewExample.ViewModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace WebViewExample
{
public partial class MainPage : ContentPage
{
private bool navigatingIsGettingCanceled = false;
private bool webViewBackButtonPressed = false;
readonly MainPageViewModel mainPageViewModel;
public MainPage()
{
InitializeComponent();
mainPageViewModel = new MainPageViewModel();
BindingContext = mainPageViewModel;
mainPageViewModel.LoadURL();
}
protected override bool OnBackButtonPressed()
{
base.OnBackButtonPressed();
if (hybridWebView.CanGoBack)
{
hybridWebView.GoBack();
return true;
}
return false;
}
private void HybridWebView_Navigating(object sender, WebNavigatingEventArgs e)
{
Debug.WriteLine("Debug - Sender: " + sender.ToString());
Debug.WriteLine("Debug - Url: " + e.Url.ToString());
if (!loadingIndicator.IsRunning)
{
loadingIndicator.IsRunning = loadingIndicator.IsVisible = true;
//check every new URL the WebView tries connecting to
if (hybridWebView == null) { return; }
if (hybridWebView.Source == null) { return; }
string nextURL = e.Url;
string urlProperty = hybridWebView.Source.GetValue(UrlWebViewSource.UrlProperty).ToString();
if (String.IsNullOrEmpty(nextURL) || nextURL.Contains("about:blank"))
{
return;
}
if (Device.RuntimePlatform.Equals(Device.Android))
{
//navigatingIsGettingCanceled = true;
try
{
Browser.OpenAsync(nextURL, new BrowserLaunchOptions
{
LaunchMode = BrowserLaunchMode.SystemPreferred,
TitleMode = BrowserTitleMode.Show,
PreferredToolbarColor = Color.White,
PreferredControlColor = Color.Red
});
}
catch (Exception ex)
{
//An unexpected error occured. No browser may be installed on the device.
Debug.WriteLine(string.Format("Debug - Following Exception occured: {0}", ex));
}
}
if (Device.RuntimePlatform.Equals(Device.iOS))
{
mainPageViewModel.ShowBackButton();
}
e.Cancel = navigatingIsGettingCanceled;
if (navigatingIsGettingCanceled)
{
loadingIndicator.IsRunning = loadingIndicator.IsVisible = navigatingIsGettingCanceled = false;
Debug.WriteLine("Debug - Navigation getting cancelled");
return;
}
//edited so it would always SetValue (still not loading the pdf on Android)
hybridWebView.Source.SetValue(UrlWebViewSource.UrlProperty, nextURL);
Debug.WriteLine("Debug - Source changed");
}
}
private void HybridWebView_Navigated(object sender, WebNavigatedEventArgs e)
{
loadingIndicator.IsRunning = loadingIndicator.IsVisible = false;
if (webViewBackButtonPressed)
{
hybridWebView.GoBack();
webViewBackButtonPressed = false;
}
}
void ClickedWebViewGoBack(System.Object sender, System.EventArgs e)
{
if (hybridWebView.CanGoBack)
{
hybridWebView.GoBack();
mainPageViewModel.HideBackButton();
webViewBackButtonPressed = true;
}
}
}
}
MainPageViewModel.cs:
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
using WebViewExample.Model;
namespace WebViewExample.ViewModel
{
public class MainPageViewModel : INotifyPropertyChanged
{
public string SourceUrl { get; set; }
public bool BackButtonIsVisible { get; set; } = false;
public MainPageViewModel() { }
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public void LoadURL()
{
SourceUrl = Settings.SourceURL;
Debug.WriteLine("Debug - Load new URL: " + SourceUrl.ToString());
RefreshURL();
}
public void RefreshURL() => OnPropertyChanged(nameof(SourceUrl));
public void ShowBackButton()
{
BackButtonIsVisible = true;
OnPropertyChanged(nameof(BackButtonIsVisible));
}
public void HideBackButton()
{
BackButtonIsVisible = false;
OnPropertyChanged(nameof(BackButtonIsVisible));
}
}
}
Settings.cs:
using System;
using Xamarin.Essentials;
namespace WebViewExample.Model
{
public static class Settings
{
#region setting Constants
private const string KeySourceURL = "sourceURL";
private static readonly string SourceURLDEFAULT = "https://download.microsoft.com/download/7/8/8/788971A6-C4BB-43CA-91DC-557B8BE72928/Microsoft_Press_eBook_CreatingMobileAppswithXamarinForms_PDF.pdf";
#endregion
#region setting Properties
public static string SourceURL
{
get { return Preferences.Get(KeySourceURL, SourceURLDEFAULT); }
set { Preferences.Set(KeySourceURL, value); }
}
#endregion
}
}
HybridWebView.cs:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace WebViewExample.View.Custom
{
public class HybridWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
Debug.WriteLine("Debug - Register Action");
action = callback;
}
public void Cleanup()
{
Debug.WriteLine("Debug - Clear Action");
action = null;
}
public void InvokeAction(string data)
{
Debug.WriteLine("Debug - Invoke Action");
if (action == null || data == null)
{
return;
}
Debug.WriteLine("Debug - Data: " + data.ToString());
action.Invoke(data);
}
}
}
git 回购示例:
https://github.com/Nitroklas/WebViewDownloadingExample
Microsoft 论坛上的相同 post:
https://docs.microsoft.com/en-us/answers/questions/497906/how-to-download-files-like-the-native-browser-with.html
感谢您对此事的任何帮助。
我在这个主题上找到的所有内容都来自 2019 年或更早....
问候
尼克拉斯
您可以尝试使用下面的代码来使用自定义渲染器通过 WebView 下载文件。
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView),
typeof(UpgradeWebViewRenderer))]
namespace App8.Droid
{
public class UpgradeWebViewRenderer : ViewRenderer<Xamarin.Forms.WebView, global::Android.Webkit.WebView>
{
public UpgradeWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (this.Control == null)
{
var webView = new global::Android.Webkit.WebView(this.Context);
webView.SetWebViewClient(new WebViewClient());
webView.SetWebChromeClient(new WebChromeClient());
WebSettings webSettings = webView.Settings;
webSettings.JavaScriptEnabled = true;
webView.SetDownloadListener(new CustomDownloadListener());
this.SetNativeControl(webView);
var source = e.NewElement.Source as UrlWebViewSource;
if (source != null)
{
webView.LoadUrl(source.Url);
}
}
}
}
public class CustomDownloadListener : Java.Lang.Object, IDownloadListener
{
public void OnDownloadStart(string url, string userAgent, string contentDisposition, string mimetype, long contentLength)
{
DownloadManager.Request request = new DownloadManager.Request(Android.Net.Uri.Parse(url));
request.AllowScanningByMediaScanner();
request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
request.SetDestinationInExternalFilesDir(Forms.Context, Android.OS.Environment.DirectoryDownloads, "hello.jpg");
DownloadManager dm = (DownloadManager)Android.App.Application.Context.GetSystemService(Android.App.Application.DownloadService);
dm.Enqueue(request);
}
}
}
我想下载在我的应用程序中实现了 WebView 的任何类型的文件。 在 iOS 上,我注意到 WebView 大多只能显示其内部的文件。 在 Android,WebView 以某种方式陷入循环或加载过程中。它可能会尝试下载该文件,但不能。 iOS 的本机行为,因此 Safari -> 在 WebView 中显示 pdf、doc 或 html。 Android 的本机行为,即 Chrome -> 下载所有内容,然后尝试在下载后尝试打开它时使用正确的应用程序查看它,或者在下载后立即打开它(在 Huawei P30 Pro 上测试过Android11.0.0.153).
为什么我的 WebView 不能这样做。谁能帮我解决这个问题?
我不想在 Android 的资源中创建自定义布局,我不能采用 Android git 示例中实现的方法,因为它会由于会话劫持而在我的网站上被阻止。如果您想查看 android WebView 无限加载,只需在 MainPage.xaml.cs:
的“if”子句中注释所有内容if (Device.RuntimePlatform.Equals(Device.Android))
MainPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WebViewExample.MainPage"
xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
xmlns:customhybridwebview="clr-namespace:WebViewExample.View.Custom">
<ContentPage.Content>
<StackLayout
BackgroundColor="#000"
Spacing="0"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<ActivityIndicator x:Name="loadingIndicator"
HorizontalOptions="Center"
VerticalOptions="Center"
Color="Red"
BackgroundColor="Black">
<ActivityIndicator.HeightRequest>
<OnPlatform x:TypeArguments="x:Double" iOS="60" Android="40"/>
</ActivityIndicator.HeightRequest>
<ActivityIndicator.WidthRequest>
<OnPlatform x:TypeArguments="x:Double" iOS="60" Android="40"/>
</ActivityIndicator.WidthRequest>
</ActivityIndicator>
<customhybridwebview:HybridWebView x:Name="hybridWebView"
Source="{Binding SourceUrl}"
HeightRequest="1000"
WidthRequest="1000"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Navigating="HybridWebView_Navigating"
Navigated="HybridWebView_Navigated"
android:WebView.DisplayZoomControls="False"
android:WebView.EnableZoomControls="True"
android:WebView.MixedContentMode="AlwaysAllow">
</customhybridwebview:HybridWebView>
<Button Text="Back"
TextColor="Red"
BackgroundColor="Black"
HorizontalOptions="CenterAndExpand"
Clicked="ClickedWebViewGoBack"
IsVisible="{Binding BackButtonIsVisible}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MainPage.xaml.cs:
using System;
using System.Diagnostics;
using WebViewExample.ViewModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace WebViewExample
{
public partial class MainPage : ContentPage
{
private bool navigatingIsGettingCanceled = false;
private bool webViewBackButtonPressed = false;
readonly MainPageViewModel mainPageViewModel;
public MainPage()
{
InitializeComponent();
mainPageViewModel = new MainPageViewModel();
BindingContext = mainPageViewModel;
mainPageViewModel.LoadURL();
}
protected override bool OnBackButtonPressed()
{
base.OnBackButtonPressed();
if (hybridWebView.CanGoBack)
{
hybridWebView.GoBack();
return true;
}
return false;
}
private void HybridWebView_Navigating(object sender, WebNavigatingEventArgs e)
{
Debug.WriteLine("Debug - Sender: " + sender.ToString());
Debug.WriteLine("Debug - Url: " + e.Url.ToString());
if (!loadingIndicator.IsRunning)
{
loadingIndicator.IsRunning = loadingIndicator.IsVisible = true;
//check every new URL the WebView tries connecting to
if (hybridWebView == null) { return; }
if (hybridWebView.Source == null) { return; }
string nextURL = e.Url;
string urlProperty = hybridWebView.Source.GetValue(UrlWebViewSource.UrlProperty).ToString();
if (String.IsNullOrEmpty(nextURL) || nextURL.Contains("about:blank"))
{
return;
}
if (Device.RuntimePlatform.Equals(Device.Android))
{
//navigatingIsGettingCanceled = true;
try
{
Browser.OpenAsync(nextURL, new BrowserLaunchOptions
{
LaunchMode = BrowserLaunchMode.SystemPreferred,
TitleMode = BrowserTitleMode.Show,
PreferredToolbarColor = Color.White,
PreferredControlColor = Color.Red
});
}
catch (Exception ex)
{
//An unexpected error occured. No browser may be installed on the device.
Debug.WriteLine(string.Format("Debug - Following Exception occured: {0}", ex));
}
}
if (Device.RuntimePlatform.Equals(Device.iOS))
{
mainPageViewModel.ShowBackButton();
}
e.Cancel = navigatingIsGettingCanceled;
if (navigatingIsGettingCanceled)
{
loadingIndicator.IsRunning = loadingIndicator.IsVisible = navigatingIsGettingCanceled = false;
Debug.WriteLine("Debug - Navigation getting cancelled");
return;
}
//edited so it would always SetValue (still not loading the pdf on Android)
hybridWebView.Source.SetValue(UrlWebViewSource.UrlProperty, nextURL);
Debug.WriteLine("Debug - Source changed");
}
}
private void HybridWebView_Navigated(object sender, WebNavigatedEventArgs e)
{
loadingIndicator.IsRunning = loadingIndicator.IsVisible = false;
if (webViewBackButtonPressed)
{
hybridWebView.GoBack();
webViewBackButtonPressed = false;
}
}
void ClickedWebViewGoBack(System.Object sender, System.EventArgs e)
{
if (hybridWebView.CanGoBack)
{
hybridWebView.GoBack();
mainPageViewModel.HideBackButton();
webViewBackButtonPressed = true;
}
}
}
}
MainPageViewModel.cs:
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
using WebViewExample.Model;
namespace WebViewExample.ViewModel
{
public class MainPageViewModel : INotifyPropertyChanged
{
public string SourceUrl { get; set; }
public bool BackButtonIsVisible { get; set; } = false;
public MainPageViewModel() { }
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public void LoadURL()
{
SourceUrl = Settings.SourceURL;
Debug.WriteLine("Debug - Load new URL: " + SourceUrl.ToString());
RefreshURL();
}
public void RefreshURL() => OnPropertyChanged(nameof(SourceUrl));
public void ShowBackButton()
{
BackButtonIsVisible = true;
OnPropertyChanged(nameof(BackButtonIsVisible));
}
public void HideBackButton()
{
BackButtonIsVisible = false;
OnPropertyChanged(nameof(BackButtonIsVisible));
}
}
}
Settings.cs:
using System;
using Xamarin.Essentials;
namespace WebViewExample.Model
{
public static class Settings
{
#region setting Constants
private const string KeySourceURL = "sourceURL";
private static readonly string SourceURLDEFAULT = "https://download.microsoft.com/download/7/8/8/788971A6-C4BB-43CA-91DC-557B8BE72928/Microsoft_Press_eBook_CreatingMobileAppswithXamarinForms_PDF.pdf";
#endregion
#region setting Properties
public static string SourceURL
{
get { return Preferences.Get(KeySourceURL, SourceURLDEFAULT); }
set { Preferences.Set(KeySourceURL, value); }
}
#endregion
}
}
HybridWebView.cs:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace WebViewExample.View.Custom
{
public class HybridWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
Debug.WriteLine("Debug - Register Action");
action = callback;
}
public void Cleanup()
{
Debug.WriteLine("Debug - Clear Action");
action = null;
}
public void InvokeAction(string data)
{
Debug.WriteLine("Debug - Invoke Action");
if (action == null || data == null)
{
return;
}
Debug.WriteLine("Debug - Data: " + data.ToString());
action.Invoke(data);
}
}
}
git 回购示例: https://github.com/Nitroklas/WebViewDownloadingExample
Microsoft 论坛上的相同 post: https://docs.microsoft.com/en-us/answers/questions/497906/how-to-download-files-like-the-native-browser-with.html
感谢您对此事的任何帮助。 我在这个主题上找到的所有内容都来自 2019 年或更早....
问候 尼克拉斯
您可以尝试使用下面的代码来使用自定义渲染器通过 WebView 下载文件。
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView),
typeof(UpgradeWebViewRenderer))]
namespace App8.Droid
{
public class UpgradeWebViewRenderer : ViewRenderer<Xamarin.Forms.WebView, global::Android.Webkit.WebView>
{
public UpgradeWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (this.Control == null)
{
var webView = new global::Android.Webkit.WebView(this.Context);
webView.SetWebViewClient(new WebViewClient());
webView.SetWebChromeClient(new WebChromeClient());
WebSettings webSettings = webView.Settings;
webSettings.JavaScriptEnabled = true;
webView.SetDownloadListener(new CustomDownloadListener());
this.SetNativeControl(webView);
var source = e.NewElement.Source as UrlWebViewSource;
if (source != null)
{
webView.LoadUrl(source.Url);
}
}
}
}
public class CustomDownloadListener : Java.Lang.Object, IDownloadListener
{
public void OnDownloadStart(string url, string userAgent, string contentDisposition, string mimetype, long contentLength)
{
DownloadManager.Request request = new DownloadManager.Request(Android.Net.Uri.Parse(url));
request.AllowScanningByMediaScanner();
request.SetNotificationVisibility(DownloadVisibility.VisibleNotifyCompleted);
request.SetDestinationInExternalFilesDir(Forms.Context, Android.OS.Environment.DirectoryDownloads, "hello.jpg");
DownloadManager dm = (DownloadManager)Android.App.Application.Context.GetSystemService(Android.App.Application.DownloadService);
dm.Enqueue(request);
}
}
}