Як отримати повернене значення з javascript у WebView Android?


83

Я хочу отримати повернене значення з Javascript в Android. Я можу це зробити з iPhone, але не можу з Android. Я використовував loadUrl, але він повернув void замість об'єкта. Хто-небудь може мені допомогти?

Відповіді:


79

Так само, як Keithі коротша відповідь

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

І реалізуйте функцію

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

Не забудьте видалити список onDataзі proguardсписку (якщо ви ввімкнули proguard)


1
Ви можете детальніше розказати про цю proguardчастину?
Євген

@eugene, якщо ти не знаєш, що це, ти, мабуть, не використовуєш його. У будь-якому випадку це щось увімкнути, що ускладнює зворотне проектування вашого додатка
Кирило Кулаков

1
Дякую. Я знаю, що у мене є файл proguard-project.txt у корені проекту, але не більше того. Я запитую, тому що ваше рішення працює, але я отримую SIGSEGV. Я оцінюю, document.getElementById("my-div").scrollTopщоб отримати позицію прокрутки від SwipeRefreshLayout::canChildScrollUp(). Я підозрюю, що це могло бути пов’язано із тим, що дзвінок надто багато разів за короткий період. або в proguardчому я підозрюю, бо просто не знаю, що це ..
Євген

Це повинна бути прийнята відповідь, а не всі коментарі, прокоментовані тут
Роміт Кумар

64

Ось хак про те, як ви можете це зробити:

Додайте цього клієнта у свій 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;
        }
    }

Тепер у своєму дзвінку JavaScript виконайте:

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

Тепер у виклику onJsAlert "повідомлення" міститиме повернене значення.


17
Це надзвичайно цінний хак; особливо коли ви створюєте додаток, де ви не маєте контролю над javascript і мусите полагодити існуючі функції. Зверніть увагу, що ви можете отримати той самий результат, не перехоплюючи оповіщення JS, використовуючи public boolean onConsoleMessage(ConsoleMessage consoleMessage)замість onJsAlertі потім у дзвінку javascript, який ви робите webView.loadUrl("javascript:console.log(functionThatReturnsSomething)");- таким чином ви залишаєте сповіщення JS у спокої.
Mick Byrne

Рішення з, WebChromeClient.onConsoleMessageздається, чистіше, але майте на увазі, що воно не працює з деякими пристроями HTC. Докладніше див. У цьому питанні .
Piotr

3
Будь-яка причина використання addJavascriptInterface не буде кращою?
Кіт

@Keith: Вам знадобиться об'єкт, в який javascript записує результати, але оскільки ви не можете торкнутися існуючого javascript у цьому питанні, а існуючий js не пише в такий об'єкт, метод інтерфейсу js тут не допомагає ( так я це зрозумів).
Рей

1
@RayKoopa Не обов'язково правда. Незважаючи на те, що ви не зможете відредагувати існуючий javascript на цільовій веб-сторінці, ви все одно можете викликати методи на об'єкт, переданий mWebView.addJavascriptInterface(YourObject mYourObject,String yourNameForObject);за допомогою виклику функціїmWebView.loadUrl("javascript:yourNameForObject.yourSetterMethod(valueToSet)")
Кріс - молодший

21

Використовуйте addJavascriptInterface()для додавання об’єкта Java до середовища Javascript. Нехай ваш Javascript викликає метод для цього об'єкта Java, щоб надати його "повернене значення".


як я можу це зробити в ситуації , як stackoverflow.com/questions/5521191 / ...
vnshetty

Це не допомагає, якщо ваш js не пише в цей об’єкт і ви не можете торкнутися js.
Рей

7
@PacMani: Використовуйте loadUrl("javascript:...")(API рівень 1-18) або evaluateJavascript()(API рівень 19+), щоб оцінити ваш власний JavaScript у контексті завантаженої веб-сторінки.
CommonsWare

@CommonsWare: Дякую, я зрозумів;) однак менеджер вирішив, що js можна змінити за допомогою "зловживання сповіщеннями javascript" (очей), тож я почав використовувати метод інтерфейсу.
Рей

12

Ось що я сьогодні придумав. Він безпечний для потоків, досить ефективний і дозволяє синхронно виконувати Javascript з Java для Android WebView .

Працює в Android 2.2 та новіших версіях. (Потрібен commons-lang, оскільки мені потрібні мої фрагменти коду, передані eval () як рядок Javascript. Ви можете видалити цю залежність, обернувши код не в лапки, а вfunction(){} )

Спочатку додайте це у свій файл 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);
}

Потім додайте це до своєї активності на Android:

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
Дякую за наведений вище код - я прийняв його у своєму проекті Android. Однак у цьому є пастка, яка виникла через поганий дизайн Java API. Рядок: jsReturnValueLock.wait (waitMs - (System.currentTimeMillis () - start)); Значення аргументу функції wait () іноді може траплятись як 0. І це означає "нескінченно чекати" - я часом отримував помилки "без відповіді". Я вирішив це тестуванням на наявність: if (минуло> = waitMS) перерва; і більше не викликати System.currentTimeMillis () нижче, а використовуючи минуле значення. Краще було б відредагувати та виправити наведений вище код.
gregko

Ого дуже дякую! Я теж бачив, що висить, і ніколи не зрозумів, звідки він береться.
Кіт,

1
Але якщо я запустив evalJS у button.click listener тощо,. waitForJsReturnValue може спричинити зависання функції evalJs (). Тож webView.loadUrl () всередині handler.post () може не мати можливості виконати, оскільки прослуховувач button.click не повернувся.
victorwoo

Але як обернути код не в лапки, а в function(){}? Як розмістити jsString {}?
victorwoo

7

В API 19+ найкращий спосіб зробити це - викликати evaluJavascript на вашому WebView:

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

Відповідна відповідь з більш детальною інформацією: https://stackoverflow.com/a/20377857


5

Рішення, яке запропонував @Felix Khazin, працює, але бракує одного ключового моменту.

Виклик javascript слід робити після завантаження веб-сторінки в WebView. Додайте це WebViewClientдо WebView, разом із WebChromeClient.

Повний приклад:

@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

Як альтернативний варіант, який використовує власну схему:

MainActivity

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 (знаходиться в складених активах)

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

Ви можете зробити це так:

[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);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.