Мне понадобилось время, чтобы понять это, но здесь идет. Проблема заключается в том, что WebView.loadData() и WebView.loadURL() не блокируются - они сами не выполняют какую-либо работу, а вместо этого отправляют Runnable в текущую активность (т.е. поток пользовательского интерфейса), который, когда выполненный, фактически загрузит и отобразит HTML. Поэтому размещение вызова WebView.loadData() внутри Activity.onCreae(), как я уже говорил выше, никогда не может работать, так как фактическая работа для loadData() не будет выполняться до выхода onCreate(); следовательно, пустое изображение.
Предлагаемое решение представлено в подклассе основного объекта 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;
}
где «это» возвращает Application объект подкласса и getPane() возвращает в настоящее время выполняется активность. Обратите внимание, что подпрограмма НЕ должна выполняться в потоке пользовательского интерфейса (или мы столкнулись с тем же тупиком, что и описано выше). Основное понимание здесь - использовать блокировку, чтобы ждать, пока (1) runOnUIThread Runnable, содержащий вызов loadData(), завершает, а затем (2) также запускает Runnable, порожденный вызовом loadData() (т.е. когда onNewPicture() называется). Внутри onNewPicture() мы делаем завершенное изображение на растровое изображение, а затем отпустите блокировку, чтобы выполнение продолжалось.
Я обнаружил, что эта реализация является «надежной» в том смысле, что правильно обработанное растровое изображение возвращается большую часть времени. Я до сих пор не совсем понимаю, какие события запускаются loadData/loadURL; похоже, в этом нет никакой согласованности. Во всяком случае, вызов signal.await() имеет тайм-аут в 1 секунду, чтобы гарантировать прогресс вперед.