前言

H5 VS Native 一直是前端技术界争执不下的话题。react、vue等技术栈引领着纯H5开发,rn、week则倡导原生体验。但在项目实战中,经常会选择一个中立的方案:混合开发。大众称呼:Hybrid。

本人目前从事新闻类产品研发,对于大家来讲,就是熟知的如今日头条、百度新闻、网易新闻等。在产品设计初期,考虑到一些实现难易程度问题(如新闻详情页,图文混排,NA实现起来不如H5这样自如),一些部分选择了Hybrid方式开发,本篇就把开发过程中的一些想法分享一下,以供大家参考。

JSBridge解决的问题

混合开发,最重要的问题是:H5和Native的双向通信。 但现实中JS和NA的交互方法非常有限,下面会详细说明。开发中如只是单纯的方法调用,既无法确保调用成功率,也无法确保代码足够简洁。于是就有了JSBridge。JSBridge,是一种JS实现的Bridge,是一种思路,可以有不同理解,不同的代码实现。主旨思想是在H5和NA之间搭建一个桥梁(Bridge),给两端留好更友好、更合理的接口。

H5和NA的双向通信通用方法

H5通信方式和兼容性如下表所示。指的是借助Native的webview加载H5页面,H5和NA之间通过API、URL拦截、全局调用等形式,实现消息通信。站在大厂的角度考虑,在实战的时候,会选择更兼容的方式。

H5调用NA方法梳理

平台 方法 备注
Android shouldOverrideUrlLoading scheme拦截方法
Android addJavascriptInterface API
Android onJsAlert()、onJsConfirm()、onJsPrompt()
IOS 拦截URL
IOS(UIwebview) JavaScriptCore API方法,IOS7 支持
IOS(WKwebview) window.webkit.messageHandlers APi方法,IOS8 支持

NA调用H5方法梳理

平台 方法 备注
Android loadurl()
Android evaluateJavascript() Android 4.4
IOS(UIwebview) stringByEvaluatingJavaScriptFromString
IOS(UIwebview) JavaScriptCore IOS7.0
IOS(Wkwebview) evaluateJavaScript:javaScriptString iOS8.0

通过上面两端调用方法梳理表,不难分析出,URL拦截 & 执行JS是 安卓和IOS比较通用且兼容性较好的方案。我们混合开发的基础正是基于这种方法来实现的。

常规混合开发思路

H5和NA通信方面,最简单直接的思路是:NA拦截H5的URL获取消息(一般是通过修改iframe的src来实现 ①),经过业务处理,NA执行JS(在H5侧提前注册好的全局方法③)回调通知H5(如下图)。


H5代码实现如下:

<html>
...
<body><div class="content">XXXXX</div>
</body><script>// ① 注册全局函数,以便端调用window.setAllContent = function(){}// ② 通用方法函数var sendschema = function(action,param){let tempnode = document.createElement('iframe');tempnode.src = "bdnews://" action param;}// ③ H5逻辑开始 运行函数document.addEventListener("DOMContentLoaded",function(){sendschema('load_finish');},false);
</script>...
</html>

Android原理大致如下:

webView.setWebViewClient(new WebViewClient() {public boolean shouldOverrideUrlLoading(WebView view, String url) {// 场景一: 拦截请求、接收schemaif (url.equals("load_url")) {// 处理逻辑dosomething// 回掉view.loadUrl("javascript:setAllContent("   json   ");")}// 场景二:端自己调用H5,没有请求发起clickbutton(){view.loadUrl("javascript:setAllContent("   json   ");")}}
});

IOS大概逻辑如下:

// 初始化webview
UIWebView * view = [[UIWebView alloc]initWithFrame:self.view.frame];
[view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.xx.com"]]];
[self.view addSubview:view];&nbsp;
&nbsp;
/*
webView协议中的方法
shouldStartLoadWithRequest //准备加载内容时调用的方法,通过返回值来进行是否加载的设置
webViewDidStartLoad //开始加载时调用的方法
webViewDidFinishLoad //结束加载时调用的方法
didFailLoadWithError //加载失败时调用的方法
*/
&nbsp;
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{if ([urlString hasPrefix:@"scheme://hybrid?info="]) {if([name isEqualToString:@"load_finish"]){// [self.webView setContent];[self.webView stringByEvaluatingJavaScriptFromString:strFormat];}}
}- clickbutton(){[self.webView setContent];
}

但这样开发存在一些痛点:

1)回调函数不明确。可以说目前没有回调函数的机制,这导致一些依赖于回调函数的分析及判断无法正常使用,如:功能调用方、调用是否成功、调用失败异常处理等这些CASE;

2)对应关系不明确。有一些调用看起来像是回调,但没有把他们放到一起,导致代码散乱,难以维护。如上面demo:sendschema('load_finish') 和 setAllContent 本来含义是 告诉NA页面准备好了,NA收到后,向页面塞数据。本来紧密相关的一对功能,拆分开看不出有什么联系;

3)全局函数冗杂。理想中如果调用和回调成对出现,DEMO中注册及维护全局函数的工作就会减少很多。提升页面可读性和维护成本。如 load_finish 和 setAllContent,只保留 load_finish 即可;

4)端内代码冗杂。端内注册了与H5约定的调用方法,很显然也需要维护一套代码标识什么时候调用。

以上开发中遇到的问题,也许刚开始功能不多的时候还察觉不出问题,但是随着功能增加,后期维护成本很大。

JSB方案设计

在H5和NA之间增加一个中间层,这层封装了H5和NA通信的交互方式。H5和NA互不关心对方的样子,通过中间层暴露的方法进行功能调用即可。


JSB交互模型

H5跟NA交互,从H5角度来看大致可分为两大类:有去无回&有去有回、无去有回。

第一类交互模型

请求逻辑:有去无回、有去有回。这里有两种实现方案(初步思路稿如下):

① 函数名关联

let BDAPPnode = {callbacks: {},// 调用函数注册invoke(action, params, successfnname, successfn) {this.callbacks[successfnname] = {success: successfn};sendschema(action, params);},// NA调用callbackSuccess(callbackname, params) {try {BDAPPnode.callbackFromNative(callbackname, params, true);} catch (e) {console.log('Error in error callback: '   callbackname   ' = '   e);}},callbackFromNative(callbackname, params, isSuccess) {let callback = this.callbacks[callbackname];if (callback) {if (isSuccess) {callback.success && callback.success(params);}};}
};

② ID 关联

let BDAPPnode = {callbackId: Math.floor(Math.random() * 2000000000),callbacks: {},invoke(action, params, onSuccess, onFail) {this.callbackId  ;this.callbacks[self.callbackId] = {success: onSuccess,fail: onFail};sendschema(action, params, this.callbackId);},callbackSuccess(callbackId, params) {try {BDAPPnode.callbackFromNative(callbackId, params, true);} catch (e) {console.log('Error in error callback: '   callbackId   ' = '   e);}},callbackError(callbackId, params) {try {BDAPPnode.callbackFromNative(callbackId, params, false);} catch (e) {console.log('Error in error callback: '   callbackId   ' = '   e);}},callbackFromNative(callbackId, params, isSuccess) {let callback = this.callbacks[callbackId];if (callback) {if (isSuccess) {callback.success && callback.success(callbackId, params);} else {callback.fail && callback.fail(callbackId, params);}delete BDAPPnode.callbacks[callbackId];};}
};

在发出请求的时候,注册回调方法。这么做有两个目的:

  • 无需提前注册所有全局回掉函数,减少不必要的初始化,进而减少白屏时间;

  • 不用额外起回掉函数的名称,发起请求的时候传入一个随机ID,同时注册此ID的回掉函数。NA通过统一封装好的回掉函数调用,回调ID和参数,进而达到执行回调逻辑。

具体选用那个,还得根据具体情况具体分析看。

第二类交互模型看

请求逻辑:无去有回,没有发出请求,NA主动调用。此类还需注册全局变量,等待NA调用。跟非JSBridge的实现是一个道理

window.fn1 = () =>{// do fn1
}window.fn2 = () =>{// do fn2
}

方案选择

实战过程中深刻体会到,混合开发可以分为两大类:NA服务H5,H5服务NA

前者H5为主,大多数交互是H5发起NA请求,等待NA回调,可称之为:『一对一请求』,如:H5请求获取地理位置,NA做完后返回N\S坐标;

后者主要是为了解决NA成本实现高的问题,多为NA主动调用H5提前注册好的方法,可称之为:『单独请求』,确保功能顺利实现。

在项目实战过程中,经常会有这种情况:回调函数既是一对一请求,也是单独调用,如:评论功能,可以页面点击弹出NA输入框发送,也可以点击底BAR上NA实现的按钮弹框发送。对于页面来讲都需要更新。站在H5角度希望NA区分,H5页面调用的评论成功和NA调用的评论成功进行区分,这样就可以把模型一和模型二区分开独立实现(同时也可以区分页面刷新的来源)。但站在NA角度来讲,不关心谁吊起的,只要评论成功,就应该去调用更新页面的H5方法。不然NA需要从调用开始就携带参数,一路到底。跟端沟通后,双方都妥协了一步,简单功能的进行了来源区分模型一实现,较为复杂的模型二实现。

API封装

API层处于JSBridge底层和业务,有些人也把它当做JSBridge的一部分,为了更好理解,我将它单独抽离出来。此处主要封装业务层调用,如下面代码。

此处多说一句:平日开发要有封装和抽离的思想,一方面减少重复代码,一方面不断抽离将代码分层,没一层可以做一些封装和扩展,可以提高代码复用性。

JSB注入时机

NA注入

我们肯定是期望JSB注入越早越好,这样不论在前端页面中任何位置都可以随时调用,NA注入JS的方法和时机都比较局限。如下表:

平台 方法 时机
IOS[UI] [self.webView stringByEvaluatingJavaScriptFromString:injectjs] webViewDidFinishLoad(会有时机问题)
IOS[wk] evaluateJavaScript:xxxx didCreateJavaScriptContext
Android webView.loadUrl("javascript:" injectjs);) OnPageFinished

网页描述页面状态的值有以下方法,根据兼容性及实现完整性,一般用DOMContentLoaded,IE9以下用readystatechange来判断页面是否加载成功。

名称 父对象 描述 兼容性
DOMContentLoaded doc 页面内容OK IE9
onload win 页面所有只要加载完成
readystatechange doc 页面加载状态:uninitialized(为初始化):对象存在但尚未初始化。loading(正在加载):对象正在加载数据。loaded(加载完毕):对象加载数据完成。interactive(交互):可以操作对象了,但还没有完全加载。complete(完成):对象已经加载完毕 IE9&IE10有实现bug

IOS的uiwebview提供了代理WebViewDidFinishLoad,WebViewDidFinishLoad 被调用时,readyState 可能处在 interactive 和 complete 两种状态,所以初始化页面直接调用会有问题。对于这个问题从NA角度可以实现一个NSObject的扩展,并实现webView:didCreateJavaScriptContext:forFrame。从H5角度可以检测页面状态,在complete之后再调用native。

IOS的didCreateJavaScriptContext和Android的OnPageFinished(the page has finished loading)均是在网页onload之前完成,所以这两个时机没有调用顺序的问题。

优点:

1)注册早,即使在页面初始化就调用端能力,也可以满足

缺点:

由于我们选择的是uiwebview如果按照上面的考虑,这样做有几点不足之处 1)监听实现成本高 2)需要NA注入,NA对于JS不熟悉,JS往往也不清楚NA逻辑,后面维护成本不可控制。

如果时间不充裕的情况下,除了NA注入,还有别的办法嘛?

JS注入

其实JS也可以在页面一开始就注入。比如在head里直接应用抽离出来的Jsbridge代码,本次8.0我们采用了这种降级方案,短时间内完成了架构搭建。

优点:

这样减小了维护成本,功能完整,提高了调用成功的几率。

缺点:

增加了页面加载解析时间会影响白屏时间。

总结

Hybrid是一种连接H5跟NA的思路,即可以快速迭代H5功能,又可以有NA的体验,是混合开发的典型开发模式。实践过程中需要根据业务形态模型来定制代码实现,注入时机也不是一成不变的可以根据业务形态来选择。

参考文献

移动混合开发中的 JSBridge

远程过程调用

你要的WebView与 JS 交互方式 都在这里了

UIWebView与WKWebView、JavaScript与OC交互

iOS中UIWebView的使用详解

UIWebView代码注入时机与姿势

Hybrid 开发

JavaScriptCore在实际项目中的使用的坑

JSBridge实战相关推荐

  1. android 加载条封装,Android基于JsBridge封装的高效带加载进度的WebView

    图片发自简书App 概述 从去年4月项目就一直用起了JsBridge,前面也针对jsBridge使用姿势介绍过一篇入门篇,<Android JsBridge实战 打造专属你的Hybrid APP ...

  2. JSBridge通信原理

    JSBridge是个啥 直接来重点,记住:JSBridge 是一个很简单的东西,更多的是一种形式.一种思想,为了解决 H5 和 Native 的双向通信. 就像我们刚接触 ajax 时,也很懵逼.其实 ...

  3. Android(2017-2018)BAT面试题整理(Android篇,含答案)

    1.四大组件  - Activity  Activity详细总结 - 简书  - Service  Carson带你学Android:一份全面 & 简洁的 Service 知识讲解攻略 - 简 ...

  4. WebView与APP交互实战记录

    WebView与APP交互 WebView与APP交互,即网页通过JSBrige调用APP的功能,APP也可以通过JSBrige调用网页提供的方法.最近刚好接触到这一块,记录一下前端侧的实际操作过程, ...

  5. Android性能优化面试题集锦,实战篇

    接触这一行也有很久了,从开始的实习到带团队,中间接触过很多人,前不久身边刚好有人去面试了阿里,抖音等这些公司还成功的面试上了,现在来分享一下面试前需要准备的知识点 很多人去面试之前,不知道会问到那些知 ...

  6. 支付宝 App 架构的原理与实战

    点击上方"开发者技术前线",选择"星标" 13:21 在看 真爱 根据公开的 2018 年移动互联网行业分析报告,目前支付宝的月活跃用户已经超过 QQ ,成为国 ...

  7. DingTalk「开发者说」第8期 钉钉微应用开发实战

    分享人:悦铭,钉钉H5微应用前端开发链路负责人 视频地址:一键回看 目录: 一.初识钉钉H5微应用 二.H5微应用原理解析 三.钉钉JSAPI使用与鉴权 四.H5微应用开发 五.H5的性能优化建议 六 ...

  8. 支付宝 App架构的原理与实战

    根据公开的 2018 年移动互联网行业分析报告,目前支付宝的月活跃用户已经超过 QQ ,成为国内第二大 App. 支付宝一开始仅仅只是一个单体应用的工具型 App,让用户可以在手机完成支付宝相关的业务 ...

  9. 直播平台源码搭建教程直播原理与web直播实战

    文章结构 直播构成 直播流程 web中直播技术 HLS协议 RTMP协议 HLS与RTMP对比 直播实战 安装nginx.nginx-rtmp-module.FFmpeg(以下操作均在mac下进行) ...

最新文章

  1. __cdecl __stdcall区别-转
  2. Android ExecutorService 的理解与使用
  3. 第一天 Requests库入门
  4. 【性能优化】 之 10053 事件
  5. 【Android开发】文本框、按钮、文本编辑框、提交登录、单选框
  6. 有关链表的小技巧,我都给你总结好了
  7. dfa算法c语言,DFA跟trie字典树实现敏感词过滤(python和c语言)
  8. 云服务器运行gpu程序很卡,请问怎样才使程序在GPU上运行?
  9. 高通发布两款耳机芯片:支持主动降噪和语音助手功能
  10. Ghost8.0分区备份与恢复详细图解
  11. ubuntu下python3及idle3的安装
  12. TensorFlow发布语音识别入门教程,附1GB数据集代码
  13. 深度学习1-tensorflow2.0自定义操作与建模方式
  14. 计算机随机储存器是什么,随机存储器是什么
  15. 【python】牛客竞赛语法入门班选择结构习题 python解法
  16. 《畅玩NAS》第8章 ZeroTier组建局域网
  17. 《Cracking the Coding Interview程序员面试金典》----猫狗收容所
  18. 考研/嵌入式/我的所思所想及其他
  19. sklearn之train_test_split()函数各参数含义
  20. MacBook 安装7zip

热门文章

  1. MATLAB入门(二)
  2. 空间滤波_第三章 灰度变换与空间滤波-(六)锐化空间滤波器之非锐化掩蔽
  3. vue 在 html 中自定义 tag
  4. pythone函数基础(11)读,写,修改EXCEL
  5. linux安装mysql(shell一键安装)
  6. Tomcat的详解和优化
  7. C语言 · 未名湖边的烦恼
  8. 用注解方式写定时任务
  9. 【黑金原创教程】【TimeQuest】【第二章】TimeQuest模型角色,网表概念,时序报告...
  10. 1. ThreadPoolExecutor的一个常用的构造方法