WebView
(戻り値)でJavaScriptへの同期呼び出しを行い、どこでなぜ動作しないのかを絞り込もうとしています。 WebView
が別スレッドで実行されているので、メインスレッドが応答を待っている間にWebView
スレッドがブロックされているようです。Androidのメインスレッドが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 -- 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あなたはlatch.await()
がタイムアウトしたとメインスレッドの実行が継続されるまで、それはしない、WebView
はsetValue()
を呼び出すために待っているから、メインスレッドの回を見ることができるように「JavaBridge」)
です。興味深いことに
、以前のAPIレベルでしよう:(19458は、 'メイン' で、19543は、 'JavaBridge' です)
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
物事はgetEval()
がWebView
を引き起こして、順番に正しく動作setValue()
に電話をかけ、それがタイムアウトする前にlatch.await()
を終了します(期待どおり/希望)。
(私はそれを理解しているように、固定のやったことがなかった2.3.3でエミュレータのみのバグを私はまたしても、以前のAPIレベルで試してみたが、物事が原因かもしれないものに出てクラッシュ。)
だから、私は少し損失があります。周囲を掘り下げて見ると、これは正しいことであると思われます。確かにはと思われます。これはAPIレベル14で正しく動作するためですが、それ以降のバージョンでは失敗してしまいます。
ありがとうございます。それは有り難いです。しかし、私はevaluateJavascript()に対してコールバックを使用しているわけではありません。非同期にするためです。上記のようにUIスレッドですべてのWebView呼び出しを行う必要がありますが、setValue()を介したJSからのコール* back *は明らかに別のスレッド(JavaBridge)上にあります。私はまだ(非同期の)evaluateJavascript()またはloadUrl()呼び出しが行われた後、メインのUIスレッドがJavaBridgeスレッドをブロックする理由を知りません。 JavaBridgeの実行中にメインのUIスレッドが待機(ラッチ、セマフォなど)できない理由についてはまだ説明していません。 –
@KT_ Nexus-4デバイスでAndroidバージョン5.1.1とその動作を確認しました。 –