2010-07-21 7 views

risposta

16

Utilizzare addJavascriptInterface() per aggiungere un oggetto Java all'ambiente Javascript. Chiedi al tuo javascript di chiamare un metodo su quell'oggetto Java per fornire il suo "valore di ritorno".

+0

come posso farlo in situazioni come http: // StackOverflow.it/questions/5521191/return-a-value-from-javascript-function-in-android – vnshetty

+0

Questo non aiuta se il tuo js non scrive in questo oggetto e non puoi toccare il js. –

+6

@PacMani: utilizzare 'loadUrl (" javascript: ... ")' (livello API 1-18) o 'evaluateJavascript()' (livello API 19+) per valutare il proprio codice JavaScript nel contesto del Web attualmente caricato pagina. – CommonsWare

59

Ecco un hack su come è possibile realizzarlo:

Aggiungi questo Cliente al vostro WebView:

final class MyWebChromeClient extends WebChromeClient { 
     @Override 
     public boolean onJsAlert(WebView view, String url, String message, JsResult result) { 
      Log.d("LogTag", message); 
      result.confirm(); 
      return true; 
     } 
    } 

Ora nella chiamata javascript fare:

webView.loadUrl("javascript:alert(functionThatReturnsSomething)"); 

Ora, nel onJsAlert chiama "messaggio" conterrà il valore restituito.

+15

Questo è un hack estremamente prezioso; specialmente quando stai costruendo un'app in cui non hai il controllo su javascript e devi accontentarti delle funzioni esistenti. Nota che puoi ottenere lo stesso risultato senza intercettare gli avvisi JS usando 'booleano pubblico onConsoleMessage (consoleMessage consoleMessage)' invece di 'onJsAlert' e poi nella chiamata javascript fai' webView.loadUrl ("javascript: console.log (functionThatReturnsSomething) ");' - in questo modo lasci da solo gli avvisi JS. –

+0

La soluzione con 'WebChromeClient.onConsoleMessage' sembra essere più pulita, ma sii consapevole che non funziona con alcuni dispositivi HTC. Vedi [questa domanda] (http://stackoverflow.com/questions/4860021/android-problem-with-debugging-javascript-in-webview) per maggiori informazioni. – Piotr

+1

Qualsiasi motivo che usi addJavascriptInterface non sarebbe preferibile? – Keith

9

Ecco cosa mi è venuto in mente oggi. È thread-safe, ragionevolmente efficiente e consente l'esecuzione javascript javascript di da Java per una WebView Android.

Funziona su Android 2.2 e versioni successive. (Richiede commons-lang perché ho bisogno dei miei snippet di codice passati a eval() come stringa Javascript. Puoi rimuovere questa dipendenza avvolgendo il codice non tra virgolette, ma in function(){})

Innanzitutto, aggiungi questo al tuo Javascript File:

function evalJsForAndroid(evalJs_index, jsString) { 
    var evalJs_result = ""; 
    try { 
     evalJs_result = ""+eval(jsString); 
    } catch (e) { 
     console.log(e); 
    } 
    androidInterface.processReturnValue(evalJs_index, evalJs_result); 
} 

Quindi, aggiungere questo alla tua attività Android:

private Handler handler = new Handler(); 
private final AtomicInteger evalJsIndex = new AtomicInteger(0); 
private final Map<Integer, String> jsReturnValues = new HashMap<Integer, String>(); 
private final Object jsReturnValueLock = new Object(); 
private WebView webView; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    webView = (WebView) findViewById(R.id.webView); 
    webView.addJavascriptInterface(new MyJavascriptInterface(this), "androidInterface"); 
} 

public String evalJs(final String js) { 
    final int index = evalJsIndex.incrementAndGet(); 
    handler.post(new Runnable() { 
     public void run() { 
      webView.loadUrl("javascript:evalJsForAndroid(" + index + ", " + 
            "\"" + StringEscapeUtils.escapeEcmaScript(js) + "\")"); 
     } 
    }); 
    return waitForJsReturnValue(index, 10000); 
} 

private String waitForJsReturnValue(int index, int waitMs) { 
    long start = System.currentTimeMillis(); 

    while (true) { 
     long elapsed = System.currentTimeMillis() - start; 
     if (elapsed > waitMs) 
      break; 
     synchronized (jsReturnValueLock) { 
      String value = jsReturnValues.remove(index); 
      if (value != null) 
       return value; 

      long toWait = waitMs - (System.currentTimeMillis() - start); 
      if (toWait > 0) 
       try { 
        jsReturnValueLock.wait(toWait); 
       } catch (InterruptedException e) { 
        break; 
       } 
      else 
       break; 
     } 
    } 
    Log.e("MyActivity", "Giving up; waited " + (waitMs/1000) + "sec for return value " + index); 
    return ""; 
} 

private void processJsReturnValue(int index, String value) { 
    synchronized (jsReturnValueLock) { 
     jsReturnValues.put(index, value); 
     jsReturnValueLock.notifyAll(); 
    } 
} 

private static class MyJavascriptInterface { 
    private MyActivity activity; 

    public MyJavascriptInterface(MyActivity activity) { 
     this.activity = activity; 
    } 

    // this annotation is required in Jelly Bean and later: 
    @JavascriptInterface 
    public void processReturnValue(int index, String value) { 
     activity.processJsReturnValue(index, value); 
    } 
} 
+2

Grazie per il codice di cui sopra - l'ho adottato nel mio progetto Android. Tuttavia, vi è una trappola in esso, derivante dal cattivo design dell'API Java. La riga: jsReturnValueLock.wait (waitMs - (System.currentTimeMillis() - start)); Il valore dell'argomento della funzione wait() può talvolta accadere per valutare su 0. E questo significa "aspetta all'infinito" - ricevevo occasionalmente errori di "nessuna risposta". L'ho risolto testando per: se break (scaduto> = waitMS); e non chiamare di nuovo System.currentTimeMillis() di seguito, ma utilizzando il valore trascorso. Sarebbe meglio modificare e correggere il codice precedente. – gregko

+0

Wow, grazie mille! Lo vedevo anch'io e non ho mai capito da dove venisse. – Keith

+1

Ma se eseguo evalJS nel listener button.click, ecc. waitForJsReturnValue può causare evalJs() per riagganciare. Quindi webView.loadUrl() all'interno di handler.post() potrebbe non avere possibilità di esecuzione perché il listener button.click non è stato restituito. – victorwoo

56

Uguale Keith ma la risposta più breve

webView.addJavascriptInterface(this, "android"); 
webView.loadUrl("javascript:android.onData(functionThatReturnsSomething)"); 

E implementare la funzione

@JavascriptInterface 
public void onData(String value) { 
    //.. do something with the data 
} 

Non dimenticare di rimuovere il onData da proguard lista (Proguard se è stato attivato)

+1

puoi approfondire la parte 'proguard'? – eugene

+0

@eugene se non sai cosa sia, probabilmente non lo usi. In ogni caso è qualcosa che abilita il che rende il reverse engineering della tua app più difficile –

+1

Grazie. So che ho proguard-project.txt nella radice del progetto ma non di più. Ti sto chiedendo perché la tua soluzione funziona ma ricevo SIGSEGV. Sto valutando 'document.getElementById (" my-div "). ScrollTop' per ottenere la posizione di scorrimento da' SwipeRefreshLayout :: canChildScrollUp() '. Sospetto che potrebbe essere dovuto al fatto che la chiamata venga chiamata troppe volte nel breve periodo. o "proguard" che sospetto perché non so cosa sia .. – eugene

4

La soluzione che @Felix Khazin suggerito opere, ma c'è un manca il punto chiave.

La chiamata javascript deve essere effettuata dopo aver caricato la pagina Web nello WebView. Aggiungi questo WebViewClient allo WebView, insieme allo WebChromeClient.

completa Esempio:

3

Su API 19+, il modo migliore per farlo è quello di chiamare evaluateJavascript sul WebView:

webView.evaluateJavascript("foo.bar()", new ValueCallback<String>() { 
    @Override public void onReceiveValue(String value) { 
     // value is the result returned by the Javascript as JSON 
    } 
}); 

risposta correlati con maggiori dettagli: https://stackoverflow.com/a/20377857

Problemi correlati