Android WebView offscreen/bitmap rendering issue

前端 未结 1 1728
情深已故
情深已故 2020-12-21 11:12

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 We         


        
相关标签:
1条回答
  • 2020-12-21 11:33

    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.

    0 讨论(0)
提交回复
热议问题