2010-07-21 15 views

Respuesta

16

Use addJavascriptInterface() para agregar un objeto de Java al entorno de Javascript. Haga que su Javascript invoque un método en ese objeto Java para proporcionar su "valor de retorno".

+0

¿Cómo puedo hacer eso en una situación como http: // stackoverflow.com/questions/5521191/returning-a-value-from-javascript-function-in-android – vnshetty

+0

Esto no ayuda si su js no escribe en este objeto y no puede tocar el js. –

+6

@PacMani: Use 'loadUrl (" javascript: ... ")' (Nivel de API 1-18) o 'evalúeJavascript()' (Nivel de API 19+) para evaluar su propio JavaScript en el contexto de la Web actualmente cargada página. – CommonsWare

59

Aquí es un truco de cómo se puede lograr que:

Añadir este cliente para su 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; 
     } 
    } 

Ahora en su llamada Javascript a hacer:

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

Ahora en el onJsAlert llamada "mensaje" contendrá el valor devuelto.

+15

Este es un truco extremadamente valioso; especialmente cuando estás construyendo una aplicación donde no tienes control sobre el javascript y tienes que conformarte con las funciones existentes. Tenga en cuenta que puede obtener el mismo resultado sin interceptar las alertas JS usando 'boolean public onConsoleMessage (ConsoleMessage consoleMessage)' en lugar de 'onJsAlert' y luego en la llamada javascript' 'webView.loadUrl (" javascript: console.log (functionThatReturnsSomething) ");' - de esa manera dejas las alertas JS en paz. –

+0

La solución con 'WebChromeClient.onConsoleMessage' parece ser más limpia, pero tenga en cuenta que no funciona con algunos dispositivos HTC. Consulte [esta pregunta] (http://stackoverflow.com/questions/4860021/android-problem-with-debugging-javascript-in-webview) para obtener más información. – Piotr

+1

¿Alguna razón para usar addJavascriptInterface no sería preferible? – Keith

9

Esto es lo que se me ocurrió hoy. Es seguro para subprocesos, razonablemente eficiente y permite la ejecución síncrona de JavaScript desde Java para Android WebView.

Funciona en Android 2.2 en adelante. (Requiere commons-lang porque necesito que mis fragmentos de código pasen a eval() como una cadena de Javascript. Puede eliminar esta dependencia envolviendo el código no entre comillas, sino en function(){})

Primero, agregue esto a su Javascript archivo:

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

a continuación, añadir esto a su actividad 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

Gracias por el código anterior: lo adopté en mi proyecto de Android. Sin embargo, hay una trampa en ella, como resultado del mal diseño de la API de Java. La línea: jsReturnValueLock.wait (waitMs - (System.currentTimeMillis() - start)); El valor del argumento de la función wait() a veces puede pasar a evaluar a 0. Y esto significa "esperar infinitamente" - Recibía ocasionalmente errores de "no respuesta". Lo resolví probando: if (transcurrido> = waitMS) break; y no llama de nuevo System.currentTimeMillis() a continuación, pero utilizando el valor transcurrido. Lo mejor sería editar y corregir el código anterior. – gregko

+0

¡Muchas gracias! Estaba viendo eso también y nunca imaginé de dónde venía. – Keith

+1

Pero si ejecuto evalJS en button.click listener, etc ,. waitForJsReturnValue puede hacer que evalJs() cuelgue. Así que webView.loadUrl() dentro de handler.post() puede no tener posibilidad de ejecución porque no se devolvió el oyente button.click. – victorwoo

56

Igual que Keith pero la respuesta más corta

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

y poner en práctica la función

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

No se olvide de quitar el onData de proguard lista (Proguard si ha habilitado)

+1

¿Puede explicar la parte 'proguard'? – eugene

+0

@eugene si no sabes de qué se trata, probablemente no lo uses. De todos modos, es algo para habilitar lo que hace que la ingeniería inversa sea más difícil para tu aplicación –

+1

Gracias. Sé que tengo proguard-project.txt en la raíz del proyecto, pero no más que eso. Te pregunto porque tu solución funciona pero obtengo SIGSEGV. Estoy evaluando 'document.getElementById (" my-div "). ScrollTop' para obtener la posición de desplazamiento de' SwipeRefreshLayout :: canChildScrollUp() '. Sospecho que podría ser debido a que la llamada se llama demasiadas veces en un período corto. o 'proguard' que sospecho porque simplemente no sé lo que es .. – eugene

4

La solución que @Felix Khazin sugirió obras, pero hay una punto clave que falta.

La llamada javascript debe realizarse después de que se cargue la página web en el WebView. Agregue WebViewClient al WebView, junto con el WebChromeClient.

Ejemplo completo:

3

El API 19+, la mejor manera de hacer esto es llamar evaluateJavascript en su WebView:

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

respuesta Relacionado con más detalle: https://stackoverflow.com/a/20377857

Cuestiones relacionadas