2016-05-27 4 views
27

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()がタイムアウトしたとメインスレッドの実行が継続されるまで、それはしない、WebViewsetValue()を呼び出すために待っているから、メインスレッドの回を見ることができるように「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で正しく動作するためですが、それ以降のバージョンでは失敗してしまいます。

答えて

1

Android 4.4での移行WebViewの詳細をご覧ください。 See description on Android Docs私は、あなたのJSアクションを促進するために別の方法を使用する必要があると思います。

たとえば、そのドキュメントのベース - Running JS Async現在表示されているページのコンテキストでJavaScriptを非同期的に評価します。 nullでない場合、| resultCallback |その実行から返された結果とともに呼び出されます。このメソッドはUIスレッドで呼び出され、UIスレッドでコールバックが行われます。

+0

ありがとうございます。それは有り難いです。しかし、私はevaluateJavascript()に対してコールバックを使用しているわけではありません。非同期にするためです。上記のようにUIスレッドですべてのWebView呼び出しを行う必要がありますが、setValue()を介したJSからのコール* back *は明らかに別のスレッド(JavaBridge)上にあります。私はまだ(非同期の)evaluateJavascript()またはloadUrl()呼び出しが行われた後、メインのUIスレッドがJavaBridgeスレッドをブロックする理由を知りません。 JavaBridgeの実行中にメインのUIスレッドが待機(ラッチ、セマフォなど)できない理由についてはまだ説明していません。 –

+0

@KT_ Nexus-4デバイスでAndroidバージョン5.1.1とその動作を確認しました。 –

関連する問題