【问题标题】:PDF displays empty the first time it's loaded on Android第一次在 Android 上加载 PDF 时显示为空
【发布时间】:2018-11-26 14:07:50
【问题描述】:

我正在尝试在 Xamarin.Forms 项目中的 Android 上显示 PDF,它工作正常,除了第一次加载时,10 次中只有一个空白页出现 9 次。

第一次调用的是这个函数,位于 Android 项目中:

    public string HTMLToPDF(string html, string filename) {
        //html param is a full html description of the pdf
        //filename param is something like "example.pdf"

        try {
            var dir = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + "/folder/");
            var file = new Java.IO.File(dir + "/" + filename);

            if (!dir.Exists())
                dir.Mkdirs();

            int x = 0;
            while (file.Exists())
            {
                x++;
                file = new Java.IO.File(dir + "/" + filename + "( " + x + " )");
            }

            if (webpage == null)
                webpage = new Android.Webkit.WebView(Android.App.Application.Context);
            else
                webpage.RemoveAllViews();

            int width = 2100;
            int height = 2970;

            webpage.Layout(0, 0, width, height);
            webpage.LoadData(html, "text/html", "UTF-8");
            webpage.SetWebViewClient(new WebViewCallBack(file.ToString()));

            this.Print(webpage, file.ToString(), filename);

            return file.ToString();
        }
        catch (Java.Lang.Exception e)
        {
            App._mainPage.DisplayAlert("Error", e.Message, "Ok");
        }

        return "";
    }

这是WebViewCallBack 类的样子:

    class WebViewCallBack : Android.Webkit.WebViewClient
    {
        string fileNameWithPath = null;

        public WebViewCallBack(string path)
        {
            this.fileNameWithPath = path;
        }

        public override void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            view.SetInitialScale(1);
            view.Settings.LoadWithOverviewMode = true;
            view.Settings.UseWideViewPort = true;

            PdfDocument document = new Android.Graphics.Pdf.PdfDocument();
            Android.Graphics.Pdf.PdfDocument.Page page = document.StartPage(new Android.Graphics.Pdf.PdfDocument.PageInfo.Builder(2100, 2970, 1).Create());                

            view.Draw(page.Canvas);

            document.FinishPage(page);

            Stream filestream = new MemoryStream();
            Java.IO.FileOutputStream fos = new Java.IO.FileOutputStream(fileNameWithPath, false);
            try
            {
                document.WriteTo(filestream);
                fos.Write(((MemoryStream)filestream).ToArray(), 0, (int)filestream.Length);
                fos.Close();
            }
            catch (Java.Lang.Exception e)
            {
                App._mainPage.DisplayAlert("Erreur", e.Message, "Ok");
            }
        }
    }

以及最后调用的方法Print

    public void Print(Android.Webkit.WebView webView, string filename, string onlyFileName)
    {
        try
        {
            PrintAttributes.Builder builder = new PrintAttributes.Builder();
            PrintAttributes.Margins margins = new PrintAttributes.Margins(0, 0, 0, 80);

            builder.SetMinMargins(margins);
            builder.SetMediaSize(PrintAttributes.MediaSize.IsoA4);
            builder.SetColorMode(PrintColorMode.Color);

            PrintAttributes attr = builder.Build();

            PrintManager printManager = (PrintManager)Forms.Context.GetSystemService(Android.Content.Context.PrintService);

            var printAdapter = new GenericPrintAdapter(Forms.Context, webView, filename, onlyFileName);
            printAdapter.OnEnded += PrintAdapter_OnEnded;
            printAdapter.OnError += PrintAdapter_OnError;

            printManager.Print(filename, printAdapter, attr);
        }
        catch (Java.Lang.Exception e)
        {
            App._mainPage.DisplayAlert("Erreur", e.Message, "Ok");
        }
    }

当我在WebViewCallBack 类的OnPageFinished 回调的第一行设置断点时,此时我看到了两种不同的情况:

  • PDF 界面已启动,但仍在加载 PDF。在这种情况下,一旦我再次点击“播放”,PDF 就会正常加载。
  • PDF 已经加载,作为一个空白页。这只发生在第一次尝试加载此特定 PDF 时。

因此我想我必须找到一种方法来强制加载程序等待OnPageFinished 方法首先运行?但这似乎是错误的。

我还可以补充一点,原始 HTML 包含图像,这些图像在我提供给 HTMLToPDF 的 html 字符串中都显示为 base64 字符串。我注意到如果 HTML 中没有图像,即使在第一次尝试时 PDF 也能很好地加载,所以我认为问题可能是 PDF 在第一次尝试准备好之前加载,可能是因为图像。不过我找不到解决办法。

有人可以帮我解释一下吗?

【问题讨论】:

  • 您是否尝试过在设置 WebViewClient 之后加载 HTML?
  • 不幸的是,将LoadData 放在SetWebViewClient 之后似乎没有任何区别。

标签: android pdf xamarin xamarin.forms


【解决方案1】:

所以有几种方法可以处理流程,但我会只使用事件或 Observable。

因此,让我们将您的WebViewCallBack 转换为在调用OnPageFinished 时实际执行回调的东西。这是使用System.Reactive,但您也可以使用EventHandler...

// a few class level variables
bool busy;
WebView webView;
IDisposable WhenPageIsLoadedSubscription;

public class WebViewObservable : WebViewClient
{
    Subject<string> pageLoaded = new Subject<string>();

    public IObservable<string> WhenPageIsLoaded
    {
        get { return pageLoaded.AsObservable(); }
    }

    public override void OnPageFinished(WebView view, string url)
    {
        pageLoaded.OnNext(url);
    }
}

现在定义您的打印例程(原始 OnPageFinished 和 Print 方法中的调用)。网页加载完成后会自动调用。

void PrintWebPage(WebView webView, string url)
{
    Log.Debug("SO", $"Page: {url} loaded, lets print it now" );

    // Perform the work that you used to do in OnPageFinished

    // Perform the work in your Print method

    // Now turn off a Progress indictor if desired

    busy = false;
}

使用每次加载页面时调用 PrintWebPage 操作的 observable 设置您的 WebView...

void LoadAndPrintWebPage(string html))
{
    busy = true;
    if (webView == null)
    {
        int width = 2100;
        int height = 2970;

        webView = new WebView(this);
        var client = new WebViewObservable();
        WhenPageIsLoadedSubscription = client.WhenPageIsLoaded.Subscribe((url) => { PrintWebPage(webView, url); });
        webView.SetWebViewClient(client);
        webView.Layout(0, 0, width, height);
    }
    webView.LoadData(html, "text/html", "UTF-8");
}

现在用你的html内容调用LoadWebPage,页面加载完成后会自动打印出来……

 LoadAndPrintWebPage(html);

完成后,清理你的 observable 和 webview 以避免内存泄漏...

void CleanupWebView()
{
    WhenPageIsLoadedSubscription?.Dispose();
    webView?.Dispose();
    webView = null;
}

【讨论】:

  • 我不得不使用EventHandler,因为我的项目与System.Reactive 不兼容,但我设法让它工作。非常感谢!虽然当我提供的 HTML 页面中有多张照片时它会无限加载,但你知道如何解决这个问题吗?
  • @yurden 所有这些图像都是嵌入的 base64 字符串?这些“html 字符串”有多大?
  • 是的。用户可以在使用该应用程序期间拍摄一些照片,所有这些照片都必须出现在 PDF 上。它甚至可以处理 100 张照片,重要的是它们不会被压缩到丑陋的水平。现在,如果有 3 张或更多照片,它会无限加载。我现在无法计算其中一个 base64 字符串的字符,但它们是数千个字符。长。在平板电脑上以全屏模式打开后,该模式会显示一个非常小的滚动条,仅供参考。
  • @yurden 每个 PDF 页面上有多少张图片?每个的图像分辨率是多少?我认为这是内存问题和 CPU 解码速度问题。如果将 html 字符串保存到文件中,如果您使用该本地文件 url 启动 Chrome(通过意图)会发生什么?在测试并失败(?)之后,我将通过将图像保存到单个文件并仅将包含 img 元素的 html 加载到每个图像并查看当您通过意图在 Chrome 中加载然后返回时会发生什么情况到您的 WebView 打印例程。
  • 否则一页一页地构建html页面,打印将每一页分别归档到一个单独的文件中,最后拼接成一个pdf。
【解决方案2】:

空白页也有类似的问题。但我使用谷歌文档来显示它。 http://docs.google.com/gview?embedded=true&amp;url=

我只能通过检查OnPageStartedOnPageFinished 之间的操作时间来解决我的问题。如果花了很短的时间,那么就会出现问题并且页面是空白的。

这是我的 webClient。

每次页面空白时,我都会重新加载它。还要添加简单的断路器以防万一

    public class MyWebViewClient : WebViewClient
    {
        private readonly WebView _webView;
        private DateTime _dateTime = DateTime.Now;
        private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);
        private int _breakerHits;
        private const int _BreakerCount = 2;

        public MyWebViewClient(WebView webView)
        {
            _webView = webView;
        }

        public override async void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
        {
            await _semaphoreSlim.WaitAsync();
            _dateTime = DateTime.Now;
            base.OnPageStarted(view, url, favicon);
            _webView.SendNavigating(new WebNavigatingEventArgs(WebNavigationEvent.NewPage, null, url));
            _semaphoreSlim.Release();
        }

        public override async void OnPageFinished(Android.Webkit.WebView view, string url)
        {
            await _semaphoreSlim.WaitAsync();
            base.OnPageFinished(view, url);
            if (url.Contains(".pdf"))
            {
                var diff = DateTime.Now - _dateTime;
                if (diff > TimeSpan.FromMilliseconds(700) || _breakerHits > _BreakerCount)
                {
                    _breakerHits = 0;
                    _webView.SendNavigated(new WebNavigatedEventArgs(WebNavigationEvent.NewPage, null, url,
                        WebNavigationResult.Success));
                }
                else
                {
                    _breakerHits++;
                    view.Reload();
                }

            }
            else
            {
                _webView.SendNavigated(new WebNavigatedEventArgs(WebNavigationEvent.NewPage, null, url,
                    WebNavigationResult.Success));
            }

            _semaphoreSlim.Release();

        }
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多