Android Wywoływanie funkcji JavaScript w WebView

249

Próbuję wywołać niektóre javascriptfunkcje znajdujące się na htmlstronie działającej w pliku android webview. Całkiem proste, co kod próbuje zrobić poniżej - z aplikacji na Androida wywołaj javascriptfunkcję z komunikatem testowym, który wywołuje z powrotem funkcję java w aplikacji na Androida, która wyświetla wiadomość testową za pośrednictwem toast.

Te javascriptspojrzenia działać jak:

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

Z WebView próbowałem wywołać javascriptnastępujące sposoby bez powodzenia:

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

I nie pozwalają javascriptnaWebView

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

I oto Javaklasa

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

Spędziłem dużo czasu, szukając w Google, co robię źle. Wszystkie znalezione przykłady wykorzystują to podejście. Czy ktoś widzi tutaj coś nie tak?

Edycja: istnieje kilka innych javascriptplików zewnętrznych , do których się odwołuje i które są używane w html, czy to może być problem?

użytkownik163757
źródło
13
Kod, którego próbujesz użyć, pomija cytaty po stronie javascript.
Paul de Vrieze
2
Pamiętaj, że od Androida 4.2 musisz używać @JavascriptInterfacedekoratora w metodach Java, które chcesz udostępnić WebView poprzez interfejs JavaScript.
Fred
1
Z kodu myWebView.loadUrl("javascript:testEcho('Hello World!')");rozumiem, że już załączyłeś plik HTML do tego widoku. Czy możesz mi powiedzieć, jak to zrobiłeś?
Tony_craft

Odpowiedzi:

195

Zrozumiałem, na czym polega problem: brakujące cudzysłowy w parametrze testEcho (). Oto jak dostałem wezwanie do pracy:

myWebView.loadUrl("javascript:testEcho('Hello World!')");
użytkownik163757
źródło
zajrzyj do tych linków stackoverflow.com/questions/9079374/…
kamal_tech_view
To miłe, ale jak przekazać obiekt Java do funkcji JavaScript jako argument?
Kovács Imre
1
@ KovácsImre String.Format ("javascript: testEcho ('% d', '% d', '% d') ', 1, 2, 3); Chyba
użytkownik457015
Dzięki, dowiedziałem się również w międzyczasie.
Kovács Imre
1
Zaczynając od KitKat, istnieje metoda oceny Javascript, sprawdź stackoverflow.com/a/32163655/3063226
Heitor,
117

Począwszy od KitKata, użyj metody replaceJavascript zamiast loadUrl, aby wywołać funkcje javascript, jak poniżej

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        webView.evaluateJavascript("enable();", null);
    } else {
        webView.loadUrl("javascript:enable();");
    }
Prasad
źródło
2
dlaczego warto korzystać z ocenyJavascript () zamiast loadUrl ()?
Kamran Bigdely
2
@kami loadUrl () ponownie załaduje stronę i ponownie wywoła funkcję onPageFinished ()
Zach.
1
@Zach Nadal jest to problem w wersji wstępnej KitKat, prawda?
Vsevolod Ganin
2
@kami Dodając do odpowiedzi Zach, EvaluJavascript (), uruchomi JavaScript asynchronicznie. Dzięki temu możemy uniknąć blokowania wątku interfejsu użytkownika za pomocą EvaluJavascript ().
Prasad
loadUrlspowodowało także, że klawiatura oprogramowania wyświetla się, gdy próbuję wpisać wartość w polu wejściowym.
Joshua
34
public void run(final String scriptSrc) { 
        webView.post(new Runnable() {
            @Override
            public void run() { 
                webView.loadUrl("javascript:" + scriptSrc); 
            }
        }); 
    }
użytkownik340994
źródło
a jeśli to nie zadziała, zrób nowy Wątek (new Runnaable () {.. <jak wyżej> ..}). start ()
Radu Simionescu,
26

Stworzyłem ładne opakowanie do wywoływania metod JavaScript; pokazuje także błędy JavaScript w logu:

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

Musisz to również dodać:

private class WebViewInterface{

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

I dodaj ten interfejs do swojego widoku internetowego:

webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebViewInterface(), "AndroidErrorReporter");
Ilya Gazman
źródło
Ta funkcja, jakkolwiek dobra jako pomysł, ma niewłaściwe wdrożenie. To wezwanie: final Object a = new Object(); callJavaScript(mBrowser, "alert", 1, true, "abc", a);podda się SyntaxErrorz Unexpected tokenco nie jest zaskakujące, kiedy obejrzysz generowanych js zadzwonić:javascript:try{alert(,,'abc',,)}catch(error){console.error(error.message);}
Lukasz Severiaan 'Grela
Nawet proste wywołanie callJavaScript(mBrowser, "alert", "abc", "def");generuje błąd, ponieważ na końcu zawsze występuje nieuczciwy przecinek. -1 ode mnie.
Łukasz „Severiaan” Grela
1
@ Lukasz'Severiaan'Grela tnx, naprawiłem to. PS Następnym razem nie bądź leniwy, możesz sam to naprawić ...;)
Ilya Gazman
16
Do wykorzystania w przyszłości: bądźmy pozytywni. Wskazanie błędu w kodzie nie jest leniwe. To wciąż pomocne informacje zwrotne.
DW
Myślę, że to nie zadziała, jeśli którykolwiek z parametrów ma znak „?” Przykład:callJavascript(mBrowser, "can't do it");
Kostanos
18

Tak, masz błąd składniowy. Jeśli chcesz uzyskać błędy JavaScript i instrukcje drukowania w logcat, musisz zaimplementować tę onConsoleMessage(ConsoleMessage cm)metodę w swoim WebChromeClient. Daje kompletne ślady stosu, takie jak konsola internetowa (element Inspect). Oto metoda.

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

Po wdrożeniu otrzymasz swoje błędy JavaScript i wypisz instrukcje ( console.log) na swoim logcat.

Dinesh
źródło
2
Uważam, że jest on w WebChromeClient, a nie WebViewClient.
Michael Levy
Tak, masz rację @MichaelLevy. Dzięki za wskazanie mi tego błędu. Teraz zredagowałem swoją odpowiedź.
Dinesh
1
Dzięki, twoja odpowiedź była właśnie tym, czego potrzebowałem. Sugerowałbym również, aby ludzie zastąpili metodę onJsAlert () w WebChromeClient. Kombinacja rejestrowania javascript i alertów może być naprawdę pomocna podczas debugowania kodu JavaScript hostowanego w widoku sieciowym.
Michael Levy,
13

Modyfikacja odpowiedzi @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);
    }

poprawnie utworzy połączenia JS np

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

Zauważ, że obiekty nie zostaną poprawnie przekazane - ale możesz je serializować przed przekazaniem jako argument.

Zmieniłem także, gdzie pojawia się błąd, przekierowałem go do dziennika konsoli, który można odsłuchać:

    webView.setWebChromeClient(new CustomWebChromeClient());

i klient

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;
    }
}
Łukasz „Severiaan” Grela
źródło
Dzięki. Czy ta metoda uniknie znaku wewnątrz dowolnej zmiennej? Innymi słowy, czy będzie działać z callJavascript(mBrowser, "can't do it");:?
Kostanos
1
Właśnie zredagowałem odpowiedź i dodałem ucieczkę do postaci
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')");
            }
        });
    }
}

plik aktywów

<!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
źródło