0.前言
前段时间,我的一个ios同事兼大学同学,提出了一个Hybrid开发中的优化方案,人很帅,可能是我见过最帅的程序猿了,下次有机会po他的照片。那么这个优化方案是什么呢?待会再说吧,其实我想表达的是Android和ios同事应该多沟通,其实很多设计思想都很像,可以相互借鉴对方一些优秀的点,就像现在新出的Android机和苹果机也越来越像了。
1.需求场景
就像下图(以Android为例,IOS把Activity替换成ViewControll),在Activity A中的H5 A里打开一个新的网页H5 B,是使用启动了一个Activity B,然后用其中的Webview B去加载H5 B,当用户在H5 B中进行了某些操作,需要让H5 A知道时,就会很麻烦,因为它们是被两个不同的Webview所加载。最常见的例子就是登录、支付了,用户在B页面完成登录或支付行为,返回到A时,希望H5 A刷新。这篇文章,就是用于解决这个问题:实现Hybrid中两个WebView中H5的通信。
额外说明:
1. 打开新的H5,采用了启动新的Activity B去加载,是为了提升用户的体验感,避免出现用户回退时,H5页面刷新的问题。
2. H5里采用document.addEventListener(‘visibilitychange’,function(){})是可以做到监听回到H5 A的事件,但是有些手机会无效,比如:华为KIW-AL10、魅族MX4
3. 以上方式解决的,不限于两个相邻的H5,也不限于1:1的更新,可以支持1:N的刷新,只要保证注册的名字一致,后面会讲到。
2.实现原理
实现原理是依赖于加载H5的WebView之间的通信,借助了观察者模式的思想。这里需要分两步走:
1. H5 A触发WebView A订阅某类事件,以便H5 B发布该类事件时,能够接受到。
2. H5 B发布该类事件,触发WebView A收到消息,并调用H5 A中Js方法。
额外说明:
1. 这里的订阅和发布,用安卓中的广播可以实现,但是考虑其比较“重”,这里使用Handler来实现广播功能。原理如下:为每一个WebView创建了一个Handler对象,并创建了一个全局的Handler集合,用于保存WebView的Handler对象,当初始化WebView时,把Handler放入集合中,当WebView销毁,即调用了其onDetachedFromWindow方法时,把Handler从集合中移除。当WebView A订阅消息时,把订阅名记下来,作为当前WebView对象的属性;当WebView B发布消息时,遍历全局的Handler结合,逐个发送消息,该消息中携带了订阅名、回调方法名、参数等信息,然后在WebView中Handler的handleMessage方法中,对比当前WebView的订阅消息,是否包含WebView B发布的订阅名。如果包含,则用消息中包含的回调方法名和参数,调用当前WebView的js方法,这样就完全了通信。(文字看着晕的话,我们待会举个栗子look look!)
2. 因为采用了Handler来实现广播,所以WebView A不能立即订阅H5 A传递过来的订阅名,这时候就要把该订阅名保存下来,这时候当WebView B需要发布消息的时候,msg.what可以指定一个特定的值,并用msg.obj携带订阅名等信息。WebView A中的handler只要去case到那个特定的值,然后判断msg.obj中是否与WebView中之前保存的订阅名是否匹配就可以了。
3. 订阅和发布的协议格式:
H5 A 订阅:协议名/register/订阅名
H5 B 发布:协议名/trigger/订阅名/回调方法名/参数1/参数2/参数N
以登录为例子:
订阅:josan://register/login
发布:josan://trigger/login/logined/张三/24
3.实现效果
当我们把App里的逻辑实现了以后,看看两个H5通信有多简单,只需要两步:
1.在需要刷新的H5页面完成订阅,并实现回调的Js方法
<script>
function openH5B() {
location.href = 'josan://register/login';
}
function logined(name, age) {
document.getElementById("login").innerHTML = name + ',年龄:' + age;
document.getElementById("login").style.backgroundColor= "green";
}
</script>
2.在另一个触发刷新的H5页面,通过协议发布事件
function triggerLogined(){
location.href = 'josan://trigger/login/logined/张三/24';
}
这样就会调用到前一个需要刷新H5的logined方法,并把张三和24作为参数传递过去。是不是很简单呢!
4.实现细节
1. H5 A触发WebView A订阅
这个页面很简单,先看第17行,这里点击进入H5 B时,触发了第9行的openH5B()方法,方法里很简单,第一行是通过协议去打开Activity B,不用关心,重点是关注第9行,这里其实就是调用了订阅的协议,其中login为订阅名。
<!DOCTYPE html>
<html>
<head>
<meta charset=