2016-05-27 2 views
27

Я работаю над проблемой синхронного вызова JavaScript в WebView (с возвращаемым значением) и пытается сузить место и почему он не работает. Кажется, что поток WebView блокируется, пока основной поток ожидает ответа от него - это не должно быть так, поскольку WebView работает в отдельном потоке.Блокировка основной темы для ОС Android

Я соединил этот маленький пример, демонстрирующий это (я надеюсь) довольно ясно:

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 is '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, которая никогда не была исправлена.)

Так что я немного потерял. При копании это похоже на правильный подход к тому, чтобы делать что-то. Это, конечно, кажется как правильный подход, потому что он работает правильно на уровне API 14. Но потом он не работает в более поздних версиях - и я протестировал на 5.1 и 6.0 без успеха.

ответ

1

Подробнее о миграции WebView с Android 4.4. See description on Android Docs Думаю, вам нужно использовать другой метод для подстройки вашего JS-действия.

Например, база на этом документе - Running JS Async Асинхронно оценивает JavaScript в контексте отображаемой на данный момент страницы. Если не null, | resultCallback | будет вызываться с любым результатом, возвращенным из этого выполнения. Этот метод должен быть вызван в потоке пользовательского интерфейса, и обратный вызов будет выполняться в потоке пользовательского интерфейса.

+0

Благодарим вас за ответ. Я ценю это. Тем не менее, я специально * не *, используя обратный вызов для оценкиJavascript(), потому что это сделает его асинхронным. И хотя все вызовы WebView должны выполняться в потоке пользовательского интерфейса, как вы можете видеть выше, вызов * назад * из JS через setValue() явно выходит на другой поток (то есть JavaBridge).Я до сих пор не понимаю, почему основной поток пользовательского интерфейса блокирует поток JavaBridge, как только был вызван (async) метод оценки JavaScript() или loadUrl(). Я еще не видел объяснений, почему основной поток пользовательского интерфейса не может ждать (защелка, семафор и т. Д.), Пока выполняется JavaBridge. –

+0

@KT_ Я проверил ваш код на устройстве Nexus-4 с Android версии 5.1.1 и его работой. –

Смежные вопросы