作为盛行已久的开发方式,Hybrid的相关介绍已经是相当普遍了。不过看到博客园里基本上都是从android或者ios的角度来讲解的,对于h5的前端来说看起来只能是一直半解。感觉有必要从前端的角度来理解和解读一下Hybrid相关内容。作为移动开发技术中不可或缺的一项,Hybrid 凭借其特有的优势牢牢的占据了一席之地。这里就不展开讨论讨论Hybrid的优劣了(想了解请查看http://www.cnblogs.com/yexiaochai/p/4921635.html)
html作为一种解释性语言,需要运行于解释器之上。我们常用的的pc端浏览器就是其一。作为移动端的一种混合开发的模式,Hybrid是如何解决这一问题的呢。这就引出了webview这一概念。hybride中的h5依托于webview容器来(对应于ios和安卓,应该有不同实现,这里就不是前端可以细说的了)解释运行。下面就看一下webview相关的部分。
一、webview的概念:
先放一段基本定义:A View that displays web pages. This class is the basis upon which you can roll your own web browser or simply display some online content within your Activity. It uses the WebKit rendering engine to display web pages and includes methods to navigate forward and backward through a history, zoom in and out, perform text searches and more.
自己的理解:webview用来展示网页的view组件,该组件是你运行自己的浏览器或者在你的线程中展示线上内容的基础。使用webkit渲染引擎来展示,并且支持前进后退等基于浏览历史,放大缩小,等更多功能。
简单来说WebView是手机中内置了一款高性能 webkit 内核浏览器,在 SDK 中封装的一个组件。不给过没有提供地址栏和导航栏,只是单纯的展示一个网页界面。
作为hybrid中的webview,相比较于native中原生组件,给人最直观的印象可能就是慢,不给过为什么这么慢呢,当然影响因素是很多的,除去一些外部因素。与webview自身的初始化和加载过程也是分不开的 ,下面来看一下webview的初始化过程
二、webview初始化
当App首次打开时,默认是并不初始化浏览器内核的;只有当创建WebView实例的时候,才会创建WebView的基础框架。所以与浏览器不同,App中打开WebView的第一步并不是建立连接,而是启动浏览器内核。
在浏览器中,我们输入地址时(甚至在之前),浏览器就可以开始加载页面。
在客户端中,客户端需要先花费时间初始化WebView完成后,才开始加载。
而这段时间,由于WebView还不存在,所有后续的过程是完全阻塞的。简单对比下两个的渲染过程:
浏览器:
webview:
可以看出在webview初始化之前,后面的步骤根本无从走起,耗时当然变长了。并且每次打开webview都要走这个过程。
三、Webview优化
从上面可以看出来webview相对浏览器来说,影响点在于webview的初始化并且阻塞后面action的这个过程,其实说来也简单(我只是说概念上,native的东西对我来说还是很难的)对应问题的两种表现形式,对应有两个方向上的优化。
3.1、减少webview初始化的消耗:
鉴于每次打开都要进行初始化webview组件的这种场景,很自然的对应起来一种设计模式即单例模式,既然每次都要走相同步骤完全可以实例化一个全局对象,从而免去其他过程的消耗。当然客户端的同学们也是这样做的: 初始完全可以建一个全局,隐藏的webview。以供使用。省去了每次的初始化过程。
不过这样的缺点也是有的,就看如何取舍了:
1、内存消耗:毕竟这货一直存在
2、页面间的跳转:毕竟只有一个webview实例,每次不同的加载需要处理的情况就比较多了。
3.2、针对阻塞的过程
每次的阻塞加载过程等待时间消耗也是很大的,就看能不能将串行的过程并行化。不过对于webview来说只能是这个样子,不过我们需要跳出仅仅局限于webview的视线,webview仅仅是移动系统的一个组件,统计存在其他组件,网络请求的过程完全可以交给native来做,也正如native一直在做的一样,完全是可以处理该过程的。例如后端同学经常要开发的mapi,就是针对native的接口。
四、webview与native的交互
上面提到native可以并行执行网络请求,以加快webview的呈现时间。又一个问题出现了,webview如何调用native接口去请求数据并获取native拿到的数据呢。除此之外还涉及到调用底层api等需求都需要借助native实现,问题的核心就是两者的交互如何实现,作为两者之间的沟通桥梁,jsbridge就应运而生了。
所谓交互无外乎两大块:一是h5调用native,另一个是native调用h5。
一、Native调用JS:
原生调用h5比较简单,可以直接调用。毕竟这里存在一个宿主的关系,脱离了webview,js就没有生命力了,而native其他功能是在webview外部的。所以反过来不能直接调用。
例如有如下代码:
1 <script type="text/javascript">
2 function myFunc() {
3 return "Text from web"
4 }
5 </script>
ios可以直接通过webview的属性调用(前端只能从方法名来看了,将js代码当做字符串读出来再解析)
NSString * result = [self.webView stringByEvaluatingJavaScriptFromString:@"myFunc()"];
2.H5网页的JS调用Native
本质还是用uiwebview的代理方法进行字段拦截(判断url的scheme),实现js间接调用native的method。
android可以直接给网页中js函数注入一个原生代码接口。
1 //相当于添加一个js回调接口可以直接通过window全局对象调用对应接口了:
2 mWebView.addJavascriptInterface(this, "native");
3 <button onClick="window.native.actionFromJs()">点击调用Native代码</button>
ios说起来也是注入只不过没有android那么方便。
在 webView 的 delegate 的 - (void)webViewDidFinishLoad:(UIWebView *)webView 里用下边的方式注入 JavaScript
1 NSString *js = @"(function() {
2 window.JSBridge = {};
3 window.JSBridge.callFunction = function(functionName, args){
4 var url = "bridge-js://invoke?";
5 var callInfo = {};
6 callInfo.functionname = functionName;
7 if (args)
8 {
9 callInfo.args = args;
10 }
11 url += JSON.stringify(callInfo);
12 var rootElm = document.documentElement;
13 var iFrame = document.createElement("IFRAME");
14 iFrame.setAttribute("src",url);
15 rootElm.appendChild(iFrame);
16 iFrame.parentNode.removeChild(iFrame);
17 };
18 return true;
19 })();";
20 [webView stringByEvaluatingJavaScriptFromString:js];
以前端的角度来看这段代码,是在 window 里创建一个叫 JSBridge 的对象,然后在里边定义一个方法 callFunction,
这个方法的作用是把两个参数打包为 JSON 字符串,然后附带到我们自定义的 URL bridge-js://invoke? 后边,
最后用 IFRAME 的方式来加载这个 URL当加载 IFRAME 的时候,就会调用 webView 的 delegate 的
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
(即特定方法来处理)方法。对url进行处理,把以约定字符开头的url后面的json解析出来,执行对应方法。
至此webview与native的交互即jsbridge的实现从前端的角度也大概就是这样子结束了,关于上面的客户端代码可以自行研究,不给过前端来看也是不太难看懂。以上就是我对webview的理解了,欢迎大家相互交流共同进步。
参考文章:WebView性能、体验分析与优化