Як отримати повернене значення з 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.