Android WebView offscreen/bitmap rendering issue

﹥>﹥吖頭↗ 提交于 2019-12-18 09:14:02

问题


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);

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);

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.


回答1:


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.

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;
}

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.

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.



来源:https://stackoverflow.com/questions/41273812/android-webview-offscreen-bitmap-rendering-issue

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!