Android主线程阻塞WebView线程

我一直在做一个问题,在一个WebView的同步调用JavaScript(带有返回值),并试图缩小为什么它不工作的原因和地方。 似乎WebView线程在主线程正在等待响应的同时被阻塞,而WebView在单独的线程上运行时不应该这样。

我已经把这个小样本放在一起(我希望)相当清楚:

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 -- ie, 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); } } } 

运行时,结果如下:

 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是'main'; 16150是'JavaBridge')

正如你所看到的,主线程超时等待WebView调用setValue() ,直到latch.await()超时,主线程执行继续。

有趣的是,尝试使用较早的API级别:

 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是“主”,19543是“JavaBridge”)

事情依次正确地工作, getEval()使WebView调用setValue() ,然后在超时之前退出latch.await() (正如您所期望的/希望)。

(我也尝试了一个更早的API级别,但是根据我的理解,事情可能会崩溃,这是2.3.3中一个仿真器的bug,从来没有被修复过。)

所以我有点不知所措 在挖掘中,这似乎是正确的做法。 它看起来像是正确的方法,因为它可以在API级别14上正常工作。但是,后来的版本失败了 – 我已经在5.1和6.0上进行了testing,没有成功。

Solutions Collecting From Web of "Android主线程阻塞WebView线程"

查看更多关于使用Android 4.4迁移WebView的信息。 请参阅Android文档上的说明我认为您需要使用另一种方法来获取您的JS动作。

例如,基于该文档 – 运行JSasynchronous在当前显示的页面上下文中asynchronous评估JavaScript。 如果非null,| resultCallback | 将从该执行中返回的任何结果被调用。 此方法必须在UI线程上调用,callback将在UI线程上进行。