Android主线程阻塞WebView线程
Posted
技术标签:
【中文标题】Android主线程阻塞WebView线程【英文标题】:Android main thread blocking WebView thread 【发布时间】:2016-09-25 07:08:00 【问题描述】:我一直在解决在 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_
android:layout_
android:weightSum="1">
<WebView
android:layout_
android:layout_
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()tryreturn " + 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);
运行时,结果如下:
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 是“主”;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 中的一个仅模拟器错误,从未得到修复。)
所以我有点不知所措。在挖掘中,这似乎是做事的正确方法。它确实似乎是正确的方法,因为它在 API 级别 14 上可以正常工作。但是在以后的版本中它会失败 - 我已经在 5.1 和 6.0 上进行了测试,但没有成功。
【问题讨论】:
你让它工作了吗?就我而言,它适用于 api 23,但不适用于 api 21。我也遇到了同样的问题。 我实际上重写了它,几乎从头开始。我最终不得不改变我做事的方法,因为我遇到了问题。但我确实记得遇到过一个问题(如果我记得的话)仅在某些 API 和/或仿真器图像中表现出来。 【参考方案1】:详细了解如何使用 Android 4.4 迁移 WebView。 See description on Android Docs 我认为您需要使用另一种方法来使您的 JS 动作有趣。
例如,基于该文档 - Running JS Async 在当前显示页面的上下文中异步评估 JavaScript。如果非空,|resultCallback|将使用从该执行返回的任何结果调用。此方法必须在 UI 线程上调用,回调将在 UI 线程上进行。
【讨论】:
感谢您的回答。我很感激。但是,我特别不 对 evaluateJavascript() 使用回调,因为这会使其异步。尽管所有的 WebView 调用都必须在 UI 线程上进行,正如您在上面看到的,来自 JS 通过 setValue() 的调用 back 显然来自不同的线程(即 JavaBridge)。一旦(异步)evaluateJavascript() 或 loadUrl() 调用完成,我仍然不明白为什么主 UI 线程会阻塞 JavaBridge 线程。我还没有看到关于为什么在 JavaBridge 执行时主 UI 线程不能等待(闩锁、信号量等)的任何解释。 @KT_ 我已经检查了您在 Nexus-4 设备中的代码,Android 版本为 5.1.1 并且可以正常工作。以上是关于Android主线程阻塞WebView线程的主要内容,如果未能解决你的问题,请参考以下文章
Android中为什么主线程不会因为Looper.loop()方法造成阻塞