2016-05-27 25 views
27

Ho lavorato su un problema con una chiamata sincrona a JavaScript in un WebView (con un valore restituito) e cercando di restringere il dove e il motivo del perché non funziona. Sembra che il thread WebView stia bloccando mentre il thread principale è in attesa di una risposta da esso, il che non dovrebbe essere il caso dal momento che il WebView viene eseguito su un thread separato.Blocco thread principale Android Thread WebView

Ho messo insieme questo piccolo esempio che dimostra che (spero) piuttosto chiaramente:

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); 
     } 
    } 
} 

Durante l'esecuzione, i seguenti risultati:

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 è "principale"; 16150 è 'JavaBridge')

Come si può vedere, i principali orari di thread di attesa per la WebView per chiamare setValue(), che non è così fino latch.await() è scaduta e l'esecuzione thread principale ha continuato.

È interessante notare che, cercando con un livello precedente 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 è 'principale'; 19543 è 'JavaBridge')

le cose funzionano correttamente in sequenza, con getEval() causando la WebView per chiamare setValue(), che esce quindi latch.await() prima che scada (come ci si aspetterebbe/spero).

(ho provato anche con un livello di API anche prima, ma le cose schianto a causa di quello che potrebbe essere, se ho capito bene, un bug di emulatore-solo in 2.3.3 che non ha mai avuto risolto.)

Quindi sono un po 'in perdita. Nel scavare intorno, questo sembra l'approccio corretto per fare le cose. Certamente sembra il come l'approccio corretto perché funziona correttamente al livello API 14. Ma poi non funziona nelle versioni successive - e ho testato su 5.1 e 6.0 senza successo.

risposta

1

Ulteriori informazioni sulla migrazione di WebView con Android 4.4. See description on Android Docs Penso che sia necessario utilizzare un altro metodo per rendere Funzionale l'azione JS.

Ad esempio, basarsi su quel documento - Running JS Async Valuta asincronicamente JavaScript nel contesto della pagina attualmente visualizzata. Se non null, | resultCallback | sarà invocato con qualsiasi risultato restituito da tale esecuzione. Questo metodo deve essere richiamato sul thread dell'interfaccia utente e il callback verrà eseguito sul thread dell'interfaccia utente.

+0

Grazie per la risposta. Lo apprezzo. Tuttavia, in particolare * non * utilizzo una callback per evaluateJavascript() perché ciò renderebbe asincrona. E mentre tutte le chiamate WebView devono essere fatte sul thread dell'interfaccia utente, come potete vedere sopra, la chiamata * back * da JS tramite setValue() è chiaramente in arrivo su un thread diverso (ad esempio, JavaBridge).Non riesco ancora a capire perché il thread dell'interfaccia utente principale blocchi il thread JavaBridge dopo che è stata effettuata la chiamata (asincrona) ValutaJavascript() o loadUrl(). Non ho ancora visto alcuna spiegazione sul motivo per cui il thread principale dell'interfaccia utente non può attendere (latch, semaforo, ecc.) Mentre JavaBridge viene eseguito. –

+0

@ KT_ Ho controllato il codice nel dispositivo Nexus-4 con Android versione 5.1.1 e il suo funzionamento. –

Problemi correlati