如何在 Android 上使用 Xamarin WebView 下载本机浏览器等文件?

Posted

技术标签:

【中文标题】如何在 Android 上使用 Xamarin WebView 下载本机浏览器等文件?【英文标题】:How to download files like the native browser with Xamarin WebView on Android? 【发布时间】:2021-10-05 03:16:43 【问题描述】:

我想下载任何在我的应用中实现了 WebView 的文件。 在 ios 上,我注意到 WebView 大多只能在其内部显示文件。 在 android 上,WebView 以某种方式陷入循环或加载过程中。它可能会尝试下载文件,但不能。 iOS 的本机行为,因此 Safari -> 在 WebView 中显示 pdf、doc 或 html。 Android 的原生行为,即 Chrome -> 下载所有内容,然后在下载后尝试打开或下载后立即打开时尝试使用正确的应用查看它(在搭载 Android 11.0.0.153 的华为 P30 Pro 上测试)。

为什么我的 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 repo 示例: https://github.com/Nitroklas/WebViewDownloadingExample

微软论坛上的相同帖子: https://docs.microsoft.com/en-us/answers/questions/497906/how-to-download-files-like-the-native-browser-with.html

感谢您在此问题上的任何帮助。 我在这个主题上找到的所有内容都来自 2019 年或更早......

问候 尼克拉斯

【问题讨论】:

请在发帖前阅读How to Ask。您的问题应包含帖子中的所有相关信息,而不是指向外部存储库的链接。 这基本上就是重现此代码所需的所有代码。可以在 iOS 上下载和查看 pdf。 Android 上的下载仅适用于默认浏览器,但不适用于 WebView。 在 Android 上,无限循环:它是否会多次执行您的某些代码?如果不是,它会“卡”在哪一行代码上/永远不会返回? 你好@ToolmakerSteve,我正在谈论的循环是加载指示器,它向我显示 webview 的导航事件仍在运行,或者另一方面导航事件尚未触发并且从从看这个屏幕开始大约 5-10 分钟,它可能永远不会。 所以HybridWebView_Navigating 运行,包括hybridWebView.Source.SetValue(UrlWebViewSource.UrlProperty, nextURL); 行?但是HybridWebView_Navigated 永远不会运行? 【参考方案1】:

您可以尝试下面的代码,使用自定义渲染器通过 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);
    



【讨论】:

可以共享 Xamarin.iOS 的代码吗?

以上是关于如何在 Android 上使用 Xamarin WebView 下载本机浏览器等文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中使用 xamarin 在 android 上获取 PSK? [关闭]

是否可以在 Xamarin Forms ContentView 中嵌入 Android.VideoView

如何在 Android 上使用 Xamarin WebView 下载本机浏览器等文件?

如何在 Xamarin.Android 中获取指南针方向

如何在 Xamarin 中使用 SetAction

如何在 Xamarin Android 中使用 FontAwesome 图标