如何在 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);
    }
}

}