Wie erhalte ich einen Rückgabewert von Javascript in WebView von Android?

82

Ich möchte einen Rückgabewert von Javascript in Android erhalten. Ich kann es mit dem iPhone machen, aber ich kann nicht mit Android. Ich habe loadUrl verwendet, aber es hat void anstelle eines Objekts zurückgegeben. Kann mir jemand helfen?

Cuong Ta
quelle

Antworten:

78

Gleich wie Keithaber kürzere Antwort

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

Und implementieren Sie die Funktion

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

Vergessen Sie nicht , entfernen die onDataaus proguardListe (wenn Sie aktiviert proguard)

Kirill Kulakov
quelle
1
Können Sie das näher erläutern proguard?
Eugene
@ Eugene Wenn du nicht weißt was es ist, benutzt du es wahrscheinlich nicht.
Kirill Kulakov
1
Vielen Dank. Ich weiß, dass ich proguard-project.txt im Projektstamm habe, aber nicht mehr. Ich frage, weil Ihre Lösung funktioniert, aber ich bekomme SIGSEGV. Ich bewerte document.getElementById("my-div").scrollTop, um die Bildlaufposition zu erhalten SwipeRefreshLayout::canChildScrollUp(). Ich vermute, es könnte daran liegen, dass der Anruf in kurzer Zeit zu oft angerufen wird. oder proguardwas ich vermute, weil ich einfach nicht weiß, was es ist ..
Eugene
Dies sollte die akzeptierte Antwort sein und nicht alle hier kommentierten Hacks
Romit Kumar
64

Hier ist ein Hack, wie Sie es erreichen können:

Fügen Sie diesen Client zu Ihrer WebView hinzu:

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

Führen Sie jetzt in Ihrem Javascript-Aufruf Folgendes aus:

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

Im onJsAlert-Aufruf enthält "message" nun den zurückgegebenen Wert.

Felix Khazin
quelle
17
Dies ist ein äußerst wertvoller Hack. vor allem, wenn Sie eine App erstellen, in der Sie keine Kontrolle über das Javascript haben und mit vorhandenen Funktionen auskommen müssen. Beachten Sie, dass Sie dasselbe Ergebnis erzielen können, ohne die JS-Warnungen abzufangen, indem Sie public boolean onConsoleMessage(ConsoleMessage consoleMessage)anstelle von onJsAlertund dann in dem von Ihnen ausgeführten Javascript-Aufruf verwenden. Auf webView.loadUrl("javascript:console.log(functionThatReturnsSomething)");diese Weise lassen Sie die JS-Warnungen in Ruhe.
Mick Byrne
Die Lösung mit WebChromeClient.onConsoleMessagescheint sauberer zu sein, aber beachten Sie, dass sie mit einigen HTC-Geräten nicht funktioniert. Weitere Informationen finden Sie in dieser Frage .
Piotr
3
Ein Grund für die Verwendung von addJavascriptInterface wäre nicht vorzuziehen?
Keith
@Keith: Sie würden ein Objekt benötigen, in das Javascript schreibt, aber da Sie das vorhandene Javascript in dieser Frage nicht berühren können und das vorhandene js nicht in ein solches Objekt schreibt, hilft die js-Schnittstellenmethode hier nicht ( so habe ich es verstanden).
Ray
1
@ RayKoopa Nicht unbedingt wahr. Obwohl Sie möglicherweise nicht in der Lage sind, das vorhandene Javascript auf der Zielwebseite zu bearbeiten, können Sie dennoch Methoden für das übergebene Objekt mWebView.addJavascriptInterface(YourObject mYourObject,String yourNameForObject);aufrufen, indem Sie die FunktionmWebView.loadUrl("javascript:yourNameForObject.yourSetterMethod(valueToSet)")
Chris - Jr
20

Verwenden Sie addJavascriptInterface()diese Option, um der Javascript-Umgebung ein Java-Objekt hinzuzufügen. Lassen Sie Ihr Javascript eine Methode für dieses Java-Objekt aufrufen, um dessen "Rückgabewert" anzugeben.

CommonsWare
quelle
Wie kann ich das in Situationen wie stackoverflow.com/questions/5521191/…
vnshetty
Dies hilft nicht, wenn Ihr js nicht in dieses Objekt schreibt und Sie das js nicht berühren können.
Ray
7
@PacMani: Verwenden Sie loadUrl("javascript:...")(API Level 1-18) oder evaluateJavascript()(API Level 19+), um Ihr eigenes JavaScript im Kontext der aktuell geladenen Webseite auszuwerten.
CommonsWare
@CommonsWare: Danke, das habe ich verstanden;) Der Manager hat jedoch entschieden, dass js über "Missbrauch von Javascript-Warnungen" (Eyeroll) geändert werden kann, also habe ich es mit der Schnittstellenmethode in Gang gebracht.
Ray
12

Folgendes habe ich mir heute ausgedacht. Es ist threadsicher, relativ effizient und ermöglicht die synchrone Ausführung von Javascript von Java für ein Android WebView .

Funktioniert in Android 2.2 und höher. (Erfordert commons-lang, da meine Codefragmente als Javascript-Zeichenfolge an eval () übergeben werden müssen. Sie können diese Abhängigkeit entfernen, indem Sie den Code nicht in Anführungszeichen, sondern in Anführungszeichen setzen. function(){})

Fügen Sie dies zunächst Ihrer Javascript-Datei hinzu:

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

Fügen Sie dies dann Ihrer Android-Aktivität hinzu:

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);
    }
}
Keith
quelle
2
Vielen Dank für den obigen Code - ich habe ihn in mein Android-Projekt übernommen. Es gibt jedoch eine Falle, die auf ein schlechtes Java-API-Design zurückzuführen ist. Die Zeile: jsReturnValueLock.wait (waitMs - (System.currentTimeMillis () - start)); Der Wert des Funktionsarguments wait () wird manchmal mit 0 ausgewertet. Dies bedeutet "unendlich warten" - ich habe gelegentlich Fehler "keine Antwort" erhalten. Ich habe es gelöst, indem ich getestet habe auf: if (elapsed> = waitMS) break; und System.currentTimeMillis () unten nicht erneut aufrufen, sondern den abgelaufenen Wert verwenden. Es ist am besten, den obigen Code zu bearbeiten und zu korrigieren.
Gregko
Wow, vielen Dank! Ich sah das auch hängen und fand nie heraus, woher es kam.
Keith
1
Aber wenn ich evalJS in button.click Listener usw. ausführe. waitForJsReturnValue kann dazu führen, dass evalJs () auflegt. Daher kann webView.loadUrl () in handler.post () möglicherweise nicht ausgeführt werden, da der Listener button.click nicht zurückgegeben wurde.
Victorwoo
Aber wie kann man den Code nicht in Anführungszeichen setzen, sondern in function(){}? Wie platziere ich jsString {}?
Victorwoo
7

Auf API 19+, der beste Weg , dies zu tun ist , ruft evaluateJavascript auf WebView:

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

Verwandte Antwort mit mehr Details: https://stackoverflow.com/a/20377857

David
quelle
5

Die von @Felix Khazin vorgeschlagene Lösung funktioniert, aber es fehlt ein wichtiger Punkt.

Der Javascript-Aufruf sollte nach dem WebViewLaden der Webseite in erfolgen . Fügen Sie diese WebViewClientauf die WebView, zusammen mit der WebChromeClient.

Vollständiges Beispiel:

@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;
    }
}
Mitko Katsev
quelle
1

Als alternative Variante, die ein benutzerdefiniertes Schema verwendet:

Hauptaktivität

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 (befindet sich in den gefalteten Assets)

<!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>
yoAlex5
quelle
0

Sie können es so machen:

[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);
    }
}
Sherif Riad
quelle