Es hat eine Weile gedauert, bis ich das herausgefunden habe, aber hier geht es. Das Problem ist/war, dass WebView.loadData() und WebView.loadURL() nicht blockierend sind - sie führen selbst keine Arbeit aus, sondern senden stattdessen eine ausführbare Datei an die aktuelle Aktivität (dh den UI-Thread), die wann ausgeführt, lädt und rendert den HTML-Code. Daher kann der Aufruf von WebView.loadData() innerhalb von Activity.onCreae(), wie ich oben getan habe, niemals funktionieren, da die eigentliche Arbeit für loadData() nicht stattfinden wird, bis onCreate() beendet wurde; daher das leere Bild.
Hier ist die vorgeschlagene Lösung, die in einer Unterklasse des Hauptanwendungsobjekts implementiert ist. Es ist ein kumulatives Ergebnis durch die Internete stapfen und versuchen, meine eigenen Ideen aus:
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;
}
wo ‚das‘ Application-Unterklasse-Objekt zurückgibt, und getPane() gibt die derzeit ausgeführt Aktivität. Beachten Sie, dass die Routine nicht im UI-Thread ausgeführt werden darf (oder wir würden auf denselben Deadlock stoßen, wie oben beschrieben). Der Haupteinblick hier ist, eine Sperre zu verwenden, um zu warten, während (1) die runOnUIThread Runnable, die den Aufruf loadData() enthält, abgeschlossen ist, und dann (2) das durch den Aufruf ladData() erzeugte Runnable auch beendet wird (das ist, wenn onNewPicture() Haken heißt). In onNewPicture() rendern wir das fertige Bild auf der Bitmap und geben dann die Sperre frei, damit die Ausführung fortgesetzt werden kann.
Ich habe festgestellt, dass diese Implementierung "zuverlässig" ist, da die meiste Zeit eine korrekt gerenderte Bitmap zurückgegeben wird. Ich verstehe immer noch nicht vollständig, welche Ereignisse von loadData/loadURL ausgelöst werden; es scheint keine Übereinstimmung darüber zu geben. In jedem Fall hat der Aufruf von signal.await() eine Zeitüberschreitung von 1 Sekunde, so dass der Vorwärtsfortschritt gewährleistet ist.