2016-12-21 1 views
2

Ich möchte ein HTML-Fragment zu einem WebView rendern und dann ein Bitmap davon greifen. Hier ist ein Code-Fragment (die innerhalb von MainActivity.onCreate ist):Android WebView Offscreen/Bitmap Rendering Problem

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

Wenn 'Bitmap' ursprünglich erstellt wird, ist es transparent. Während des Aufrufs 'view.draw' wird ein weißer Hintergrund angezeigt, die HTML-Zusammenfassung wird jedoch nicht gerendert!

Genau die gleiche Wirkung wird durch die folgenden erhalten:

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 beiden Fällen wird die zugrundeliegende Bitmap sonst einen All-weißen Hintergrund, aber nichts zu zeigen, modifiziert. Kann irgendjemand etwas darüber sagen, warum dies geschieht? Ich verwende ein Sony Experia Z3 mit Android 6.0.1 API 23, wenn dies wichtig ist.

Antwort

1

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.