简体   繁体   English

Xamarin Android WebView 下载内联pdf内容

[英]Xamarin Android WebView download inline pdf content

Android experts ... I'm in a quandry. Android 专家......我陷入了困境。 I have a WebView embedded in a Forms app which also has a custom renderer (for the WebView) implemented for it.我在 Forms 应用程序中嵌入了一个 WebView,该应用程序还为它实现了一个自定义渲染器(用于 WebView)。

I'm wrapping a basic web app (SAP Fiori) and within that app, it has a salary statement section where a user can view their pay slip.我正在封装一个基本的 Web 应用程序 (SAP Fiori),在该应用程序中,它有一个工资单部分,用户可以在其中查看他们的工资单。 On that page, there is a button to download the pay slip to a PDF.在该页面上,有一个按钮可将工资单下载为 PDF。

In iOS within my app, this all works as standard, ie the PDF opens and is viewed in the browser.在我的应用程序中的 iOS 中,这一切都按标准运行,即 PDF 打开并在浏览器中查看。 On both Android and iOS, any browser (like Chrome or Safari) all works fine as well.在 Android 和 iOS 上,任何浏览器(如 Chrome 或 Safari)都可以正常工作。

In my Android app, clicking on the "Open as PDF" button produces no outcome, nothing on the front end ever happens but within the app itself, the implemented DownloadListener method is triggered ...在我的 Android 应用程序中,单击“以 PDF 格式打开”按钮不会产生任何结果,前端不会发生任何事情,但在应用程序本身内,已实现的 DownloadListener 方法被触发......

protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
    {
        base.OnElementChanged(e);

        Control.Settings.AllowUniversalAccessFromFileURLs = true;

        Control.SetWebViewClient(new MySAPWebViewClient(Context));
        Control.SetDownloadListener(new MySAPDownloadListener());
    }

public class MySAPDownloadListener : Java.Lang.Object, IDownloadListener
{
    public new void Dispose() { }

    public void OnDownloadStart(string url, string userAgent, string contentDisposition, string mimetype, long contentLength)
    {
        System.Diagnostics.Debug.WriteLine("Url = " + url);
        System.Diagnostics.Debug.WriteLine("Content Disposition = " + contentDisposition);
        System.Diagnostics.Debug.WriteLine("Mime Type = " + mimetype);
    }
}

These are the main DownloadEventArgs values ...这些是主要的 DownloadEventArgs 值...

url = "https://organisation.com/sap/opu/odata/sap/HCM_MY_PAYSTUBS_SRV/PDFPaystubs(SEQUENCENUMBER=189,PersonnelAssignment='00044411')/$value"
contentDisposition = "inline; filename=Paystub_01.31.2018.pdf"
mimetype = "application/pdf"

... now, behind that url, there is a piece of javascript that generates the dynamic PDF so it's not stored anywhere on the server and this is where I seem to be having an issue. ...现在,在该 url 后面,有一段 javascript 生成动态 PDF,因此它不会存储在服务器上的任何位置,这就是我似乎遇到问题的地方。

Everything I read online says to use the HttpClient to download the contents of the PDF to file and then open it.我在网上阅读的所有内容都说使用 HttpClient 将 PDF 的内容下载到文件中,然后打开它。 However, if I pass in that URL to the HttpClient, I don't get the PDF data, I get the HTML text.但是,如果我将该 URL 传递给 HttpClient,则不会获取 PDF 数据,而是获取 HTML 文本。

I've tried GoogleDocs and numerous other links here on StackOverflow but no matter what I do, that URL returns the HTML text, not the contents of the PDF document.我已经在 StackOverflow 上尝试了 GoogleDocs 和许多其他链接,但无论我做什么,该 URL 都会返回 HTML 文本,而不是 PDF 文档的内容。

I have very little control over what SAP is giving me as well so changing the server side is an unlikely option.我对 SAP 给我的控制也很少,所以改变服务器端是一个不太可能的选择。

Any help would be much appreciated.任何帮助将不胜感激。

Unfortunately, for some reason Android WebView cannot display PDF files.不幸的是,由于某种原因,Android WebView 无法显示 PDF 文件。 So, if using WebView is crucial for you, where is only one option - use Google Docs Viewer.因此,如果使用 WebView 对您来说至关重要,那么只有一种选择 - 使用 Google Docs Viewer。 But it will works only with PDFs available via public URLs.但它仅适用于通过公共 URL 提供的 PDF。 It will looks something like that:它看起来像这样:

 WebView view=(WebView)findViewById(R.id.webView);
 view.setWebViewClient(new WebViewClient());
 String url=getIntent().toString("value1");
 String URL="https://docs.google.com/viewer?url="+url; 
 view.loadUrl(Url);

If using WebView is not so crucial, you can use:如果使用 WebView 不是那么重要,您可以使用:

  1. Some third-party library to display PDF一些第三方库来显示PDF
  2. Call an Intent to display PDF via another app通过其他应用调用 Intent 以显示 PDF
  3. If you usin API level 21 and above - you can get your hands dirty with Android PdfRenderer如果您使用 API 级别 21 及更高级别 - 您可以使用 Android PdfRenderer

Hope it help希望有帮助

You can do this by two solutions :您可以通过两种解决方案来做到这一点:

Solution 1: Open URL in System Default Browser.解决方案 1:在系统默认浏览器中打开 URL。

private async Task MWebview_Download(object sender, DownloadEventArgs e)
{
    await Launcher.OpenAsync(e.Url);               
}

Solution 2 Downloading using HttpClient.解决方案2使用HttpClient 下载。

    private async void MWebview_Download(object sender, DownloadEventArgs e)
    {
        string fileName = GetFileName(e);

        Device.BeginInvokeOnMainThread(() =>
        {
            Toast.MakeText(MainActivity.Instance, "Downloading File...", ToastLength.Long).Show();
        });


        await DownloadPdfFile(e, fileName);
    }

    private async Task DownloadPdfFile(DownloadEventArgs e, string fileName)
    {
        try
        {
            //Bypass the certificate 
            HttpClientHandler clientHandler = new HttpClientHandler();
            clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };

            // Getting Cookie
            String cookies = CookieManager.Instance.GetCookie(e.Url);

            var client = new HttpClient(clientHandler);
            var httpRequestMessage = new HttpRequestMessage
            {
                Method = HttpMethod.Get,
                RequestUri = new Uri(e.Url),
                Headers = {
                { HttpRequestHeader.TransferEncoding.ToString(), "binary" },
                { HttpRequestHeader.ContentLength.ToString(), e.ContentLength+"" },
                { HttpRequestHeader.ContentType.ToString(), "application/pdf" },
                {HttpRequestHeader.Cookie.ToString(),cookies },
            },
            };

            // Api Call
            var response = await client.SendAsync(httpRequestMessage);

            // Read content as Byte[]
            var bytes = response.Content.ReadAsByteArrayAsync().Result;

            // Save to Local DB
            var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
            var filePath = Path.Combine(documentsPath, fileName);

            // convert byte to stream
            MemoryStream stream = new MemoryStream(bytes);

            // Write file
            SaveAndView(fileName, "application/pdf", stream);

        }
        catch (Exception ee)
        {
            Toast.MakeText(MainActivity.Instance, "Failed !!", ToastLength.Long).Show();
        }
    }

    public void SaveAndView(string fileName, String contentType, MemoryStream stream)
    {
        string exception = string.Empty;
        string root = null;

        if (ContextCompat.CheckSelfPermission(MainActivity.Instance, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
        {
            ActivityCompat.RequestPermissions((Activity)MainActivity.Instance, new String[] { Manifest.Permission.WriteExternalStorage }, 1);
        }

        if (Android.OS.Environment.IsExternalStorageEmulated)
        {
            root = Android.OS.Environment.ExternalStorageDirectory.ToString();
        }
        else
            root = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);

        Java.IO.File myDir = new Java.IO.File(root + "/PDFFiles");
        myDir.Mkdir();

        Java.IO.File file = new Java.IO.File(myDir, fileName);

        if (file.Exists()) file.Delete();

        try
        {
            FileOutputStream outs = new FileOutputStream(file);
            outs.Write(stream.ToArray());

            outs.Flush();
            outs.Close();
        }
        catch (Exception e)
        {
            exception = e.ToString();
        }

        if (file.Exists() && contentType != "application/html")
        {
            string extension = MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString());
            string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
            Intent intent = new Intent(Intent.ActionView);
            intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.NewTask);
            Android.Net.Uri path = FileProvider.GetUriForFile(MainActivity.Instance, Android.App.Application.Context.PackageName + ".provider", file);
            intent.SetDataAndType(path, mimeType);
            intent.AddFlags(ActivityFlags.GrantReadUriPermission);

            MainActivity.Instance.StartActivity(Intent.CreateChooser(intent, "Choose App"));
        }

    }

Note : Make sure to handle FileProvider :注意:确保处理 FileProvider :

 **Changes in Manifest file:** 

   - Add android:requestLegacyExternalStorage="true" in application tag
   
   - Add between application tag

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false" android:grantUriPermissions="true">
          <meta-data
             android:name="android.support.FILE_PROVIDER_PATHS"
             android:resource="@xml/file_paths" />
    </provider>

Add file_paths.xml in xml folder inside Resources folder in Xamarin.Android Project:在 Xamarin.Android 项目的 Resources 文件夹内的 xml 文件夹中添加 file_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
   <external-files-path name="my_images" path="Pictures" />
   <external-files-path name="my_movies" path="Movies" />
   <external-path name="external_files" path="."/>
</paths>     

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM