Android chamando funções JavaScript no WebView

249

Estou tentando chamar algumas javascriptfunções em uma htmlpágina em execução dentro de um android webview. Bem simples do que o código tenta fazer abaixo - no aplicativo Android, chame uma javascriptfunção com uma mensagem de teste, que inturn chama uma função java de volta no aplicativo Android que exibe a mensagem de teste via brinde.

A javascriptfunção se parece com:

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

No WebView, tentei chamar javascriptas seguintes maneiras sem sorte:

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

Eu habilitei javascriptnoWebView

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

E aqui está a Javaclasse

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

Passei muito tempo pesquisando o que eu estava fazendo de errado. Todos os exemplos que encontrei usam essa abordagem. Alguém vê algo errado aqui?

Edit: Existem vários outros javascriptarquivos externos sendo referenciados e usados ​​no html, eles poderiam ser o problema?

user163757
fonte
13
O código que você tenta usar não possui aspas no lado do javascript.
Paul de Vrieze
2
Observe que, desde o Android 4.2, você precisa usar o @JavascriptInterfacedecorador nos métodos Java que deseja disponibilizar para o WebView por meio da interface JavaScript.
28413 Fred Fred
1
Pelo código myWebView.loadUrl("javascript:testEcho('Hello World!')");, entendo que você já anexou o arquivo html a essa visualização na web. Você poderia me dizer como você fez isso?
Tony_craft

Respostas:

195

Eu descobri qual era o problema: falta de aspas no parâmetro testEcho (). Foi assim que recebi a ligação para trabalhar:

myWebView.loadUrl("javascript:testEcho('Hello World!')");
user163757
fonte
dê uma olhada nestes links stackoverflow.com/questions/9079374/…
kamal_tech_view
É bom, mas como você passa um objeto Java para uma função JavaScript como argumento?
Kovács Imre
1
@ KovácsImre String.Format ("javascript: testEcho ('% d', '% d', '% d')", 1, 2, 3); Eu acho que
user457015
Obrigado, eu também descobri nesse meio tempo.
Kovács Imre
1
Começando com KitKat, existe o método EvaluJavascript, verifique stackoverflow.com/a/32163655/3063226
Heitor
117

A partir do kitkat, use o método assessmentJavascript, em vez disso, loadUrl para chamar as funções javascript como abaixo

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        webView.evaluateJavascript("enable();", null);
    } else {
        webView.loadUrl("javascript:enable();");
    }
Prasad
fonte
2
por que usar EvaluJavascript () em vez de loadUrl ()?
Kamran Bigdely
2
@kami loadUrl () irá recarregar a página e chamar onPageFinished () novamente
Zach
1
@Zach Ainda é um problema no pré-KitKat, não é?
Vsevolod Ganin
2
@kami Ao adicionar à resposta Zach, EvaluJavascript (), o JavaScript será executado de forma assíncrona. Portanto, podemos evitar o bloqueio do encadeamento da interface do usuário usando EvaluJavascript ().
Prasad
loadUrltambém fez com que o teclado do software aparecesse quando tento preencher o valor em um campo de entrada.
Joshua
34
public void run(final String scriptSrc) { 
        webView.post(new Runnable() {
            @Override
            public void run() { 
                webView.loadUrl("javascript:" + scriptSrc); 
            }
        }); 
    }
user340994
fonte
. e se isso não funcionar, fazer new Thread (new Runnaable () {.. <como acima> ..}) start ()
Radu Simionescu
26

Criei um bom invólucro para chamar métodos JavaScript; também mostra erros de JavaScript no log:

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

Você precisa adicionar isso também:

private class WebViewInterface{

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

E adicione esta interface ao seu webview:

webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebViewInterface(), "AndroidErrorReporter");
Ilya Gazman
fonte
Esta função, por melhor que seja uma ideia, tem implementação incorreta. Esta chamada: final Object a = new Object(); callJavaScript(mBrowser, "alert", 1, true, "abc", a);vai ceder SyntaxErrorcom Unexpected tokeno que não é surpreendente quando você vai olhar para os js gerados chamar:javascript:try{alert(,,'abc',,)}catch(error){console.error(error.message);}
Lukasz 'Severiaan' Grela
Mesmo chamadas simples callJavaScript(mBrowser, "alert", "abc", "def");geram erros, pois sempre há vírgula desonesta no final. -1 de mim.
Lukasz 'Severiaan' Grela
1
@ Lukasz'Severiaan'Grela tnx, eu consertei isso. PS Da próxima vez, não seja preguiçoso você pode corrigi-lo sozinho ...;)
Ilya gazman
16
Para referência futura: vamos permanecer positivos. Apontar um erro no código não é preguiçoso. Ainda é um feedback útil.
DW
Eu acho que isso não vai funcionar se algum dos parâmetros tiver 'caractere nele? Exemplo:callJavascript(mBrowser, "can't do it");
Kostanos 22/01/19
18

Sim, você tem o erro de sintaxe. Se você deseja obter seus erros de Javascript e instruções de impressão em seu logcat, deve implementar o onConsoleMessage(ConsoleMessage cm)método em seu WebChromeClient. Ele fornece rastreamentos completos da pilha, como o console da Web (elemento Inspecionar). Aqui está o método

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

Após a implementação, você receberá seus erros de Javascript e imprimirá instruções ( console.log) no seu logcat.

Dinesh
fonte
2
Acredito que esteja no WebChromeClient e não no WebViewClient.
Michael Levy
Sim, você está correto @MichaelLevy. Obrigado por me indicar esse erro. Agora eu editei minha resposta.
Dinesh
1
Obrigado, sua resposta foi exatamente o que eu precisava. Eu também sugiro que as pessoas substituam onJsAlert () no WebChromeClient. A combinação de log e alertas em javascript pode ser realmente útil ao depurar o Javascript hospedado na visualização da web.
Michael Levy
13

Modificação da resposta @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);
    }

criará corretamente chamadas JS, por exemplo

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

Observe que os objetos não serão transmitidos corretamente - mas você pode serializá-los antes de passar como argumento.

Também mudei para onde o erro foi, desviei-o para o log do console, que pode ser ouvido por:

    webView.setWebChromeClient(new CustomWebChromeClient());

e 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
fonte
Obrigado. Esse método escapará do caractere 'dentro de qualquer variável? Em outras palavras, ele funcionará com callJavascript(mBrowser, "can't do it");:?
Kostanos
1
Apenas editei a resposta e adicionei a fuga ao 'personagem'
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')");
            }
        });
    }
}

arquivo de ativos

<!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
fonte