前言通信原理之先了解webviewh5向ios客户端发送消息IOS客户端调用H5方法H5调用Android客户端方法Android客户端调用H5方法H5调用RN客户端RN客户端调用H5前端jsBridge的封装四、调试

打开网易新闻 查看更多图片

为了提高开发效率,开发人员往往会使用原生app里面嵌套前端h5页面的快速开发方式,这就要涉及到h5和原生的相互调用,互相传递数据,接下来就实践项目中的交互方式做一个简单的记录分享,废话不多说,直接上正文:

由于安卓和ios的处理方式不一样,所以我们要分开处理。先贴上判断访问终端的代码

打开网易新闻 查看更多图片

IOS容器

在IOS客户端中,我们首先要提到的是一个叫UIWebView的容器,苹果对它的介绍是:

UIWebView是一个可加载网页的对象,它有浏览记录功能,且对加载的网页内容是可编程的。说白了UIWebView有类似浏览器的功能,我们使用可以它来打开页面,并做一些定制化的功能,如可以让js调某个方法可以取到手机的GPS信息。

但需要注意的是,Safari 浏览器使用的浏览器控件和 UIwebView 组件并不是同一个,两者在性能上有很大的差距。幸运的是,苹果发布 iOS8 的时候,新增了一个 WKWebView 组件容器,如果你的 app 只考虑支持 iOS8 及以上版本,那么你就可以使用这个新的浏览器控件了。

WKWebView 重构了原有 UIWebView 的14个类,3个协议,性能提升的同时,赋予了开发者更加细致的配置(这些配置仅针对客户端 iOS 开发,对于前端 H5 来说,保持两种容器调用方法的一致性很重要)。

Android容器

在安卓客户端中,webView 容器与手机自带的浏览器内核一致,多为 android-chrome。不存在兼容性和性能问题。

RN容器

在 react-native 开发中,从 rn 0.37 版本开始官方引入了组件,在安卓中调用原生浏览器,在 iOS 中默认调用的是 UIWebView 容器。从 iOS12 开始,苹果正式弃用 UIWebView,统一采用WKWebView。

RN 从0.57起,可指定使用 WKWebView 作为 WebView 的实现

WebView 组件不要嵌套在或原生点击组件中,会造成H5内页面滚动失效

在 iOS 中,并没有现成的 API 让 js 去调用 native 的方法,但是 UIWebView 与 WKWebView 能够拦截 h5 内发起的所有网络请求。所以我们的思路就是通过在 h5 内发起约定好的特定协议的网络请求,如 'jsbridge://bridge2.native?params=' + encodeURIComponent(obj) 然后带上你要传递给 iOS 的参数;然后在客户端内拦截到指定协议头的请求之后就阻止该请求并解析 url 上的参数,执行相应逻辑。

在 H5 中发起这种特定协议的请求方式分两种:

1. 通过localtion.href;通过location.href有个问题,就是如果我们连续多次修改window.location.href的值,在Native层只能接收到最后一次请求,前面的请求都会被忽略掉。

2. 通过iframe方式;使用iframe方式,以唤起 Native。以唤起分享组件为例

打开网易新闻 查看更多图片

然后客户端通过拦截这个请求,并且解析出相应的方法和参数: 这里以 iOS 为例:

打开网易新闻 查看更多图片

看不懂就略过,非重点。。。。。

这里我们在请求参数中加上了 cbName=jsCallClientBack,这个 jsCallClientBack 为 JS 调用客户端所定义的回调函数,在业务层 jsBridge 封装中,我们传入一个匿名函数作为回调,底层将这个函数绑定在 window 的 jsbridge 对象下并为其定义一个独一无二的 key,这个 key 就是 jsCallClientBack,客户端在处理完逻辑后,会通过上面已经介绍过的方法来回调 window 下的方法。

PS: 在将回调绑定在 window 下时,特别注意要使用 bind 保持函数内 this 的原有指向不变。

Native 调用 Javascript 语言,是通过 UIWebView 组件的stringByEvaluatingJavaScriptFromString 方法来实现的,该方法返回 js 脚本的执行结果。

从上面代码可以看出它其实就是执行了一个字符串化的 js 代码,调用了 window 下的一个对象,如果我们要让 native 来调用我们 js 写的方法,那这个方法就要在 window 下能访问到。但从全局考虑,我们只要暴露一个对象如 JSBridge 给 native 调用就好了。

调用客户端原生方法的回调函数也将绑在 window 下供客户端成功反调用,实际上一次调用客户端方法最后产生的结果是双向互相调用。

在安卓webView中有三种调用native的方式:

通过 schema 方式,客户端使用 shouldOverrideUrlLoading 方法对 url 请求协议进行解析。这种js的调用方式与 iOS 的一样,使用 iframe 来调用 native 方法。

通过在 webview 页面里直接注入原生 js 代码方式,使用 addJavascriptInterface 方法来实现。

打开网易新闻 查看更多图片

上面的代码就是在页面的 window 对象里注入了 AndroidNativeApi 对象。在 js 里可以直接调用原生方法。 使用 prompt,console.log,alert 方式,这三个方法对 js 里是属性原生的,在 Android webview 这一层是可以重写这三个方法的。一般我们使用 prompt,因为这个在js里使用的不多,用来和 native 通讯副作用比较少。

打开网易新闻 查看更多图片

一般而言安卓客户端选用1、2方案中的一种进行通信,从前端层面来讲,推荐客户端都使用 schema 协议的方式,便于前端 jsBridge 底层代码的维护与迭代。

在安卓APP中,客户端通过 webview 的 loadUrl 进行调用:

打开网易新闻 查看更多图片

H5端将方法绑定在 window 下的对象即可,无需与 iOS 作区分。

我们知道 RN 的 webView 组件实际上就是对原生容器的二次封装,因此我们不需要直接通过 schema 协议来通信,只需要使用浏览器 postMessage、onMessage 来传递消息即可,类似于 iframe,而真正的通信过程 RN 已经帮我们做了。

打开网易新闻 查看更多图片

postMessage 是双向的,所以也可以在 RN 里发消息,H5 里接消息来触发对应的回调。

打开网易新闻 查看更多图片

在了解了js与客户端底层的通信原理后,我们可以将 iOS、安卓统一封装成 jsBridge 提供给业务层开发调用。

打开网易新闻 查看更多图片

在核心封装的基础上,我们可以还做更多的优化,比如将每个回调函数调用后自我销毁释放内存

安卓使用 chrome://inspect 进行调试,需要科学上网。

iOS使用 mac safari 的 develop 选项进行调试。

使用RN的 http://localhost:8081/debugger-ui 只能调试 RN 代码,无法调试 webView 代码,RN下 webView 调试和对应 native 相同,但是在 chrome://inspect 下会出现样式问题。 除非是纯 RN 编写,直接打包成 APP,否则不建议在 RN 下调用 webView 组件。