Funciones de JavaScript de Android Calling en WebView

249

Estoy tratando de llamar a algunas javascriptfunciones que se encuentran en una htmlpágina que se ejecuta dentro de un android webview. Bastante simple lo que el código intenta hacer a continuación: desde la aplicación de Android, llame a una javascriptfunción con un mensaje de prueba, que a su vez llama a una función de Java en la aplicación de Android que muestra el mensaje de prueba a través de una tostada.

La javascriptfunción se ve así:

function testEcho(message){
     window.JSInterface.doEchoTest(message);
}

Desde WebView, he intentado llamar a javascriptlas siguientes formas sin suerte:

myWebView.loadUrl("javascript:testEcho(Hello World!)");
mWebView.loadUrl("javascript:(function () { " + "testEcho(Hello World!);" + "})()");

Lo habilité javascripten elWebView

myWebView.getSettings().setJavaScriptEnabled(true);
// register class containing methods to be exposed to JavaScript
myWebView.addJavascriptInterface(myJSInterface, "JSInterface"); 

Y aquí está la Javaclase

public class JSInterface{

private WebView mAppView;
public JSInterface  (WebView appView) {
        this.mAppView = appView;
    }

    public void doEchoTest(String echo){
        Toast toast = Toast.makeText(mAppView.getContext(), echo, Toast.LENGTH_SHORT);
        toast.show();
    }
}

He pasado mucho tiempo buscando en Google para ver qué puedo estar haciendo mal. Todos los ejemplos que he encontrado utilizan este enfoque. ¿Alguien ve algo mal aquí?

Editar: Hay varios otros javascriptarchivos externos a los que se hace referencia y se usan en el html, ¿podrían ser el problema?

usuario163757
fuente
13
El código que intenta utilizar no incluye comillas en el lado de JavaScript.
Paul de Vrieze
2
Tenga en cuenta que desde Android 4.2 debe usar el @JavascriptInterfacedecorador en los métodos Java que desea poner a disposición de WebView a través de la interfaz de JavaScript.
Fred
1
Según el código myWebView.loadUrl("javascript:testEcho('Hello World!')");, entiendo que ya ha adjuntado el archivo html a esa vista web. ¿Podrías decirme cómo hiciste eso?
Tony_craft

Respuestas:

195

Descubrí cuál era el problema: faltan comillas en el parámetro testEcho (). Así es como recibí la llamada para trabajar:

myWebView.loadUrl("javascript:testEcho('Hello World!')");
usuario163757
fuente
eche un vistazo en estos enlaces stackoverflow.com/questions/9079374/…
kamal_tech_view
Es agradable, pero ¿cómo se pasa un objeto Java a una función de JavaScript como argumento?
Kovács Imre
1
@ KovácsImre String.Format ("javascript: testEcho ('% d', '% d', '% d')", 1, 2, 3); Supongo
user457015
Gracias, también me he enterado mientras tanto.
Kovács Imre
1
Comenzando con KitKat, existe el método evalJavascript
Heitor el
117

Desde kitkat en adelante, use el método evalJavascript en lugar de loadUrl para llamar a las funciones de javascript como a continuación

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        webView.evaluateJavascript("enable();", null);
    } else {
        webView.loadUrl("javascript:enable();");
    }
Prasad
fuente
2
¿Por qué usar EvalJavascript () en lugar de loadUrl ()?
Kamran Bigdely
2
@kami loadUrl () volverá a cargar la página y llamará a onPageFinished () nuevamente
Zach
1
@Zach Todavía es un problema en pre-KitKat, ¿no?
Vsevolod Ganin
2
@kami Agregando a la respuesta de Zach, evalúe JavaScript (), ejecutará JavaScript de forma asincrónica. Por lo tanto, podemos evitar bloquear el subproceso de la interfaz de usuario utilizando evalJavascript ().
Prasad
loadUrltambién causó que el teclado del software apareciera cuando intento completar el valor en un campo de entrada.
Joshua
34
public void run(final String scriptSrc) { 
        webView.post(new Runnable() {
            @Override
            public void run() { 
                webView.loadUrl("javascript:" + scriptSrc); 
            }
        }); 
    }
usuario340994
fuente
y si esto no funciona, haga un nuevo Thread (nuevo Runnaable () {.. <like above> ..}). start ()
Radu Simionescu
26

Creé un buen contenedor para llamar a los métodos de JavaScript; También muestra errores de JavaScript en el registro:

private void callJavaScript(String methodName, Object...params){
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("javascript:try{");
    stringBuilder.append(methodName);
    stringBuilder.append("(");
    for (int i = 0; i < params.length; i++) {
        Object param = params[i];
        if(param instanceof String){
            stringBuilder.append("'");
            stringBuilder.append(param.toString().replace("'", "\\'"));
            stringBuilder.append("'");
        }
        if(i < params.length - 1){
            stringBuilder.append(",");
        }
    }
    stringBuilder.append(")}catch(error){Android.onError(error.message);}");
    webView.loadUrl(stringBuilder.toString());
}

Necesitas agregar esto también:

private class WebViewInterface{

    @JavascriptInterface
    public void onError(String error){
        throw new Error(error);
    }
}

Y agregue esta interfaz a su vista web:

webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebViewInterface(), "AndroidErrorReporter");
Ilya Gazman
fuente
Esta función, por buena que sea como idea, tiene una implementación incorrecta. Esta llamada: final Object a = new Object(); callJavaScript(mBrowser, "alert", 1, true, "abc", a);cederá SyntaxErrorcon lo Unexpected tokenque no es sorprendente cuando veas la llamada js generada:javascript:try{alert(,,'abc',,)}catch(error){console.error(error.message);}
Lukasz 'Severiaan' Grela
Incluso una simple llamada callJavaScript(mBrowser, "alert", "abc", "def");genera un error, ya que siempre hay una coma deshonesta al final. -1 de mi parte.
Lukasz 'Severiaan' Grela
1
@ Lukasz'Severiaan'Grela tnx, lo arreglé. PD La próxima vez no seas perezoso, puedes arreglarlo tú mismo ...;)
Ilya Gazman
16
Para referencia futura: seamos positivos. Señalar un error en el código no es vago. Sigue siendo un comentario útil.
DW
Supongo que esto no funcionará si alguno de los parámetros tiene 'carácter'. Ejemplo:callJavascript(mBrowser, "can't do it");
Kostanos
18

Sí, tienes el error de sintaxis. Si desea obtener sus errores de Javascript y las declaraciones de impresión en su logcat, debe implementar el onConsoleMessage(ConsoleMessage cm)método en su WebChromeClient. Proporciona los rastros completos de la pila como la consola web (elemento Inspeccionar). Aquí está el método.

public boolean onConsoleMessage(ConsoleMessage cm) 
    {
        Log.d("Message", cm.message() + " -- From line "
                             + cm.lineNumber() + " of "
                             + cm.sourceId() );
        return true;
    }

Después de la implementación, obtendrá sus errores de Javascript y las declaraciones de impresión ( console.log) en su logcat.

Dinesh
fuente
2
Creo que está en el WebChromeClient y no en el WebViewClient.
Michael Levy
Sí, tienes razón @MichaelLevy. Gracias por señalarme por ese error. Ahora he editado mi respuesta.
Dinesh
1
Gracias, tu respuesta fue justo lo que necesitaba. También sugeriría que las personas anulen onJsAlert () en el WebChromeClient. La combinación de registro de JavaScript y alertas puede ser realmente útil al depurar Javascript alojado en la vista web.
Michael Levy
13

Modificación de la respuesta de @Ilya_Gazman

    private void callJavaScript(WebView view, String methodName, Object...params){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("javascript:try{");
        stringBuilder.append(methodName);
        stringBuilder.append("(");
        String separator = "";
        for (Object param : params) {               
            stringBuilder.append(separator);
            separator = ",";
            if(param instanceof String){
                stringBuilder.append("'");
            }
                stringBuilder.append(param.toString().replace("'", "\\'"));
            if(param instanceof String){
                stringBuilder.append("'");
            }

        }
        stringBuilder.append(")}catch(error){console.error(error.message);}");
        final String call = stringBuilder.toString();
        Log.i(TAG, "callJavaScript: call="+call);


        view.loadUrl(call);
    }

creará correctamente llamadas JS, por ejemplo

callJavaScript(mBrowser, "alert", "abc", "def");
//javascript:try{alert('abc','def')}catch(error){console.error(error.message);}
callJavaScript(mBrowser, "alert", 1, true, "abc");
//javascript:try{alert(1,true,'abc')}catch(error){console.error(error.message);}

Tenga en cuenta que los objetos no se pasarán correctamente, pero puede serializarlos antes de pasarlos como argumento.

También he cambiado dónde va el error, lo he desviado al registro de la consola que puede ser escuchado por:

    webView.setWebChromeClient(new CustomWebChromeClient());

y cliente

class CustomWebChromeClient extends WebChromeClient {
    private static final String TAG = "CustomWebChromeClient";

    @Override
    public boolean onConsoleMessage(ConsoleMessage cm) {
        Log.d(TAG, String.format("%s @ %d: %s", cm.message(),
                cm.lineNumber(), cm.sourceId()));
        return true;
    }
}
Lukasz 'Severiaan' Grela
fuente
Gracias. ¿Este método escapará del carácter 'dentro de alguna variable? En otras palabras, ¿funcionará con callJavascript(mBrowser, "can't do it");:?
Kostanos
1
Acabo de editar la respuesta y agregué el escape a 'personaje
Kostanos
2

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

MainActivity.java


import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.bluapp.androidview.R;

public class WebViewActivity3 extends AppCompatActivity {
    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_view3);
        webView = (WebView) findViewById(R.id.webView);
        webView.setWebViewClient(new WebViewClient());
        webView.getSettings().setJavaScriptEnabled(true);
        webView.loadUrl("file:///android_asset/webview1.html");


        webView.setWebViewClient(new WebViewClient(){
            public void onPageFinished(WebView view, String weburl){
                webView.loadUrl("javascript:testEcho('Javascript function in webview')");
            }
        });
    }
}

archivo de activos

<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>WebView1</title>
<meta forua="true" http-equiv="Cache-Control" content="max-age=0"/>
</head>

<body style="background-color:#212121">
<script type="text/javascript">
function testEcho(p1){
document.write(p1);
}
</script>
</body>

</html>
dariush
fuente