揭开WebView神秘的面纱系列(二)之Native与WebView的通信

前言

揭开WebView的神秘面纱系列(一)之WebView简介我们已经简单介绍了WebView,WebViewClient等几个常用的类和方法。今天我们也将用到这些方法,在webview发展之初更多的是用于信息展示,没有过多的交互,然而随着Hybrid方案越来越火热,与页面交互的需求越来越多,在某些场景下更是需要调用Native的API,例如打开摄像头,这是web页面无法单独完成,需要调用系统API。由此,Web页面与Native的通信就成了无法回避的问题,那么今天我们就来探索一下Native是如何与web页面进行通信的。

目录

原理


如上图所示,web页面与Native的交互主要是通过Jsbridge完成的,web页面通过调用JsBridge中的Java接口来调用一些系统API,如打开摄像头等,这也是最常用的功能,最主要还是web页面调用一些系统接口,Native直接调用一些JS方法还是比较少见的,常用的方法是Native向web页面中注入一些JS代码动态修改页面,例如注入JS以达到对网页进行换肤,黑夜模式等。

Native调用JS

方式1:通过loadUrl(“javascript:”)

我们以一个小小的例子来说明这个过程,我们向页面中注入js弹出弹框。效果入下图

首先我们需要申请权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

我还需要设置WebSettings,WebViewClient和WebChromeClient,其他配置参考第一篇文章,这里就不过多阐述了

WebSettings webSettings = webView.getSettings();
webView.setWebChromeClient(new WebChromeClient());
webSettings.setJavaScriptEnabled(true)
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webView.setWebViewClient(new myWebVliewClient());

然后在WebClient的OnPageStarted中添加如下

    webView.loadUrl("javascript: alert('Native注入的JS')");

可以注意到我们上面的图片的背景是白色的,其实我们load是http://www.baidu.com, 因为我们在onPageStarted中调用,当时资源还没有加载,所以背景是白色的,而且弹窗会阻塞资源的加载,不按取消页面就无法继续加载下去,会被阻塞在这里,所以对于注入的时机根据具体需求而定。

上面例子是非常简单的JS,如果是复杂的JS文件我们又该如何处理呢,有以下方法,其中我将test.js文件放在了Assets文件夹下,代码如下

 InputStreamReader isr = null;
        try {
            isr = new InputStreamReader(this.getAssets().open("test.js"), "UTF-8");
            BufferedReader bf = new BufferedReader(isr);
            String content = "";
            StringBuilder sb = new StringBuilder();
            while (content != null) {
                content = bf.readLine();
                if (content == null) {
                    break;
                }
                sb.append(content.trim());
            }
            bf.close();
            wholeJS = sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }

其中wholeJS就是JS文件转化而来的字符串,然后调用

  webView.loadUrl("javascript: " + wholeJS);

就可以看到和之前一样的效果。

方式2: 通过evaluateJavascript方法

相较于loadUrl,evaluateJavascript的优势在于异步加载,还可以将执行JS代码的结果带回来,我们以下面一个小例子来说明。

我们在web页面中声明如下方法:

<script>
    function getUID() {
        var id = 120;
        return id + 1;
    }
</script>

然后我们注入js代码调用这个函数,并将121这个结果带回来来,在onPageFinished中调用以下代码

 webView.evaluateJavascript("getUID()", new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String value) {
                    Log.d("CALLBACK", value);
                }
            });

结果如下

JS调用Native

JS调用Native是最常用也是最重要的方法,在实现上有两种思路,一种是拦截schema,有两种方式拦截,一种是注册接口。我们依然以一个例子取说明这个过程,也很简单,我们load一个在本地的页面,页面上只有一个按钮如下图,按下这个按钮去调用原生的Toast方法。

方式1:拦截URL schema

URL schema是一种请求格式,用拦截URL schema的方式我就需要自定义一个URL schema,如jsbridge://jsbridge://webview?&arg1=hello&arg2=world,我们的html页面如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
         *{
            margin: 0;
            padding: 0;
        }
        html,body{
            width: 100%;
            height: 100%;
        }
        div{
            width:100%;
            height: 100%;
            background: aquamarine;
        }
    </style>
</head>
<body>
<div style="bac">
<button  onclick="callAndroid()" style="height: 26px ;width:160px; text-align: center; vertical-align: middle ">JS 调用Native</button>
</div>
</body>
<script>
    function callAndroid() {
      location.href= "jsbridge://webview?&arg1=hello&arg2=world"
    }
</script>
</html>

我们需要到WebViewClient的shouldOverrideUrl去拦截,并解析参数,代码如下

   @Override
        public boolean shouldOverrideUrlLoading(WebView webView, String s) {
            Uri uri = Uri.parse(s);
            Log.d("test112", s);
            if(uri.getScheme().startsWith("jsbridge")) {
                String arg1 = uri.getQueryParameter("arg1");
                String arg2 = uri.getQueryParameter("arg2");
                String s1 = "JS调用Native,参数1:"+arg1+"参数2:"+arg2;
                Toast.makeText(MainActivity.this, s1, Toast.LENGTH_LONG).show();
            }
            return true;
        }

最后的结果如下图

还有另一种拦截就是通过WebChromeClient 拦截web的prompt,过程类似,这里就不再赘述。

方式2:注册Javascript接口

首先创建一个管理接口的类如下

   public class AndroidToJS extends Object {
        @JavascriptInterface
        public void hello(String msg) {
            Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
        }
    }

然后通过addJavascriptInterface去注册和映射接口如下,JS中就可以 test.函数名 调用函数

 webView.addJavascriptInterface(new AndroidToJS(), "test");

html页面如下

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
   <style>
        *{
           margin: 0;
           padding: 0;
       }
       html,body{
           width: 100%;
           height: 100%;
       }
       div{
           width:100%;
           height: 100%;
           background: aquamarine;
       }
   </style>
</head>
<body>
<div style="bac">
<button  onclick="callAndroid()" style="height: 26px ;width:160px; text-align: center; vertical-align: middle ">JS 调用Native</button>
</div>
</body>
<script>
   function callAndroid() {
   test.hello("JS调用Native");
   }
</script>
</html>

效果如下

最后

今天我们总结了JS与Native的相互调用,只是介绍了思路,离真正的产品还有很远的距离,大家可以看一看一些优秀的开源库如Dsbridge。本文如故有什么谬误,欢迎指正。点赞就是最大的支持。更多内容可以关注公众号QStack,追寻最纯粹的技术,享受编程的快乐。
QStack

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值