Làm cách nào để nhận giá trị trả về từ javascript trong WebView của Android?


82

Tôi muốn nhận giá trị trả về từ Javascript trong Android. Tôi có thể làm điều đó với iPhone, nhưng tôi không thể với Android. Tôi đã sử dụng loadUrl, nhưng nó trả về void thay vì một đối tượng. Ai có thể giúp tôi?

Câu trả lời:


79

Giống như Keithcâu trả lời nhưng ngắn hơn

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

Và thực hiện chức năng

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

Đừng quên xóa danh sách onDatakhỏi proguarddanh sách (nếu bạn đã bật proguard)


1
bạn có thể nói rõ hơn về proguardmột phần?
eugene

@eugene nếu bạn không biết nó là gì, bạn có thể không sử dụng nó. Dù sao một cái gì đó để cho phép mà làm cho kỹ thuật đảo ngược ứng dụng của bạn khó khăn hơn
Kirill Kulakov

1
Cảm ơn. Tôi biết tôi có proguard-project.txt trong gốc dự án nhưng không nhiều hơn thế. Tôi đang hỏi vì giải pháp của bạn hoạt động nhưng tôi nhận được SIGSEGV. Tôi đang đánh giá document.getElementById("my-div").scrollTopđể có được vị trí cuộn từ SwipeRefreshLayout::canChildScrollUp(). Tôi nghi ngờ có thể do cuộc gọi bị gọi quá nhiều lần trong thời gian ngắn. hay proguardmà tôi nghi ngờ vì tôi chỉ không biết nó là gì ..
eugene

Đây phải là câu trả lời được chấp nhận hơn là tất cả các vụ hack được bình luận ở đây
Romit Kumar

64

Đây là một bí quyết về cách bạn có thể thực hiện nó:

Thêm Ứng dụng khách này vào WebView của bạn:

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

Bây giờ trong lệnh gọi javascript của bạn, hãy làm:

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

Bây giờ trong lời gọi onJsAlert, "thông báo" sẽ chứa giá trị trả về.


17
Đây là một bản hack cực kỳ giá trị; đặc biệt là khi bạn đang xây dựng một ứng dụng mà bạn không có quyền kiểm soát javascript và phải thực hiện với các chức năng hiện có. Lưu ý rằng bạn có thể nhận được cùng một kết quả mà không cần chặn các cảnh báo JS bằng cách sử dụng public boolean onConsoleMessage(ConsoleMessage consoleMessage)thay vì onJsAlertvà sau đó trong lệnh gọi javascript mà bạn thực hiện webView.loadUrl("javascript:console.log(functionThatReturnsSomething)");- theo cách đó bạn để yên các cảnh báo JS.
Mick Byrne

Giải pháp với WebChromeClient.onConsoleMessagecó vẻ sạch hơn nhưng lưu ý rằng nó không hoạt động với một số thiết bị HTC. Xem câu hỏi này để biết thêm thông tin.
Piotr

3
Bất kỳ lý do nào sử dụng addJavascriptInterface sẽ không thích hợp?
Keith

@Keith: Bạn sẽ yêu cầu một đối tượng mà javascript ghi nó vào đó cho kết quả, nhưng vì bạn không thể chạm vào javascript hiện có trong câu hỏi này và js hiện có không ghi vào đối tượng như vậy, phương thức giao diện js không hữu ích ở đây ( đó là cách tôi hiểu nó).
Ray

1
@RayKoopa Không nhất thiết phải đúng. Mặc dù bạn có thể không chỉnh sửa được javascript hiện có trong trang web đích, nhưng bạn vẫn có thể gọi các phương thức lên đối tượng được truyền vào mWebView.addJavascriptInterface(YourObject mYourObject,String yourNameForObject);bằng cách gọi hàmmWebView.loadUrl("javascript:yourNameForObject.yourSetterMethod(valueToSet)")
Chris - Jr

20

Sử dụng addJavascriptInterface()để thêm một đối tượng Java vào môi trường Javascript. Yêu cầu Javascript của bạn gọi một phương thức trên đối tượng Java đó để cung cấp "giá trị trả về" của nó.


làm thế nào tôi có thể làm điều đó trong tình huống như thế stackoverflow.com/questions/5521191/...
vnshetty

Điều này không hữu ích nếu js của bạn không ghi vào đối tượng này và bạn không thể chạm vào js.
Ray

7
@PacMani: Sử dụng loadUrl("javascript:...")(API cấp 1-18) hoặc evaluateJavascript()(API cấp 19+) để đánh giá JavaScript của riêng bạn trong ngữ cảnh của trang Web hiện đang tải.
CommonsWare

@CommonsWare: Cảm ơn, tôi hiểu rồi;) tuy nhiên, người quản lý quyết định rằng js có thể được sửa đổi do "lạm dụng cảnh báo javascript" (cuộn mắt), vì vậy tôi đã xử lý bằng phương thức giao diện.
Ray

12

Đây là những gì tôi nghĩ ra ngày hôm nay. Nó an toàn theo luồng, hiệu quả hợp lý và cho phép thực thi Javascript đồng bộ từ Java cho Android WebView .

Hoạt động trên Android 2.2 trở lên. (Yêu cầu commons-lang vì tôi cần các đoạn mã của mình được chuyển đến eval () dưới dạng một chuỗi Javascript. Bạn có thể loại bỏ sự phụ thuộc này bằng cách đặt mã không phải trong dấu ngoặc kép mà ở trong function(){})

Đầu tiên, thêm cái này vào tệp Javascript của bạn:

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

Sau đó, thêm cái này vào hoạt động Android của bạn:

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
Cảm ơn bạn vì mã trên - Tôi đã sử dụng nó trong dự án Android của mình. Tuy nhiên, có một cái bẫy trong đó, do thiết kế API Java không tốt. Dòng: jsReturnValueLock.wait (waitMs - (System.currentTimeMillis () - start)); Giá trị của đối số hàm wait () đôi khi có thể được đánh giá bằng 0. Và điều này có nghĩa là "chờ vô hạn" - tôi thỉnh thoảng nhận được lỗi "không có phản hồi". Tôi đã giải quyết nó bằng cách kiểm tra: if (elapsed> = waitMS) break; và không gọi lại System.currentTimeMillis () bên dưới mà sử dụng giá trị đã trôi qua. Tốt nhất là chỉnh sửa và sửa chữa đoạn mã trên.
gregko,

Wow, cảm ơn rất nhiều! Tôi cũng thấy điều đó bị treo và không bao giờ biết nó đến từ đâu.
Keith

1
Nhưng nếu tôi chạy evalJS trong trình nghe button.click, v.v. waitForJsReturnValue có thể khiến evalJs () bị treo. Vì vậy, webView.loadUrl () bên trong handler.post () có thể không có cơ hội thực thi vì trình nghe button.click không trả về.
victorwoo

Nhưng làm thế nào để gói mã không phải trong dấu ngoặc kép mà trong function(){}? Làm thế nào để đặt jsString vào {}?
victorwoo

7

Trên API 19+, cách tốt nhất để làm điều này là gọi evalJavascript trên WebView của bạn:

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

Câu trả lời liên quan với chi tiết hơn: https://stackoverflow.com/a/20377857


5

Giải pháp mà @Felix Khazin đề xuất hoạt động, nhưng thiếu một điểm chính.

Lệnh gọi javascript phải được thực hiện sau khi trang web trong WebViewtệp được tải. Thêm cái này WebViewClientvào WebView, cùng với WebChromeClient.

Ví dụ đầy đủ:

@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

Là một biến thể thay thế sử dụng lược đồ tùy chỉnh:

Hoạt động chủ yêu

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 (nằm trong phần tử được gấp lại)

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

Bạn có thể làm như thế này:

[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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.