我一直在处理一个问题,在WebView中同步调用Javascript(具有返回值),并试图缩小在何处以及为什么它不工作。似乎是WebView线程阻塞,而主线程正在等待它的响应 – 这不应该是这种情况,因为WebView运行在一个单独的线程。

我把这个小样本放在一起(我希望):

main.xml:

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:weightSum="1">

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:id="@+id/webView"/>

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

}

}

}

运行时,得到以下结果:

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是’main’; 19543是’JavaBridge’)

事情按顺序正确地工作,用getEval()使WebView调用setValue(),然后在它超时之前退出latch.await()(正如你期望的/希望的)。

(我也试过一个更早的API级别,但事情崩溃了,由于可能是,因为我理解,一个仿真器只有Bug 2.3.3,从来没有得到修复。)

所以我有点失落。在挖掘,这似乎是做事的正确方法。它肯定看起来像正确的方法,因为它在API级别14上正常工作。但是后来的版本上失败 – 我在5.1和6.0上测试没有成功。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐