【问题标题】:How to get return value from javascript in WebView of Android?如何从 Android 的 WebView 中的 javascript 获取返回值?
【发布时间】:2011-03-18 22:27:33
【问题描述】:

我想从 Android 中的 Javascript 获取返回值。我可以用 iPhone 做到这一点,但我不能用 Android。我使用了 loadUrl,但它返回的是 void 而不是一个对象。有人可以帮帮我吗?

【问题讨论】:

    标签: android return


    【解决方案1】:

    Keith 相同,但答案更短

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

    并实现功能

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

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

    【讨论】:

    • 您能详细说明proguard 部分吗?
    • @eugene 如果你不知道它是什么,你可能不会使用它。无论如何,它可以使你的应用程序更难逆向工程
    • 谢谢。我知道我在项目根目录中有 proguard-project.txt,但仅此而已。我问是因为您的解决方案有效,但我得到了 SIGSEGV。我正在评估 document.getElementById("my-div").scrollTop 以从 SwipeRefreshLayout::canChildScrollUp() 获取滚动位置。我怀疑这可能是由于电话在短时间内被调用太多次。或proguard 我怀疑是因为我只是不知道它是什么..
    • 这应该是公认的答案,而不是这里评论的所有黑客行为
    【解决方案2】:

    这里有一个关于如何实现它的技巧:

    将此客户端添加到您的 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 调用中的“消息”将包含返回值。

    【讨论】:

    • 这是一个非常有价值的 hack;特别是当您构建一个无法控制 javascript 并且必须使用现有功能的应用程序时。请注意,您可以在不使用public boolean onConsoleMessage(ConsoleMessage consoleMessage) 而不是onJsAlert 拦截JS 警报的情况下获得相同的结果,然后在javascript 调用中执行webView.loadUrl("javascript:console.log(functionThatReturnsSomething)"); - 这样您就可以不理会JS 警报了。
    • WebChromeClient.onConsoleMessage 的解决方案似乎更简洁,但请注意它不适用于某些 HTC 设备。请参阅this question 了解更多信息。
    • 有什么理由不使用 addJavascriptInterface 吗?
    • @Keith:您需要一个 javascript 将其写入结果的对象,但是由于您无法在此问题中触及现有的 javascript 并且现有的 js 不会写入此类对象,因此 js接口方法在这里没有帮助(这就是我的理解)。
    • @RayKoopa 不一定正确。虽然您可能无法在目标网页中编辑现有的 javascript,但您仍然可以通过调用函数 mWebView.loadUrl("javascript:yourNameForObject.yourSetterMethod(valueToSet)") 来调用传递给 mWebView.addJavascriptInterface(YourObject mYourObject,String yourNameForObject); 的对象的方法
    【解决方案3】:

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

    【讨论】:

    • stackoverflow.com/questions/5521191/…这样的情况下我该怎么做
    • 如果你的 js 不写入这个对象,你也不能碰 js,这也无济于事。
    • @PacMani:使用loadUrl("javascript:...")(API 级别 1-18)或evaluateJavascript()(API 级别 19+)在当前加载的网页上下文中评估您自己的 JavaScript。跨度>
    • @CommonsWare:谢谢,我明白了;)但是,经理决定可以通过“滥用javascript警报”(eyeroll)来修改js,所以我使用接口方法进行了修改。跨度>
    【解决方案4】:

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

    适用于 Android 2.2 及更高版本。 (需要 commons-lang,因为我需要将我的代码 sn-ps 作为 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);
        }
    }
    

    【讨论】:

    • 感谢您提供上述代码 - 我在我的 Android 项目中采用了它。但是,其中有一个陷阱,这是由于 Java API 设计不佳造成的。该行: jsReturnValueLock.wait(waitMs - (System.currentTimeMillis() - start)); wait() 函数参数的值有时可能恰好为 0。这意味着“无限等待”——我偶尔会收到“无响应”错误。我通过测试解决了这个问题: if (elapsed >= waitMS) break;并且不在下面再次调用 System.currentTimeMillis() ,而是使用经过的值。最好编辑和修复上面的代码。
    • 哇,非常感谢!我也看到了这个问题,但一直不知道它是从哪里来的。
    • 但是如果我在 button.click 监听器等中运行 evalJS。 waitForJsReturnValue 可能会导致 evalJs() 挂断。所以 handler.post() 中的 webView.loadUrl() 可能没有机会执行,因为 button.click 监听器没有返回。
    • 但是如何将代码不用引号括起来,而是用function(){}?如何将 jsString 放入{}
    【解决方案5】:

    在 API 19+ 上,最好的方法是在您的 WebView 上调用 evaluateJavascript

    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

    【讨论】:

    • 最佳答案,谢谢
    【解决方案6】:

    @Felix Khazin 建议的解决方案可行,但缺少一个关键点。

    应在WebView 中的网页加载后进行javascript 调用。将此WebViewClientWebChromeClient 一起添加到WebView

    完整示例:

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

    【讨论】:

      【解决方案7】:

      作为使用 自定义方案Android native code &lt;-&gt; HTML/JS code 通信的替代变体。例如 MRAID 使用这种技术[About]

      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>
      

      【讨论】:

        【解决方案8】:

        你可以这样做:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-09-13
          • 2011-11-16
          • 1970-01-01
          • 1970-01-01
          • 2017-05-03
          • 2011-09-06
          • 1970-01-01
          相关资源
          最近更新 更多