Android-Hauptthread blockiert WebView-Thread

Ich habe an einem Problem mit einem synchronen Aufruf von JavaScript in einem @ gearbeiteWebView (mit einem Rückgabewert) und der Versuch, das Wo und Warum einzugrenzen, warum es nicht funktioniert. Es scheint, dass dasWebViewer @ -Thread blockiert, während der Hauptthread auf eine Antwort von ihm wartet - was nicht der Fall sein sollte, da der @ -ThreWebView läuft auf einem separaten Thread.

Ich habe dieses kleine Beispiel zusammengestellt, das es ziemlich deutlich zeigt (hoffe ich):

main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:weightSum="1">

    <WebView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:id="@+id/webView"/>
</LinearLayout>

MyActivity.java:

package com.example.myapp;

import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.JavascriptInterface;
import android.webkit.WebViewClient;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class MyActivity extends Activity {

    public final static String TAG = "MyActivity";

    private WebView webView;
    private JSInterface JS;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        webView = (WebView)findViewById(R.id.webView);
        JS = new JSInterface();

        webView.addJavascriptInterface(JS, JS.getInterfaceName());

        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);

        webView.setWebViewClient(new WebViewClient() {
             public void onPageFinished(WebView view, String url) {
                 Log.d(TAG, JS.getEval("test()"));
             }
         });

        webView.loadData("<script>function test() {JSInterface.log(\"returning Success\"); return 'Success';}</script>Test", "text/html", "UTF-8");
    }


    private class JSInterface {

        private static final String TAG = "JSInterface";

        private final String interfaceName = "JSInterface";
        private CountDownLatch latch;
        private String returnValue;

        public JSInterface() {
        }

        public String getInterfaceName() {
            return interfaceName;
        }

        // JS-side functions can call JSInterface.log() to log to logcat

        @JavascriptInterface
        public void log(String str) {
            // log() gets called from Javascript
            Log.i(TAG, str);
        }

        // JS-side functions will indirectly call setValue() via getEval()'s try block, below

        @JavascriptInterface
        public void setValue(String value) {
            // setValue() receives the value from Javascript
            Log.d(TAG, "setValue(): " + value);
            returnValue = value;
            latch.countDown();
        }

        // getEval() is for when you need to evaluate JS code and get the return value back

        public String getEval(String js) {
            Log.d(TAG, "getEval(): " + js);
            returnValue = null;
            latch = new CountDownLatch(1);
            final String code = interfaceName
                    + ".setValue(function(){try{return " + js
                    + "+\"\";}catch(js_eval_err){return '';}}());";
            Log.d(TAG, "getEval(): " + code);

            // It doesn't actually matter which one we use; neither works:
            if (Build.VERSION.SDK_INT >= 19)
                webView.evaluateJavascript(code, null);
            else
                webView.loadUrl("javascript:" + code);

            // The problem is that latch.await() appears to block, not allowing the JavaBridge
            // thread to run -- i.e., to call setValue() and therefore latch.countDown() --
            // so latch.await() always runs until it times out and getEval() returns ""

            try {
                // Set a 4 second timeout for the worst/longest possible case
                latch.await(4, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Log.e(TAG, "InterruptedException");
            }
            if (returnValue == null) {
                Log.i(TAG, "getEval(): Timed out waiting for response");
                returnValue = "";
            }
            Log.d(TAG, "getEval() = " + returnValue);
            return returnValue;
        }

        // eval() is for when you need to run some JS code and don't care about any return value

        public void eval(String js) {
            // No return value
            Log.d(TAG, "eval(): " + js);
            if (Build.VERSION.SDK_INT >= 19)
                webView.evaluateJavascript(js, null);
            else
                webView.loadUrl("javascript:" + js);
        }
    }
}

Wenn ausgeführt, werden die folgenden Ergebnisse angezeigt:

Emulator Nexus 5 API 23:

05-25 13:34:46.222 16073-16073/com.example.myapp D/JSInterface: getEval(): test()
05-25 13:34:50.224 16073-16073/com.example.myapp I/JSInterface: getEval(): Timed out waiting for response
05-25 13:34:50.224 16073-16073/com.example.myapp D/JSInterface: getEval() = 
05-25 13:34:50.225 16073-16073/com.example.myapp I/Choreographer: Skipped 239 frames!  The application may be doing too much work on its main thread.
05-25 13:34:50.235 16073-16150/com.example.myapp I/JSInterface: returning Success
05-25 13:34:50.237 16073-16150/com.example.myapp D/JSInterface: setValue(): Success

(16073 ist 'main'; 16150 ist 'JavaBridge')

ie Sie sehen können, wartet der Haupt-Thread nicht mehr auf dasWebView anrufensetValue(), was es nicht tut, bislatch.await() ist abgelaufen und die Ausführung des Hauptthreads wurde fortgesetzt.

nteressanterweise versuchen Sie es mit einer früheren API-Version:

Emulator Nexus S API 14:

05-25 13:37:15.225 19458-19458/com.example.myapp D/JSInterface: getEval(): test()
05-25 13:37:15.235 19458-19543/com.example.myapp I/JSInterface: returning Success
05-25 13:37:15.235 19458-19543/com.example.myapp D/JSInterface: setValue(): Success
05-25 13:37:15.235 19458-19458/com.example.myapp D/JSInterface: getEval() = Success
05-25 13:37:15.235 19458-19458/com.example.myapp D/MyActivity: Success

(19458 ist 'main'; 19543 ist 'JavaBridge')

Things funktionieren in der richtigen Reihenfolge, mitgetEval() verursacht dasWebView anrufensetValue(), welches dann @ verläslatch.await() bevor es abläuft (wie Sie es erwarten / hoffen würden).

(Ich habe es auch mit einer noch früheren API-Version versucht, aber die Dinge stürzen ab, da es sich nach meinem Verständnis möglicherweise um einen reinen Emulator-Fehler in 2.3.3 handelt, der nie behoben wurde.)

So bin ich ein bisschen ratlos. Beim Herumgraben scheint dies der richtige Ansatz zu sein, um Dinge zu tun. Sicherlich scheint wie der richtige Ansatz, da er auf API-Ebene 14 ordnungsgemäß funktioniert. In späteren Versionen schlägt er jedoch fehl - und ich habe ihn auf 5.1 und 6.0 ohne Erfolg getestet.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage