Android WebView 离屏/位图渲染问题 [英] Android WebView offscreen/bitmap rendering issue

查看:112
本文介绍了Android WebView 离屏/位图渲染问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想将 HTML 片段呈现到 WebView,然后获取它的位图.这是一个代码片段(在 MainActivity.onCreate 中):

I'd like to render an HTML fragment to a WebView and then grab a bitmap of it. Here is a code fragment (which is inside of MainActivity.onCreate):

final WebView view = new WebView(this);
String summary = "<html><body>You scored <b>192</b> points.</body></html>";
view.loadData(summary, "text/html", null);
Bitmap bitmap= Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888);
Canvas offscreen = new Canvas(bitmap);
view.draw(offscreen);

最初创建位图"时,它是透明的.在 'view.draw' 调用期间,它提供了一个全白的背景,但不呈现 HTML 摘要字符串!

When 'bitmap' is originally created, it is transparent. During the 'view.draw' call, it is provided with an all-white background, but the HTML summary string is not rendered!

通过以下方式获得完全相同的效果:

The exact same effect is obtained by the following:

final WebView view = new WebView(this);
String summary = "<html><body>You scored <b>192</b> points.</body></html>";
view.loadData(summary, "text/html", null);
Picture picture = new Picture();
Canvas picCanvas = picture.beginRecording(400, 400);
view.draw(picCanvas);
picture.endRecording();
Bitmap bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888);
Canvas offscreen = new Canvas(bitmap);
offscreen.drawPicture(picture);

在这两种情况下,底层位图都被修改为显示全白背景,但没有其他内容.任何人都可以解释为什么会发生这种情况吗?如果这很重要,我正在使用运行 Android 6.0.1 API 23 的 Sony Experia Z3.

In both cases, the underlying bitmap is modified to show an all-white background, but nothing else. Can anyone shed some light on why this is happening? I'm using a Sony Experia Z3 running Android 6.0.1 API 23, if this matters.

推荐答案

我花了一些时间才弄明白这个问题,但现在解决了.问题是/是 WebView.loadData() 和 WebView.loadURL() 是非阻塞的——它们自己不执行任何工作,而是将 Runnable 发布到当前活动(即 UI 线程),当执行,将实际加载和呈现 HTML.因此,将 WebView.loadData() 调用放在 Activity.onCreae() 内部,就像我上面所做的那样,永远无法工作,因为 loadData() 的实际工作在 onCreate() 退出之前不会发生;因此是空白图像.

It took me a while to figure this one out, but here goes. The problem is/was that WebView.loadData() and WebView.loadURL() are non-blocking -- they do not perform any work themselves, but instead post a Runnable to the current Activity (i.e., the UI thread) which, when executed, will actually load and render the HTML. So placing the WebView.loadData() call inside of Activity.onCreae(), as I did above, can never work since the actual work for loadData() will not occur until onCreate() has exited; hence the blank image.

这是建议的解决方案,在主 Application 对象的子类中实现.这是我在互联网上跋涉并尝试自己的想法的累积结果:

Here's the proposed solution, implemented in a subclass of the main Application object. It is a cumulative result of trudging through the Internets and trying out my own ideas:

public Bitmap renderHtml(final String html, int containerWidthDp, int containerHeightDp) {
    final int containerWidthPx = dpToPx(containerWidthDp);
    final int containerHeightPx = dpToPx(containerHeightDp);

    final Bitmap bitmap = Bitmap.createBitmap(containerWidthPx, containerHeightPx, Bitmap.Config.ARGB_8888);
    final CountDownLatch signal = new CountDownLatch(1);
    final AtomicBoolean ready = new AtomicBoolean(false);

    final Runnable renderer = new Runnable() {
        @Override
        public void run() {
            final WebView webView = new WebView(getPane());
            webView.setWebChromeClient(new WebChromeClient() {
                @Override
                public void onProgressChanged(WebView v, int newProgress) {
                    super.onProgressChanged(v, newProgress);
                    if (newProgress==100) {
                        ready.set(true);
                    }
                }
            });
            webView.setPictureListener(new WebView.PictureListener() {
                @Override
                public void onNewPicture(WebView v, Picture picture) {
                    if (ready.get()) {
                        final Canvas c = new Canvas(bitmap);
                        v.draw(c);
                        v.setPictureListener(null);
                        signal.countDown();
                    }
                }
            });
            webView.setWebViewClient(new WebViewClient() {
                @Override
                public void onPageFinished(WebView v, String url) {
                    super.onPageFinished(v, url);
                    ready.set(true);
                }
            });
            webView.layout(0, 0, containerWidthPx, containerHeightPx);
            /* The next statement will cause a new task to be queued up
               behind this one on the UI thread.  This new task shall
               trigger onNewPicture(), and only then shall we release
               the lock. */
            webView.loadData(html, "text/html; charset=utf-8", null);
        }
    };

    runOnUiThread(renderer);
    try {
        signal.await(1000, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
    }

    return bitmap;
}

其中'this'返回Application子类对象,getPane()返回当前执行的Activity.请注意,该例程不得在 UI 线程中执行(否则我们会遇到与上述相同的死锁).这里的主要见解是使用锁来等待 (1) 包含 loadData() 调用的 runOnUIThread Runnable 完成,然后 (2) 由 loadData() 调用产生的 Runnable 也完成(当 onNewPicture() 钩子被调用).在 onNewPicture() 中,我们将完成的图片渲染到位图上,然后释放锁以便继续执行.

where 'this' returns the Application subclass object, and getPane() returns the presently executed Activity. Note that the routine must NOT be executed in the UI thread (or we'd encounter the same deadlock as desribed above). The main insight here is to use a lock in order to wait while (1) the runOnUIThread Runnable containing the loadData() call completes, and then (2) the Runnable spawned by the loadData() call also completes (which is when the onNewPicture() hook is called). Inside onNewPicture(), we render the completed picture onto the bitmap and then release the lock so that execution can continue.

我发现这个实现是可靠的",因为大部分时间都会返回正确呈现的位图.我仍然不完全了解 loadData/loadURL 触发了哪些事件;这似乎没有任何一致性.无论如何,signal.await() 调用的超时时间为 1 秒,以便保证前进的进度.

I have found this implementation to be "reliable" in that a properly rendered bitmap is returned most of the time. I still do not completely understand which events get fired by loadData/loadURL; there does not seem be any consistency about this. At any rate, the signal.await() call has a timeout of 1 second so that forward progress is guaranteed.

这篇关于Android WebView 离屏/位图渲染问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆