Android WebView屏幕外/位图渲染问题

时间:2016-12-21 23:54:51

标签: android android-webview android-bitmap

我想将一个HTML片段呈现给WebView,然后抓取它的位图。这是一个代码片段(在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摘要字符串不会呈现!

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

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,如果这很重要的话。

1 个答案:

答案 0 :(得分:1)

我花了一段时间才想出这个,但是这里有。问题是WebView.loadData()和WebView.loadURL()是非阻塞的 - 它们本身不执行任何工作,而是将Runnable发布到当前的Activity(即UI线程),当时执行后,实际上会加载并呈现HTML。因此,将Activity.onCreae()中的WebView.loadData()调用放在Activity.onCreae()中是不可行的,因为在onCreate()退出之前,loadData()的实际工作不会发生;因此空白的图像。

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

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()中,我们将完成的图片渲染到位图上,然后释放锁定,以便继续执行。

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