前言:

学习rn已经有大半年了,目前的项目是采用rn的,在项目中曾遇到webview中调用postMessage,然后在浏览器和真机中查看,发现在浏览器中会报错,后者不会报错,然后觉得很奇怪,因此就去粗略的研究了一下rn中webview的实现。由于webview涉及很多东西,所以这一篇写介绍一下安卓的webview,下篇再继续介绍ios的webview。

介绍:

webview主要是用来进行页面请求,页面加载,页面渲染,页面交互等处理,而安卓对于webview是没有区分UIWebview和WKWebview,而是直接采用webkit内核的wkwebview。

webview的工具类:

  • websetting:

作用:主要使用来设置和管理webview;

获取WebSettings实例:

// 添加访问网络权限(AndroidManifest.xml)  
<uses-permission android:name="android.permission.INTERNET" />
// 创建webview实例 
Webview webview = new WebView(this); 
// 创建webSettings实例 
WebSettings websettings = webview.getSettings();

WebSettings常用方法:
image.png

  • webviewclient:

作用:处理各种通知 & 请求事件;

  1. shouldOverrideUrlLoading:该方法主要是在请求url的时候触发该事件,该事件会根据返回值来决定是否拦截url请求;如果返回true则会不让webview处理该url,而是交给系统来自行处理该url的请求,如果返回的是false,则会不做拦截,直接由webview来处理加载url。
  2. onPageStarted:页面资源开始加载,此时可以做一个loading操作。
  3. onPageFinished:页面资源加载结束时触发,此时可以关闭loading操作。
  4. onLoadResource:页面请求或者加载资源时出错会触发。
  • webChromeClient:

作用:辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等。

  1. onProgressChanged(WebView view, int newProgress):获取网页的加载进度并且展示出来。
  2. onReceivedTitle(WebView view, String title):获取网页的title。
  3. onJsAlert,onJsConfirm,onJsPrompt:支持js的警告窗,确认窗和输入窗。

js与原生webview的交互:

  • 安卓调用js代码:

1)webview的loadUrl:该方法可以在webview中执行本地资源的加载,远程资源的加载以及js代码的执行。

调用形式:
webview.loadUrl(远程url);
webview.loadUrl(本地文件);
webview.loadUrl('javascript:js代码'):该形式必须在在onPageFinished之后才能调用,因为要等到资源加载完毕后,才能执行js代码。其中如果要调用html文件内的代码,只能时js代码嵌入到html的script标签之中。

// html
<html>
 ...
 <script>
   function toCall() { ... }
 </script>
 ...
 </html>
 
 // webview
 shouldOverrideUrlLoading(Webview webview, String url) { 
        webview.loadUrl(url); 
 }
 onPageFinished(Webview webview) { 
       webview.loadUrl("javascript: tocall()") 
       webview.loadUrl("javascript:console.log('asdf')")
 }

优点:简单;
缺点:对于原生要采用js代码的结果时就会很麻烦,而且效率低,还有调用它来执行js代码会刷新页面;
使用场景:不需要获取返回值且性能要求不高;

2)webview的evaluateJavascript:可以直接调用js代码,并且能直接获取js代码中的返回值;

webview.evaluateJavascript("avascript:callJS()", new ValueCallback() {
    @Override 
    public void onReceiveValue(String value) { 
    //此处为 js 返回的结果 
    } 
})

优点:调用简单,且效率高,而且能获取js层的执行结果;
缺点:必须要在安卓4.4之后才能用;
使用场景:在安卓4.4条件下优先使用;

  • js唤起安卓端代码:

1)webview的addJavascriptInterface:主要时将java类的对象和js对象进行映射,从而实现js调用java层代码;

调用形式:webview.addJavascriptInterface(java对象,js对象)

step1:制定特定的java类:

public class ToJs { 
    // 定义JS需要调用的方法 
    // 被JS调用的方法必须加入@JavascriptInterface注解 
    @JavascriptInterface 
    void executeJs() { 
    ..... 
    } 
}

step2:进行映射

// 设置与Js交互的权限 
webSettings.setJavaScriptEnabled(true); 
mWebView.addJavascriptInterface(new ToJs(), "test");

step3:在js层调用

window.test.executeJs();

缺点:存在安全性问题,因为可以通过test来获取整一个java类的所有方法,可能会获取系统或者用户等敏感信息。

2)WebViewClient的shouldOverrideUrlLoading:主要是对url请求进行拦截操作,对url的协议格式schema,协议名authority进行判断,如果符合则调用对应的方法并且返回true,允许url拦截,否则返回false,不支持url拦截。

协议形式:schema://authority?param1=xxx&param2=xxx

// java 
 websetting.setJavascriptEnabled(true);
 webview.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
       Uri uri = Uri.parse(url);
       String schema = uri.getSchema();
       String authority = uri.getAuthority();
       Set collection = uri.getQueryParameterNames();
       if (schema.equal('js') && authority.equal('webview')) {
          ...
          webview.loadUrl('javascript: c"(" + result + ")')
          return false
      }
   }
   return true;
 })
 
 // js 
  function b() { 
     window.location = 'js://webview?a=1&b=2'; 
  } 
  function c(data) {} 
  b()

优点:不存在安全漏洞;
缺点:很难在js端获取安卓端方法调用后的返回值;只能通过loadUrl方式将结果返回给js端;
使用场景:不需要返回值的时候;

3)WebChromeClient的onJsAlert()、onJsConfirm()、onJsPrompt():
原理:就是js层调用了alert,confirm,prompt时,webview可以通过设置webchromeclient来拦截弹窗的默认行为,从而对传过来的参数进行处理,一般传过来的参数是协议的格式传过来,主要是为了约束。

区别:

alert ---》没有返回值;
comfirm ---》只返回true或者false;
prompt ----》输入框输入什么,可返回什么,因此可以拦截它,然后改变返回的值;

image.png

image.png

优点:能获取webview的返回值;
缺点:就是比较麻烦;
(吐槽一下segmengfault这里的富文本框,贴代码特别不方便。)

js与RN的交互:

上面说了这么多,主要是为了给rn中webview和js层通信而做的一个铺垫,接下来就介绍本文的主题。
在rn中,对于视图组件在安卓中是会受到ViewManager控制;在rn中webview管理器的结构如下:

image.png

其中:
ReactWebViewClient:实现了WebviewClient,并且重写了几个重要的生命钩子;
ReactWebView:实现了webview,它根据rn的特点重写了很多方法,并且在setWebViewClient中将ReactWebviewClient注入进来。
ReactWebviewBridge:提供给js层调用。

当设置webview的source的时候,就会触发setSource方法,此时的处理逻是:
image.png
由此处可以看出当对于html或者uri会有不一样的处理,但最后还是通过webview.loadUrl来加载资源。

对于原生调用js代码,rn会多一层封装,将loadUrl和evaluteJavascript封装在一起evaluateJavascriptWithFallback;

image.png

通信原理图(有些复杂,ios的不会这么复杂):

image.png
image.png
image.png

(由于流程图比较大且复杂,所以分开三段展示)

关键步骤:
step1:js层调用webview层方法:
image.png

image.png

image.png

image.png

step2:rn层与rn-js层通信:
image.png

step3:rn-js层与rn层通信:
image.png

step4:rn层调用js方法:
image.png

总结:其实rn-js与html层通信,可以拆分成rn-js层和rn原生层通信+webview和js层的通信;

参考文章:
安卓webview
最全面总结 Android WebView与 JS 的交互方式
Android WebView 全面干货指南
你不知道的 Android WebView 使用漏洞


DragonChen
285 声望15 粉丝

下一篇是:Axios源码解析。