如何从Android的WebView中的javascript获取返回值?


82

我想从Android中的Javascript获取返回值。我可以用iPhone来做,但不能用Android。我使用了loadUrl,但它返回的是void而不是对象。有谁能够帮助我?

Answers:


79

相同,Keith但答案较短

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

并实现功能

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

不要忘记onDataproguard列表中删除(如果您启用了proguard)


1
您能详细说明一下proguard吗?
尤金2014年

@eugene如果您不知道它是什么,则可能不使用它。无论如何,启用它会使您的应用程序进行反向工程变得更加困难
Kirill Kulakov 2014年

1
谢谢。我知道我在项目根目录中有proguard-project.txt,但仅此而已。我问是因为您的解决方案有效,但是我得到了SIGSEGV。我正在评估document.getElementById("my-div").scrollTop从中获取滚动位置SwipeRefreshLayout::canChildScrollUp()。我怀疑这可能是因为通话在短时间内被呼叫过多。或者proguard我怀疑,因为我只是不知道它是什么..
尤金

这应该是公认的答案,而不是在这里评论所有黑客
Romit Kumar

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且必须使用现有功能的应用程序时。请注意,您可以得到相同的结果,而无需使用public boolean onConsoleMessage(ConsoleMessage consoleMessage)代替来拦截JS警报onJsAlert,然后在您执行的javascript调用中webView.loadUrl("javascript:console.log(functionThatReturnsSomething)");-通过这种方式,您就不用再担心JS警报了。
米克·伯恩

该解决方案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)")
Chris-Jr

20

使用addJavascriptInterface()Java对象添加到JavaScript环境。让您的Javascript调用该Java对象上的方法以提供其“返回值”。



如果您的js没有写入该对象并且您无法触摸js,这将无济于事。

7
@PacMani:使用loadUrl("javascript:...")(API级别1-18)或evaluateJavascript()(API级别19+)在当前加载的网页的上下文中评估您自己的JavaScript。
CommonsWare 2014年

@CommonsWare:谢谢,我明白了;)但是,经理决定可以通过“滥用javascript警报”(eyeroll)来修改js,因此我采用了接口方法。

12

这就是我今天想出的。它是线程安全的,相当高效,并且允许从Java同步执行Android WebView的Javascript

适用于Android 2.2及更高版本。(需要commons-lang,因为我需要将我的代码段作为Javascript字符串传递给eval()。您可以通过将代码不包装在引号中,而将包裹在中来消除此依赖性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(elapsed> = waitMS)中断;并且不会在下面再次调用System.currentTimeMillis(),而是使用经过的值。最好编辑并修复上面的代码。
gregko

哇,非常感谢!我也看到过挂物,却从不知道它从哪里来。
Keith

1
但是,如果我在button.click监听器等中运行evalJS,等等。waitForJsReturnValue可能导致evalJs()挂断。因此,handler.post()中的webView.loadUrl()可能没有执行的机会,因为没有返回button.click侦听器。
victorwoo

但是如何将代码包装在引号中而不是引号中function(){}呢?如何将jsString放入{}
victorwoo

7

在API 19+,要做到这一点的最好方法是调用evaluateJavascript您的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建议的解决方案有效,但缺少一个关键点。

WebView载入中的网页后,应进行javascript调用。将此WebViewClientWebView一起添加到中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

作为使用自定义方案的替代变体:

主要活动

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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.