Jak uzyskać wartość zwracaną z javascript w WebView Androida?


83

Chcę uzyskać wartość zwracaną przez JavaScript w systemie Android. Mogę to zrobić na iPhonie, ale nie mogę na Androidzie. Użyłem loadUrl, ale zwrócił on void zamiast obiektu. Czy ktoś może mi pomóc?

Odpowiedzi:


79

Taka sama, Keithale krótsza odpowiedź

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

I zaimplementuj funkcję

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

Nie zapomnij usunąć onDataz proguardlisty (jeśli masz włączony proguard)


1
czy możesz rozwinąć tę proguardczęść?
Eugene

@eugene, jeśli nie wiesz, co to jest, prawdopodobnie go nie używasz. W każdym razie jest to coś, co można włączyć, co utrudnia inżynierię wsteczną aplikacji
Kirill Kulakov,

1
Dzięki. Wiem, że mam proguard-project.txt w katalogu głównym projektu, ale nic więcej. Pytam, ponieważ Twoje rozwiązanie działa, ale otrzymuję SIGSEGV. Oceniam, document.getElementById("my-div").scrollTopaby uzyskać pozycję przewijania z SwipeRefreshLayout::canChildScrollUp(). Podejrzewam, że może to być spowodowane zbyt częstym dzwonieniem w krótkim okresie. lub proguardco podejrzewam, bo po prostu nie wiem, co to jest ..
eugene

Powinna to być akceptowana odpowiedź, a nie wszystkie hacki komentowane tutaj
Romit Kumar

64

Oto hack, jak możesz to osiągnąć:

Dodaj tego klienta do swojego 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;
        }
    }

Teraz w wywołaniu javascript wykonaj:

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

Teraz w wywołaniu onJsAlert "wiadomość" będzie zawierać zwróconą wartość.


17
To niezwykle cenny hack; zwłaszcza gdy tworzysz aplikację, w której nie masz kontroli nad javascript i musisz radzić sobie z istniejącymi funkcjami. Zauważ, że możesz uzyskać ten sam wynik bez przechwytywania alertów JS za pomocą public boolean onConsoleMessage(ConsoleMessage consoleMessage)zamiast, onJsAlerta następnie w wywołaniu javascript, które robisz webView.loadUrl("javascript:console.log(functionThatReturnsSomething)");- w ten sposób pozostawiasz alerty JS w spokoju.
Mick Byrne,

Rozwiązanie WebChromeClient.onConsoleMessagewydaje się być czystsze, ale należy pamiętać, że nie działa z niektórymi urządzeniami HTC. Zobacz to pytanie, aby uzyskać więcej informacji.
Piotr

3
Czy jest jakiś powód, dla którego użycie addJavascriptInterface nie byłoby lepsze?
Keith

@Keith: Wymagałbyś obiektu, do którego javascript zapisuje wyniki, ale ponieważ nie możesz dotknąć istniejącego javascript w tym pytaniu, a istniejący js nie zapisuje do takiego obiektu, metoda interfejsu js tutaj nie pomaga ( tak to zrozumiałem).
Ray

1
@RayKoopa Niekoniecznie prawda. Chociaż możesz nie być w stanie edytować istniejącego kodu JavaScript na docelowej stronie internetowej, nadal możesz wywoływać metody na przekazanym obiekcie mWebView.addJavascriptInterface(YourObject mYourObject,String yourNameForObject);, wywołując funkcjęmWebView.loadUrl("javascript:yourNameForObject.yourSetterMethod(valueToSet)")
Chris - Jr

21

Służy addJavascriptInterface()do dodawania obiektu Java do środowiska Javascript. Niech JavaScript wywoła metodę na tym obiekcie Java, aby podać jego „wartość zwracaną”.


jak mogę to zrobić w takiej sytuacji jak stackoverflow.com/questions/5521191/ ...
vnshetty

To nie pomoże, jeśli twój js nie zapisuje w tym obiekcie i nie możesz dotknąć js.
Ray

7
@PacMani: Użyj loadUrl("javascript:...")(Poziom API 1-18) lub evaluateJavascript()(Poziom API 19+), aby ocenić własny JavaScript w kontekście aktualnie załadowanej strony internetowej.
CommonsWare

@CommonsWare: Dzięki, rozumiem;) jednak menedżer zdecydował, że js można modyfikować przez „nadużywanie alertów javascript” (eyeroll), więc udało mi się to za pomocą metody interfejsu.
Ray,

12

Oto, co dziś wymyśliłem. Jest bezpieczny dla wątków, rozsądnie wydajny i umożliwia synchroniczne wykonywanie JavaScriptu z Java dla Android WebView .

Działa na Androidzie 2.2 i nowszych. (Wymaga commons-lang, ponieważ potrzebuję fragmentów kodu przekazywanych do eval () jako ciągu JavaScript. Możesz usunąć tę zależność, zawijając kod nie w cudzysłów, ale w function(){})

Najpierw dodaj to do swojego pliku Javascript:

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

Następnie dodaj to do swojej aktywności w Androidzie:

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
Dziękuję za powyższy kod - zaadoptowałem go w moim projekcie na Androida. Jest w tym jednak pułapka, wynikająca ze złego zaprojektowania Java API. Wiersz: jsReturnValueLock.wait (waitMs - (System.currentTimeMillis () - start)); Czasami może się zdarzyć, że wartość argumentu funkcji wait () wynosi 0. A to oznacza „czekaj nieskończenie” - od czasu do czasu otrzymywałem błędy „brak odpowiedzi”. Rozwiązałem to, sprawdzając: if (elapsed> = waitMS) przerwa; i nie wywołując ponownie System.currentTimeMillis () poniżej, ale używając wartości elapsed. Najlepiej byłoby edytować i naprawić powyższy kod.
gregko

Wow, wielkie dzięki! Widziałem, że to też zawisło i nigdy nie zorientowałem się, skąd pochodzi.
Keith

1
Ale jeśli uruchomię evalJS w odbiorniku button.click itp. waitForJsReturnValue może spowodować zawieszenie się funkcji evalJs (). Więc webView.loadUrl () wewnątrz handler.post () może nie mieć szansy na wykonanie, ponieważ odbiornik button.click nie zwrócił.
victorwoo

Ale jak zawijać kod nie w cudzysłowach, ale w function(){}? Jak umieścić jsString w {}?
victorwoo

7

W przypadku API 19+ najlepszym sposobem na to jest wywołanie compareJavascript w swoim WebView:

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

Powiązana odpowiedź z bardziej szczegółowymi informacjami: https://stackoverflow.com/a/20377857


5

Rozwiązanie, które zasugerował @Felix Khazin, działa, ale brakuje jednego kluczowego punktu.

Wywołanie javascript należy wykonać po WebViewzaładowaniu strony internetowej w . Dodaj to WebViewClientdo WebView, wraz z WebChromeClient.

Pełny przykład:

@Override
public void onCreate(Bundle savedInstanceState) {
    ...
    WebView webView = (WebView) findViewById(R.id.web_view);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebViewClient(new MyWebViewClient());
    webView.setWebChromeClient(new MyWebChromeClient());
    webView.loadUrl("http://example.com");
}

private class MyWebViewClient extends WebViewClient {
    @Override
    public void onPageFinished (WebView view, String url){
        view.loadUrl("javascript:alert(functionThatReturnsSomething())");
    }
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        return false;
    }
}

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

1

Jako alternatywny wariant korzystający ze schematu niestandardowego:

Główna aktywność

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }

        final WebView webview = new CustomWebView(this);

        setContentView(webview);

        webview.loadUrl("file:///android_asset/customPage.html");

        webview.postDelayed(new Runnable() {
            @Override
            public void run() {

                //Android -> JS
                webview.loadUrl("javascript:showToast()");
            }
        }, 1000);
    }
}

CustomWebView

public class CustomWebView extends WebView {
    public CustomWebView(Context context) {
        super(context);

        setup();
    }

    @SuppressLint("SetJavaScriptEnabled")
    private void setup() {
        setWebViewClient(new AdWebViewClient());

        getSettings().setJavaScriptEnabled(true);

    }

    private class AdWebViewClient extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {

            if (url.startsWith("customschema://")) {
                //parse uri
                Toast.makeText(CustomWebView.this.getContext(), "event was received", Toast.LENGTH_SHORT).show();

                return true;
            }


            return false;
        }

    }
}

customPage.html (znajduje się w zwiniętych zasobach)

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript View</title>

    <script type="text/javascript">

        <!--JS -> Android-->
        function showToast() {
            window.location = "customschema://goto/";
        }

    </script>
</head>

<body>

</body>
</html>

0

Możesz to zrobić w ten sposób:

[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
    public WebView web_view;
    public static TextView textView;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        Xamarin.Essentials.Platform.Init(this, savedInstanceState);

        Window.AddFlags(WindowManagerFlags.Fullscreen);
        Window.ClearFlags(WindowManagerFlags.ForceNotFullscreen);

        // Set our view from the "main" layout resource
        SetContentView (Resource.Layout.main);

        web_view = FindViewById<WebView>(Resource.Id.webView);
        textView = FindViewById<TextView>(Resource.Id.textView);

        web_view.Settings.JavaScriptEnabled = true;
        web_view.SetWebViewClient(new SMOSWebViewClient());
        web_view.LoadUrl("https://stns.egyptair.com");
    }

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
    {
        Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

public class SMOSWebViewClient : WebViewClient
{
    public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
    {
        view.LoadUrl(request.Url.ToString());
        return false;
    }

    public override void OnPageFinished(WebView view, string url)
    {
        view.EvaluateJavascript("document.getElementsByClassName('notf')[0].innerHTML;", new JavascriptResult());
    }
}

public class JavascriptResult : Java.Lang.Object, IValueCallback
{
    public string Result;

    public void OnReceiveValue(Java.Lang.Object result)
    {
        string json = ((Java.Lang.String)result).ToString();
        Result = json;
        MainActivity.textView.Text = Result.Replace("\"", string.Empty);
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.