hybrid的机制学习

对于hybrid真正的接触和深入了解,是从来到这家公司开始的,之前虽然接触过,但是那个时候使用的都是各种sdk,面试通过后,在来之前,前端团队负责人就跟我说,来之前可以先自己了解一下hybrid。

什么是hybrid开发

所谓hybrid,顾名思义就是‘混合模式开发’,简单的解释就是前端h5结合native的原声能力,结合各自的优势和长处,打造一个超h5,类原生的前端应用。

说到h5开发和native开发,这里还是搜集总结了各自的优缺点

Native/Web/Hybrid的对比

Native/Web/Hybrid的对比

从这里可以明显看出来,native的优势呢:

  • 优秀的原生的操作体验
  • 性能好
  • 权限高

native的优势又往往受到设计和产品的青睐。那就开始产生出结合两者优势的技术方案。

那h5的具备的优势非常明显:

  • 无需走发版
  • 无版本问题
  • 一套代码多端运行

就这几点优势,也是为什么众多开发者和企业对其趋之若鹜。

接触hybrid以后就知道了,以前做的jssdk的开发、小程序的开发,以及后续接触的weex开发,其实都是hybrid的开发,只是结合的形式和开发的方式有所区别罢了,其实具体的核心部分都是一样,都是利用h5与native的通讯,让客户端和前端各司其责,然后有效的、高效率的结合。

小程序和weex

目前正火的小程序,其实处于h5和native中间的一个产物,webview作为渲染容器,结合小程序提供的高性能的原生组件,以及小程序官方提供的很便捷的开发api,开发者可以很快的开发一个类原生的在微信中使用的‘app’,当然还有不得不提的,小程序的发展迭代速度,社区的维护、文档的更新、健全的发布及版本控制机制,以其很多hybrid开发者梦寐以求的pc端的开发调试工具(还开放了远程调试哦)。

weex就比小程序更加的彻底,全部都会转换成原生(想想都很激动),前端按照h5开发的页面最终都呈现成原生。三端统一这一个跨时代的技术,一直在跌跌撞撞中缓慢前行,之前有了解过且'hello world'过的有React Native、PhoneGap、Cordova但是都很快就放弃了,因为这些都需要‘多栖程序员’才来驾驭。目前我们的项目中就是存在典型的两种技术栈,h5+hybrid以及weex+hybrid,对于两种技术栈的抉择上,即使weex目前仍存在很多问题,但就weex不会有‘安卓字体上下居中会偏上’的问题,我就会优先选它!

hybrid的实现方式

最终希望达到的效果就是js和native能自由通讯,目前主流的两种方式是:

  • url schema的声明式调用,如 hybird://changeTitle?title=修改标题&id=1
  • 典型的‘发布订阅’的函数式调用,通过客户端内置在webview中的bridge对象,通过调用bridge的send方法把js中的信息传给native,native通过访问webview的window对象,或者bridge对象,从而触发回调。

schema

对于url schema的这种形式,有点类似打点的处理方式,需要

  • 与native约定好数据格式
  • native拦截h5的请求
  • 分析处理
  • 回调

比如这里的,会拦截所有hybrid://的前端请求,openURL为前端调用的方法,后面即为处理的参数和回调id,这种方式一般就把回调函数挂载在window对象下。

那前端是如何发起请求呢?通常的有

  1. window.location.href跳转的形式
  2. iframe的src方式

但是location的形式存在一个问题,多次修改href的值,在Native层只能接收到最后一次请求,前面的请求都会被忽略掉。所用通常会使用iframe的形式。

window.callbacks[1] = function() {
  // ...
}
var url = 'hybird://changeTitle?title=修改标题&id=1';
var iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(function() {
    iframe.remove();
}, 100);

bridge

bridge的使用也是需要与native约定数据格式和函数,数据格式好理解,就类似与后端约定接口的数据格式一样(这里约定的是第一个参数是请求数据,第二个是回调处理函数)。

bridge的实现原理可以简单用下面这段代码表示

bridge.callbacks[1] = function() {// 先将回调执行函数挂载在bridge下
  //deal in changeTitle callback
 delete bridge.callbacks[1];
}
bridge.send({
    target: 'changeTitle',
    data: {
        title: '修改标题'
    },
   id:1, // 告诉navtive 回调的函数挂载的id
})

send就是bridge提供的方法,实现js到native到通讯(安卓的WebViewJavascriptBridge对应的是sendMessage,ios的webkit.WebViewJavascriptBridge对应的是postMessage)。

但是实际的开发过程中,往往不希望这样去自己定义id,而是希望通过下面这种方式来调用。我们扩展一下send函数,我们还接收一个回调函数作为参数,这里会在执行最后的send函数之前,会生成唯一的id(自增或通过时间戳等方式)作为取回调函数的钥匙,并将回调函数挂载在bridge对象下。

bridge.send({
    target: 'changeTitle',
    data: {
        title: '修改标题'
    },
},() => {
  // deal in changeTitle callback
})

这里就会有一个生成唯一的id的过程

bridge.send = function (message,callback) {
  message.id = new Date().getTime();
  bridge.callbacks[id] = callback;
//挂载成功后,在执行真正的send
  if(isAndroid){
    window.WebViewJavascriptBridge.sendMessage(message);
  } else {
    window.webkit.messageHandlers.WebViewJavascriptBridge.postMessage(message);
  }
};

然后我们在window对象下定义一个doInFinish的函数,doInFinish是native到js的通讯过程,调用的时候传入唯一的回调id,这个时候就去取bridge.callbacks下对应id的函数然后执行,这里需要注意的是,为了防止对象冗余,所以在执行后会进行销毁。这里也就解释了我们注册了右上角的分享按钮,分享一次后需重新注册一次,因为调用一次回调函数就被销毁了。

function doInFinish(id,result) {
    const callback = bridge.callbacks[id];
    callback(result);
    delete bridge.callbacks[id];
};

以上就是hybird的基本原理了,两种形式在业务中都有使用。

url schema用于‘声明式‘调用,通常用于页面的跳转链接。如指定打开带有特殊属性(禁用下拉刷新、隐藏分享按钮)的webview容器,或指定打开weex容器等,就可以在链接上带上固定的参数,如https://xxxxx/index.html?hybrid_info={'disableRefresh': true,'hideShare': true}

bridge这种’函数式‘调用,适合在代码中使用,底层做好封装,js就可以执行函数那样去调用native的函数了。

以下是以changeTitle为例,用简图说明下bridge的调用过程。

前端调用过程简图

令我困惑的config注册过程

在了解以上的基础原理介绍以后,使用函数式调用使用hybrid的时候,直接使用即可,但是看了公司的源码会发现,在hybrid的调用之前,有一个约定的config操作,这里称之为注册的过程。

就拿changeTitle为例,按照上面的原理分析,按照bridge里分析的直接send就可以了,但是实际上是先config,之后在进行changeTitle的调用。

// 这里抽象逻辑后的代码结构
bridge.send({
    target: 'config',
    data: {
        api: 'changeTitle'
    },
},() => {// 注册成功
  bridge.send({
    target: 'changeTitle',
    data: {
        title: '修改标题'
    },
  },() => {
    // deal in changeTitle callback
  })
})

这里也是纠结了一下,觉得注册过程略显冗余,经过分析和探讨,这块之所以会有注册的策略,主要的原因有以下几点

  1. 防止调用的hybrid方法不存在出错

js调用之后处于’黑盒’(无法跟踪定位问题)状态,注册之后,调用出错至少能排除’native还没有该方法’这种情况。由于h5的灵活和跨平台性,无法确定和限制h5最终运行的环境,而且很多公司会有好几个app,各app之间的差异性(版本、私有的实现等)会导致js的环境不可控,通过提前注册的形式来明确的知道当前所有调用的方法Native是否已经注册并支持。

  1. 应对后续有可能接入第三方的应用的可能性

类似微信的jssdk,不同的主体对于微信的native开放出来的sdk使用权限不一样,通过用appid注册的形式,来确定sdk的使用权限。

这里就出现了一个问题,hybrid每次调用都要注册么,这样岂不是很冗余而且影响性能么?微信的jssdk是下面使用的

wx.config({// 仅执行一次
    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
    appId: '', // 必填,公众号的唯一标识
    timestamp: , // 必填,生成签名的时间戳
    nonceStr: '', // 必填,生成签名的随机串
    signature: '',// 必填,签名
    jsApiList: ['chooseImage'] // 必填,需要使用的JS接口列表
});

wx.chooseImage({
    count: 1, // 默认9
    sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
    sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
    success: function (res) {
        var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
    }
});

会发现也是会分两个步骤,先是进行config,成功之后就可以直接调用使用了。

那我们的自己的hybrid的原理其实也是一样,我们的基础使用方法也类似hybrid('changeTitle').changeTitle({title: '修改标题'},() => {}),其中同样也是包含了两个过程,先注册然后进行调用。

最后说一下之所以能这样链式调用,是因为这里注册完以后,会以此为函数名挂载在hybrid对象下,且返回当前对象。

下面这里例子就是先在对象a下注册一个方法名为b的函数,然后直接调用b函数,下次调用b函数的时候就无需走注册的过程。

let a = {}
a = (name) => {
  // ... config finish
  if (typeof a[name] !== 'function'){
    // 仅挂载一次
    a[name] = (params) => {
      //deal with params
      console.log(params)
    }
  }
  return a //需返回当前对象
}
a('b').b('test')  // 'test'
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容