环评在那个网站做,网站上的站点地图链接是这么做的,好用的在线代理,网站建设管理经验做法前言
H5 VS Native 一直是前端技术界争执不下的话题。react、vue等技术栈引领着纯H5开发#xff0c;rn、week则倡导原生体验。但在项目实战中#xff0c;经常会选择一个中立的方案#xff1a;混合开发。大众称呼#xff1a;Hybrid。
本人目前从事新闻类产品研发#xff…前言
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方法梳理
平台方法备注AndroidshouldOverrideUrlLoadingscheme拦截方法AndroidaddJavascriptInterfaceAPIAndroidonJsAlert()、onJsConfirm()、onJsPromptIOS拦截URLIOS(UIwebview)JavaScriptCoreAPI方法IOS7 支持IOS(WKwebview)window.webkit.messageHandlersAPi方法IOS8 支持
NA调用H5方法梳理
平台方法备注Androidloadurl()AndroidevaluateJavascript()Android 4.4 IOS(UIwebview)stringByEvaluatingJavaScriptFromStringIOS(UIwebview)JavaScriptCoreIOS7.0 IOS(Wkwebview)evaluateJavaScript:javaScriptStringiOS8.0
通过上面两端调用方法梳理表不难分析出URL拦截 执行JS是 安卓和IOS比较通用且兼容性较好的方案。我们混合开发的基础正是基于这种方法来实现的。
常规混合开发思路
H5和NA通信方面最简单直接的思路是NA拦截H5的URL获取消息一般是通过修改iframe的src来实现 ①经过业务处理NA执行JS在H5侧提前注册好的全局方法③回调通知H5如下图。 H5代码实现如下
html
...
bodydiv classcontentXXXXX/div
/bodyscript// ① 注册全局函数,以便端调用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...
/htmlAndroid原理大致如下
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对应关系不明确。有一些调用看起来像是回调但没有把他们放到一起导致代码散乱难以维护。如上面demosendschema(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服务H5H5服务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:xxxxdidCreateJavaScriptContextAndroidwebView.loadUrl(javascript: injectjs);OnPageFinished
网页描述页面状态的值有以下方法根据兼容性及实现完整性一般用DOMContentLoadedIE9以下用readystatechange来判断页面是否加载成功。
名称父对象描述兼容性DOMContentLoadeddoc页面内容OKIE9 onloadwin页面所有只要加载完成readystatechangedoc页面加载状态uninitialized(为初始化)对象存在但尚未初始化。loading(正在加载)对象正在加载数据。loaded(加载完毕)对象加载数据完成。interactive(交互)可以操作对象了但还没有完全加载。complete(完成)对象已经加载完毕IE9IE10有实现bug
IOS的uiwebview提供了代理WebViewDidFinishLoadWebViewDidFinishLoad 被调用时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在实际项目中的使用的坑