在Android和iOS两端Native与JS通信一般会使用开源的Bridge:
https://github.com/marcuswestin/WebViewJavascriptBridge
该Bridge工作机制的大致描述:
协议与接口
-
wvjbscheme://__BRIDGE_LOADED__
:JS触发,通知Native加载WebViewJavascriptBridge.js -
wvjbscheme://__WVJB_QUEUE_MESSAGE__
:JS触发,通知Native有调用消息(可以调用接口4获取了) -
wvjbscheme://return/_fetchQueue/
:JS触发,向Native发送调用消息(附加在_fetchQueue/
后面) -
WebViewJavascriptBridge._fetchQueue()
:Native调用,通知JS发送调用消息(JS执行协议3) -
WebViewJavascriptBridge._handleMessageFromNative('%s')
:Native调用,向JS发送回应数据
实现:
- Native拦截loadUrl和onJsPrompt, 收取协议消息;
- JS通过动态创建并设置iframe的src属性,或者调用window.prompt发送协议消息。
- WebViewJavascriptBridge.js暴露
_fetchQueue()
和_handleMessageFromNative()
接口供Native调用。
注册及回调
-
WebViewJavascriptBridge.registerHandler()
:注册Native调用JS时对应处理程序的回调??,这个回调可以向Native发送消息 -
WebViewJavascriptBridge.callHandler()
:调用处理程序,�以通过发送消息的方式触发Native执行操作
Native与JS通信模型在移动端的差异

- iOS同步完成API注册,Android异步完成API注册,因此小程序调用同步API在Android上会导致UI冻结
- iOS和Android在注册完API,都会另外调用JS,传回响应;iOS返回API数据,Android只返回成功状态

小程序的运行时机制
启动机制
小程序的启动方式分为两种:
- 首次加载小程序的冷启动;
- 一定时间内重新打开小程序的热启动。
名词解释:
- 冷启动: 通常是用户首次打开小程序,或者在客户端主动销毁了小程序之后,用户又重新打开了小程序,在这两种情况下,小程序都会初始化启动;
- 热启动: 用户已经访问过该小程序,短暂退出后,又重新回到小程序。这时客户端不会销毁该小程序,而只是控制小程序的显隐。
销毁机制
客户端主动触发小程序销毁的场景:
- 当用户退出小程序超过 5 分钟后;
- 打开小程序数量超过系统支持上限 6 个(系统按照小程序被打开的先后顺序进行销毁)。
更新机制
基础包下载/更新
流程
- 在 APP 启动后合适时机(打开小程序之前),加载框架的 json 文件
- 对比 json 文件返回的版本号与本地缓存的版本号
- 如果版本号不一致,则根据 url 字段下载小程序框架包(zip)
- 框架包下载完成后,校验 md5 是否与 json 文件中 md5 字段值一致
- 解压 zip 包,将里面的 html 文件缓存起来(连同版本号一起缓存)
json 文件
url 为: https://im.qihoo.net:8282/fed/miniapp/miniapp.json
,host
后续会变动,当前为临时地址。数据格式为:
{
version: "0.1.2",
fid: "5efc6851ee9eac3fbb45c508",
md5: "884530f116d0011066215a052f19911e",
url: "https://im.qihoo.net:8282/fed/miniapp/0.1.2.zip"
}
发布新的版本
将公共库zip包上传到织语测试服,方法为:在Web版聊天界面上传公共库包,抓upload接口返回结果,里面有fid:https://im.qihoo.net:8282/fed/web-module
上传后更新上述JSON的全部字段,然后将JSON文件覆盖发布到测试服。
小程序包下载/更新
流程
- 首次打开小程序时(本地无缓存),小程序包基于fid由Native直接下载。fid作为版本控制机制,更新zip包,fid随之更新,appId不变。
- 本地缓存有缓存时,直接使用本地版本启动小程序。后台异步去下载,更新后的小程序下次打开才会生效。
问题:小程序包可能缺少 app zip 的MD5验证机制。
流程图
[图片上传失败...(image-319638-1710936634081)]
https://www.processon.com/view/link/5f277a885653bb7fd262919c
执行/渲染机制
业界模式
微信/百度等小程序的做法一般是将 JS 放在一个独立的逻辑层来处理(iOS 用 JavaScriptCore,安卓用 V8),在逻辑层执行时就没有 BOM 环境了,也就是 JS 里不能再包含 window/document 之类的代码了。UI 层是通过 webview 来进行的,一个页面用一个独立的 webview 渲染。
逻辑层和 UI 层的数据交互是通过 setData 和事件来传递的,类似:
[图片上传失败...(image-10a1b1-1710936634082)]
逻辑执行 JS 后最终都会变成数据的变更,然后通过 setData 将数据传递到 UI 层来渲染,UI 层将用户响应的事件传递到逻辑层,从而达到二边交互的逻辑。
临时方案
由于 PC 小程序是基于 Vue 框架来弄的,所以如果用微信/百度小程序的方案,就需要改造 Vue 框架的 runtime/compiler 等部分,改造成本比较高,并且这种会影响 Vue 的组件(如果组件里用了一些 DOM 的操作就会有问题)
所以临时方案为还是用多个 webview 的机制,JS 的执行也是放在 webview 里执行的,这样就可以完全使用 Vue 的功能了。但这个模式会带来另外一个问题,就是多个 webview 之间的数据同步(因为 App 是单例的,小程序生命周期内只会执行一次)。
为了处理多个 webview 数据同步的问题,引用一个隐藏的 webview 来执行 app.js,页面用单独的 webview 来执行
- 小程序初次打开时,Native 打开一个隐藏的 webview 加载 app.html,同时打开一个 webview 来渲染 page.html;
- 后续 tabBar/页面 点击打开新页面时直接用 webview 打开 page.html;
- Native 需要管理页面的 webview(就是打开 page.html 的 webview)。
多个 webview 页面的数据同步通过 Native 提供的 getSessionStorageSync/setSessionStorageSync 来执行,并且限定全局数据放在 app.globalData 上,app.globalData 通过 defineProperty 来实现
getSessionStorageSync/setSessionStorageSync 方法是在当前小程序的生命周期内生效的,也就是当小程序销毁时,这个接口下面的数据也跟随销毁。
冷启动过程
- 根据小程序 id,去下载或者从本地缓存中读取小程序代码(
app.json
、app.js
、page.js
、app.css
),并复制基础库(app.html
、page.html
)到小程序目录。 - 打开隐藏的 Webview,加载
app.html
。在小程序运行期间,保持该 Webview 一直处于打开状态。 - 打开一个新的 Webview ,并加载
page.html
。如果有指定的页面 path,需要在页面 URL 后面添加#页面路径
,比如#/pages/home/index
。
页面跳转
在页面中可以使用 navigateTo
、navigateBack
、redirectTo
方法跳转到指定页面。
-
navigateTo:新打开一个 Webview,并加载
page.html#页面路径
。如果此时该小程序已经打开了 10 个 Webview,则不再新打开 Webview,而是直接将当前页面替换为page.html#页面路径
- navigateBack: 销毁当前 Webview,并展示之前的 Webview。
-
redirectTo:当前 Webview 的页面替换为
page.html#页面路径
。
页面关闭
如果用户点击右上角的关闭按钮,小程序应该切换到后台,打开的 Webview 依旧保留。
切换到后台的小程序,只会保留 5 分钟。超过 5 分钟后,小程序将会退出。
小程序退出
如果用户主动杀掉进程,或者进行被系统杀掉,都将退出。小程序退出时,所有 Webview 都会销毁。
生命周期方法
客户端可以通过 webview.loadUrl("javascript:onShow()")
调用 Webview 中的函数。
小程序级别的生命周期方法
- onLoad:app.html 在打开后会自动调用,不需要客户端调用。
-
onShow:已经在后台运行的小程序,被切换到前台后,需要客户端调用
app.html
中的onShow
函数。 -
onHide:正在运行中的小程序被切换到后台时需要客户端调用
app.html
中的onHide
函数。 -
onUnload:小程序被销毁时(比如切换到后台超过5分钟),需要调用
app.html
中的onUnload
函数。
页面级别的生命周期方法
- onLoad:page.html 在打开后会自动调用,不需要客户端调用。
-
onHide:当前正在展示的页面,被隐藏起来的时候,需要调用
page.html
的onHide
函数。比如小程序被切换到后台,或者打开了新的 Webview。 -
onShow:之前被隐藏的页面,被切换到前台后,需要客户端调用
page.html
中的onShow
函数。 -
onUnload:页面被销毁时,需要调用该页面的
onUnload
方法。
网友评论