58同城ios客户端hybrid框架探索 -买球官网平台

2顶
0踩

58同城ios客户端hybrid框架探索

2017-07-14 16:23 by 副主编 jihong10102006 评论(3) 有13062人浏览
ios
引用
作者:杜艳新,刘文军。58同城ios高级研发工程师,专注于app hybrid框架的架构研发,主导了58同城app的hybird混合研发的系统架构以及研发。
责编:唐小引,欢迎技术投稿、约稿、给文章纠错,请发送邮件至[email protected]
本文为原创文章,未经允许不得转载,更多精彩文章请

58同城ios客户端的hybrid框架在最初设计和演进的过程中,遇到了许多问题。为此,整个hybrid框架产生了很大的变化。本文作者将遇到的典型问题进行了总结,并重点介绍58 ios采用的买球软件推荐的解决方案,希望能给读者搭建自己的hybrid框架提供一些参考。

引言

hybrid app是指同时使用native与web的app。native界面具有良好的用户体验,但是不易动态改变,且开发成本较高。对于变动较大的页面,使用web来实现是一个比较好的选择,所以,目前很多主流app都采用native与web混合的方式搭建。58同城客户端上线不久即采用了hybrid方式,至今已有六七年。而ios客户端的hybrid框架在最初设计和演进的过程中,随着时间推移和业务需求的不断增加,遇到了许多问题。为了解决它们,整个hybrid框架产生了很大的变化。本文将遇到的典型问题进行了总结,并重点介绍58 ios采用的买球软件推荐的解决方案,希望能给读者搭建自己的hybrid框架一些参考。主要包括以下四个方面:

1. 通讯方式以及通讯框架

58 app最初采用的web调用native的通讯方式是ajax请求,不仅存在内存泄露问题,且native在回调给web结果时无法确定回调给哪个web view。另外,如何搭建一个简单、实用、扩展性好的hybrid框架是一个重点内容。这些内容将在通讯部分详细介绍。

2. 缓存原理及缓存框架

提升web页面响应速度的一个有效手段就是使用缓存。58 ios客户端如何对web资源进行缓存以及如何搭建hybrid缓存框架将在缓存部分介绍。

3. 性能

ios 8推出了webkit框架,核心是wkwebview,其在性能上要远优于uiwebview,并且提供了一些新的功能,但遗憾的是wkwebview不支持自定义缓存。我们经过调研和测试发现了一些从uiwebview升级到wkwebview的可行买球软件推荐的解决方案,将在性能部分重点介绍。

4. 耦合

58 ios客户端最初的hybrid框架设计过于简单,导致web载体页渐渐变得十分臃肿,继承关系十分复杂。耦合部分详细介绍了平稳解决载体页耦合问题的方案。

通讯

hybrid框架首先要考虑的问题就是web与native之间的通讯。苹果在ios 7系统推出了javascriptcore.framework框架,通过该框架可以方便地实现javascript与native的通讯工作。但是在58 app最早引入hybrid时,需要支持ios 7以下的系统版本,所以58 app并没有使用javascriptcore.framework,而是采用了更原始的方式。

传统的通讯方式(如图1所示)中,native调用javascript代码比较简单,直接使用uiwebview提供的接口stringbyevaluatingjavascriptfromstring:就可以实现。而javascript调用native的功能需要通过拦截请求的方式来实现。即javascript发送一个特殊的url请求,该请求并不是真正的网络访问请求,而是调用native功能的请求,并传递相关的参数。native端收到请求后进行判断,如果是功能调url请求则调用native的相应功能,而不进行网络访问。

图1 传统的通讯方式流程

按照上面的思路,在实现hybrid通讯时,我们需要考虑以下几个问题:

通讯方式

前端能发起请求的方法有很多种,比如使用window.open()方法、ajax请求、构造iframe等,甚至于使用img标签的src属性也可以发起请求。58 app最早是使用ajax请求来发起native调用的,这种方式在最初支撑了58 app中hybrid很长一段时间,不过却存在两个很严重的缺陷:
  • 一是内存问题:在ios 8以前,ios中内嵌web页都是通过系统提供的uiwebview来实现的。而在uiwebview中,javascript在创建xmlhttprequest对象发起ajax请求后,会存在内存泄露问题。在实现的应用中,javascript与native的交互操作是很频繁的,使用xmlhttprequest会引起比较严重的内存问题。
  • 二是拦截方法:uiwebview中的正常url请求会触发其代理方法,我们可以在其代理方法中进行拦截。但是ajax请求是一个异步的数据请求,并不会触发uiwebview的代理方法。我们需要自定义app中的nsurlcache或nsurlprocotol对象,在其中可以拦截到url请求。但是这种方式有两个问题,一个是当收到功能调用请求时,不易确定是哪个web view对象发起的调用,回调时也无法确定调用哪个web view的回调方法。为了解决这个问题,58 app的hybrid框架维护了一个web view栈,记录所有视图层中的web view,前端在发起native调用时,附加一个web view的唯一标识信息。在native需要回调javascript方法时,通过web view的唯一标识信息在web view栈中找到对应的web view。另一个是对app的框架结构有影响,hybrid中的一个简单的调用需要放在app的全局对象进行拦截处理,破坏hybrid框架的内聚性,违反面向对象设计原则。
iframe称作嵌入式框架,和框架网页类似,它可以把一个网页的框架和内容嵌入在现有的网页中。iframe是在现有的网页中增加一个可以单独载入网页的窗口,通过在html页面中创建大小为0的iframe,可以达到在用户完全无感知的情况下发起请求的目的。使用iframe发送请求的代码如下:
var iframe = document.createelement("iframe");
//设置iframe加载的页面链接
iframe.src = “ http://127.0.0.1/nativefunction?parameters=values”;
//向dom tree中添加iframe元素,以触发请求
document.body.appendchild(iframe);
//请求触发后,移除iframe
iframe.parentnode.removechild(iframe);
iframe = null;

iframe是加载一个新的页面,请求会走uiwebview的代理方法,不存在ajax请求中无法确定web view的问题。经过调研测试,多次创建和释放iframe不会存在内存泄露的问题。从这两个方面来说,使用iframe是远优于使用ajax的,比较有名的phonegap和webviewjavascriptbridge底层都是采用的iframe进行通讯的。

iframe是前端调用native方法的一个非常优秀的方案,但它也存在一些细微的局限性。58 app前端为了提升代码的复用性和方便使用native的功能,对iframe的通讯方式进行了统一封装,封装的具体实现是——在javascript代码中动态地向dom tree上添加一个大小为0的iframe,在请求发起后立刻将其移除。这个操作的前提是dom tree已经形成,也就是说在dom tree进行之前,这个方案是行不通的。浏览器解析html的详细过程为:
  • 接受网络数据;
  • 将二进制码变成字符;
  • 将字符变为unicode code points;
  • tokenizer;
  • tree constructor;
  • dom ready;
  • window ready。
dom ready事件就是dom tree创建完成后触发的。在业务开发过程中,有少量比较特殊的需求,需要在dom ready事件之前发起native功能的调用,而动态添加iframe的方法并不能满足这种需求。为此,我们对其他几种发起请求的方法进行了调查,包括前文提到的ajax请求、为window.location.href赋值、使用img标签的src属性、调用window.open()方法(各个方式的表现结果如表1所示)。

表1 五种方法效果对比

结果显示,其他几种方式除window.open()与iframe表现基本相同外,都有比较致命的缺陷。ajax有内存问题,并且无法使用web view代理拦截请求,window.location.href在连续赋值时只有一次生效,img标签不需要添加到dom tree上也可发起请求,但是无法使用web view代理拦截,并且相同的url请求只发一次。

对于在dom ready之前需要发起native调用的问题,最终采取的买球软件推荐的解决方案是尽量避免这种需求。无法避免的进行特殊处理,通过在html中添加静态的iframe来解决。

通讯协议

通讯协议是整个hybrid通讯框架的灵魂,直接影响着hybrid框架结构和整个hybrid的扩展性。为了保证尽量高的扩展性,58 app中采用了字典的格式来传递参数。一个完整的native功能调用的url如下:
“hybrid://iframe?parameter={“action”:”changetitle”,”title”:”标题”}

其中“hybrid”是native调用的标识,native端在拦截到请求后判断请求url的前缀是否为“hybrid”,如果是则调起native功能,同时阻止该请求继续进行。native功能调用的相应参数在parameter后面的json数据里,其中“action”字段指明调用哪个native功能,其余字段是调用该功能需要的参数。因为“action”字段名称的原因,后来把为web提供的native功能的处理逻辑称为action处理。

这样制定通讯协议有很强的可扩展性,native端任意增加新的hybrid接口,只要为action字段定一个新值,就可以实现,新接口需要的参数完全自定义。但是这种灵活的协议格式存在一个问题,就是开发者很难记住每种调用协议的参数字段,开发过程中需要查看文档来调用native功能,需要更长的开发时间。为此58 app首先建立了健全的协议文档,将每种调用协议都一一列举,并给出调用示例,方便前端开发者查阅。另外,native端开发了一套协议数据校验系统,该系统将每种调用协议的参数要求用xml文档表示出来,在收到native调用协议数据时,动态地解析数据内部是否符合xml文档中的要求,如果不符合则禁止调用native功能,并提示哪里不符合要求。

框架设计

依照上面的通讯协议,58 app中目前的hybrid的框架设计如图2所示。其中:

图2 hybrid框架设计

native基础服务是native端已有的一些通用的组件或接口,在native端各处都在调用,比如埋点系统、统一跳转及全局alert提示框等。这些功能在某些web页面也会需要使用到。

native hybrid框架是整个hybrid的核心部分,其内部封装了除缓存以外的所有hybrid相关的功能。native hybrid框架可大致分为web载体、hybrid处理引擎、hybrid功能接口三部分。校验系统是前文提到的在开发过程中校验协议数据格式的模块,方便前端开发者在开发过程中快速定位问题。

web载体包含web载体页和web view组件,所有的hybrid页面使用统一的web载体页。web载体页提供了所有web页面都可能会使用到的功能,而web view组件为了实现web view的一些定制需求,对系统的web view进行了继承,并重写了某些父类方法。

hybrid处理引擎负责处理web页面发起事件,是web view组件的代理对象,也是web调用native功能的通讯桥梁。前面提到的判断web请求是页面载入请求还是native功能调用请求的逻辑在hybrid处理引擎中实现。在判定请求为native功能调用请求后,hybrid处理引擎根据请求参数中的“action”字段的值对该native调用请求进行分发,找到对应的hybrid功能组件,并将参数传递给该组件,由组件进行真正的处理。

hybrid功能组件部分包含了所有开放给前端调用的功能。这些功能可以分成两类,一类是需要native基础服务支撑的,另一类是hybrid框架内部可以处理的。需要native基础服务支撑的功能,如埋点、统一跳转、native模块化组件(图片选择、登录等),本身在native端已经有可用的成熟的组件。这些hybrid功能组件所做的事是解析web页传递过来的参数,将参数转换为native组件可用的数据,并调用相应的native基础服务,将基础服务返回的数据转换格式回调给web。另一类hybrid功能组件通常是比较简单的操作,比如改变web载体页的标题和导航栏按钮、刷新或者返回等。这些组件通过代理的方式获取载体页和web view对象,对其进行相应的操作。

再看web端,前端对hybrid通讯进行了一层封装,将发送native调用请求的逻辑统一封装为一个方法,业务层需要调用native功能时调用这个方法,传入action名称、参数,即可完成调用。当需要回调时,需要先定义一个回调方法,然后在参数中将方法名带上即可。

缓存

web页面具有实时更新的特点,它为app提供了不依赖发版就能更新的能力。但是每次都请求完整的页面,增加了流量的消耗,并且界面展示依赖网络,需要更长的时间来加载,给用户比较差的体验。所以对一些常用的不需要每次都更新的内容进行缓存是很重要的。另外,web页面需要用到的某些css和javascript资源是固定不变的,可以直接内置到app包中。所以,在hybrid中,缓存是必不可少的功能。要实现hybrid缓存,需要考虑三个方面的问题,即hybrid缓存实现原理、缓存策略和hybrid缓存框架设计。

缓存实现原理

nsurlcache是ios系统提供的一个类,每个app都存在一个nsurlcache的单例对象,即使开发者没有添加任何有关nsurlcache的代码,系统也会为app创建一个默认的nsurlcache单例对象。几乎app中的所有网络请求都会调用这个单例对象的cachedresponseforrequest:方法。该方法是系统从缓存中获取数据的方法,如果缓存中有数据,通过这个方法将缓存数据返回给请求者即可,不必发送网络请求。通过使用nsurlcache的自定义子类替换默认的全局nsurlcache单例,并重写cachedresponseforrequest:方法,可以截获app内几乎所有的网络请求,并决定是否使用缓存数据。

当没有缓存可用时,我们在cachedresponseforrequest:方法中返回null。这时系统会发起网络请求,拿到请求数据后,系统会调用nsurlcache实例的storecachedresponse:forrequest:方法,将请求信息和请求得到的数据传入这个方法。app通过重写这个方法就可以达到更新缓存的目的。

58 app目前就是通过替换全局的nsurlcache对象,来实现拦截app内的url请求。在自定义nsurlcache对象的cachedresponse forrequest:方法中判断请求的url是否有对应的缓存,如果有缓存则返回缓存数据,没有则再正常走网络请求。请求完成后在store cachedresponse:forrequest:方法中将请求到的数据按需加入缓存中。

使用替换nsurlcache的方法时需要注意替换nsurlcache单例对象的时机,一定要在整个app发起任何网络请求之前替换。一旦app有了网络请求行为,nsurlcache单例对象就确定了,再去改变是无效的。

缓存策略

web的大部分内容是多变的,开发者需要根据具体的业务需求制定缓存策略。好的缓存策略可以在很大程度上弥补web页带来的加载慢和流量耗费大的问题。缓存策略的一般思路是:
  • 内置通用的资源和关键页面;
  • 按需缓存常用页面;
  • 为缓存设置版本号,根据版本号进行使用和升级。
58 app中对一些通用资源和十分重要的web页面进行了内置,防止app在首次启动时由于网络原因导致某些重要页面无法展示。在缓存使用和升级的策略上,58 app除了设置版本号以外,还针对那些已过期但还可用的缓存数据设置了缓存过期阈值。58 app的详细缓存策略如下:
  • 将通用hybrid资源(css、js文件等)和关键页面(比如业务线大类页)附带版本号内置到app的特定bundle中;
  • 在nsurlcache单例中拦截到请求后,判断该请求是否带有缓存版本号信息,如果没有,说明该页面不使用缓存,走正常网络请求;
  • 从缓存库中查找缓存数据,如果有则取出,否则到内置资源中取。如果两者都没有数据,走正常网络请求。并在请求完成后,将结果保存到缓存库中;
  • 拿到缓存或内置数据后,将请求中带的版本号v1与取到数据的版本号v2进行对比。如果v1≤v2,返回取到的数据,不再请求网络;如果v1>v2且v1 – v2小于缓存过期阈值,则先返回缓存数据以供使用,然后后台请求新的数据并存入缓存;如果v1>v2且v1 – v2大于缓存过期阈值,走正常网络请求,并在请求完成后,将结果保存到缓存库中。

缓存框架设计

58 app中hybrid的缓存框架设计如图3所示,其中:

图3 hybrid缓存框架设计

1. hybrid内置资源管理

hybrid内置资源管理模块是单独为hybrid的内置资源而创建的。hybrid内置资源单独存放在一个bundle下,这些内置资源主要包括html文件、javascript文件、css文件和图片。hybrid内置资源管理模块负责解读这个bundle,并向上提供读取内置资源的接口,该接口以资源的url和版本号为参数,按照固定的规则进行转换,查找可用的内置资源。

内置资源中除了这些web资源外,还单独内置了一份文件,用于保存url到内置资源文件名和内置资源版本号的映射表。管理模块在收到内置资源请求后,先用url到这个映射表中查找内置资源版本号,比对版本号,然后再通过映射表中查到的文件名读取相应的内置资源并返回。

2. app缓存库

58 app内有一个独立的缓存库组件,app中需要用到的缓存性质的数据都存放在这个库中,便于缓存的统一管理。缓存库内的缓存数据也有版本号的概念,完全可以满足hybrid缓存的需求,且使用十分方便。hybrid的缓存数据都使用app的缓存库来保存。

3. hybrid缓存管理器

hybrid缓存管理器是hybrid缓存相关功能的总入口,负责提供hybrid缓存数据和升级缓存数据,所有的hybrid缓存相关的策略都封装在这个模块中。全局的nsurlcache实例在收到hybrid请求时会调起hybrid缓存管理器,索取缓存数据。hybrid缓存管理器先到app的缓存库中查找可用的缓存,如果没有再到内置资源管理模块查找,如果可以查到数据,则返回查到的数据,如果查不到,则返回空。在nsurlcache的storecachedresponse:forrequest:方法中,会调用hybrid缓存管理器的缓存升级接口,将请求到的数据传入该接口。新请求到的数据会带有最新的版本号信息。缓存升级接口将新的数据和版本号信息一同存入缓存库中,以便下次使用。

性能

前面分享了58 app中hybrid的通讯框架和缓存框架,接下来介绍一下遇到的性能方面的问题及买球软件推荐的解决方案。

ajax通讯方式的内存泄露问题

前面介绍过在uiwebview中使用ajax的方式进行native功能调用,会产生内存泄露问题,《uiwebview secrets - part1 - memory leaks on xmlhttprequest》(参考资料1)中给出了一个买球软件推荐的解决方案,是在uiwebview的代理方法webviewdidfinishload:中添加如下代码:
[[nsuserdefaults standarduserdefaults] setinteger:0 forkey:@"webkitcachemodelpreferencekey"];

测试结果显示,这种方法并没有使用iframe的效果好。加上拦截方式的局限性,58 app最终选择的买球软件推荐的解决方案是使用iframe代替ajax。

uiwebview内存问题

使用过uiwebview的开发者应该都知道,uiwebview有比较严重的内存问题。苹果在ios8推出了webkit框架,其核心是wkwebview,志在取代uiwebview。wkwebview不仅解决了uiwebview的内存问题,且具有更高的稳定性和响应速度,还支持一些新的功能。使用wkwebview代替uiwebview对提升整个hybrid框架的性能会有很重大的意义。

但是,wkwebview一直存在一个问题,就是wkwebview发出的请求并不走nsurlcache的方法。这就导致我们自定义的缓存系统会整个失效,也无法再用内置资源。经过一段时间的摸索和调研,终于找到了可以实现自定义缓存的方法。主要思想是wkwebview发起的请求可以通过nsurlprotocol来拦截——将自定义的nsurlprotocol子类注册到nsurlprotocol的方式,可以像之前用nsurlcache一样使用缓存或内置数据代替请求结果返回。注册自定义nsurlprotocol的关键代码如下:
[nsurlprotocol registerclass:wbcustomprotocol.class];
class cls = nsclassfromstring(@"wkbrowsingcontextcontroller");
sel sel = nsselectorfromstring(@"registerschemeforcustomprotocol:");
if ([(id)cls respondstoselector:sel]) {
    [(id)cls performselector:sel withobject:@"http"];

代码中从第二行开始,是为了让wkwebview发起的请求可以被自定义的nsurlprotocol对象拦截而添加的。添加了上面的代码后,就可以在自定义的nsurlprotocol子类的方法中截获到wkwebview的请求和数据下载完成的事件。

以上方案解决了wkwebview无法使用自定义缓存的问题,但是这种方案还存在一些问题,且使用了苹果系统的私有api,不符合官方规定,在app中直接使用有被拒的风险。另外wkwebview还有一些其他问题(详情可参见参考资源6)。

目前,58 app正在准备接入wkwebview,但是没有决定使用这种方案来解决自定义缓存问题。我们正在逐步减少对自定义缓存的依赖程度,在前面几个版本迭代中,已经逐步去除了内置的html页面。

页面加载完成事件优化

正常的web页面加载是比较耗时的,尤其是在网络环境较差的情况下。而web的页面文件与样式表、javascript文件以及图片是分别加载的,很有可能界面元素已经渲染完成,但样式表或javascript文件还没有加载完,这时会出现布局混乱和事件不响应的情况,影响用户体验。为了不让用户看到这种情况,一般native会在加载web资源的过程中隐藏掉web view,或用loading视图遮挡住web view。等到web资源加载完成再将web view展示给用户。系统通过uiwebviewdelegate的webviewdidfinishload:方法告知native资源加载完成的事件。这个方法在页面用到的所有资源文件全部加载完成后触发。

在实用中发现,一般情况下样式表资源和javascript资源的加载速度很快,比较耗时的是图片资源(事实是native界面也存在图片加载比较慢的情况,一般native会采用异步加载图片的策略,即先将界面展示给用户,后台下载图片,下载完成后再刷新图片控件)。实际上当html、样式表和javascript文件加载完成后,整个界面就完全可以展示给用户并允许用户交互了。图片资源加载完成与否并不影响交互。

且这样的逻辑也与native异步加载图片的体验一致。在webviewdidfinishload:方法中才展示界面的策略会延长加载时间,尤其在图片很大或网络环境较差的情况下,用户可能需要多等待几倍的时间。

基于以上的考虑,58 app的hybrid框架专门为web提供了一功能接口,允许web提前通知native展示界面。该功能实现起来很简单,只需单独定义一个hybrid通讯协议,并在native端相应的处理逻辑即可。前端在开发一些图片资源比较多的页面时,提前调用该接口,可以在很大程度上提升用户体验。

耦合

58 app最初引入hybrid的时候,业务要简单许多,native没有现在这么多功能可供web调用,所以最开始设计的hybrid通讯框架也比较简单。由于使用ajax的方式进行通讯,通讯请求的拦截也要在nsurlcache中。当时也没有公用的缓存库组件,hybrid的缓存功能与内置资源一起写在单独的模块中(最初的hybrid框架如图4所示)。

图4 旧版hybrid框架设计图

这个框架在58 app中存在了很长一段时间,运行比较稳定。但是随着业务的不断增加,这个框架暴露出了一些比较严重的问题。

自定义的nsurlcache类中耦合了hybrid的业务逻辑

由于ajax方式的通讯请求要在nsurlcache中进行拦截,nsurlcache在收到请求后,不得不先判断是否是hybrid通讯请求——如果是,则需要将请求转发给hybrid通讯框架处理。另外,为了解决native回调web时无法确定web view的问题,需要维护一个web view的web view栈,app内所有的web view对象都需要存入到这个栈中。这个栈需要全局存放,但是web载体页和hybrid事件分发器都是局部对象,无法保存这个栈。考虑到nsurlcache对象与hybrid有关联且是单例,最终将这个栈保存在了nsurlcache的属性中,更加重了nsurlcache与hybrid的耦合。

nsurlcache耦合hybrid业务逻辑的问题随着iframe的引入迎刃而解,通讯请求的拦截直接转移到了hybrid事件分发器中。

nsurlcache的职责重新恢复单一,只负责缓存相关的内容。使用iframe的通讯方式,web在调用native功能的请求是在uiwebview的代理方法中截获,系统会将相应的web view通过参数传递过来,不再有无法确定web view的问题,之前的web view栈也没有必要再维护了。iframe的引入使得hybrid的通讯框架和缓存框架完全分离开来,互不干涉。

web载体页臃肿

最初的hybrid框架中,action处理的具体实现写在了web载体页中,这导致web载体页随着业务的增加变得十分臃肿,内部包含大量的action处理代码。另外,由于一些为web提供的功能是针对某些特定业务场景的,写在公用载体页中并不合适,所以开始了使用继承的方式派生出各种各样的web载体页,最终导致app内的view controller的继承关系十分混乱,继承层次最多时高达九层。

web载体页耦合action处理的问题是业务逐步累积的结果,当决定要重构的时候,这里的逻辑已经变得十分庞杂。强行将这两部分剥离困难很大,一方面代码太多,工作量大,另一方面逻辑过于复杂,稍有不慎就会引起bug。解决web载体页的问题采取的方案分成两部分。

搭建新hybrid框架,逐步淘汰老的框架。

为了解决web载体页臃肿的问题,更为了提供对ios 8 webkit框架的支持,提升hybrid性能,58 ios客户端重新搭建了一套新的hybrid框架。新hybrid框架严格按照图2所示的结构进行实现。新增的业务使用新的hybrid框架,并逐步将老的业务切换到新的框架上来。

在图2的框架中,为了在增加新的hybrid功能组件时整体框架满足开闭原则,需要解除hybrid处理引擎对hybrid功能组件的依赖。这里采用的设计是,处理引擎不主动添加组件,而是提供全局的注册接口,内部保存一份共享的注册表。各个功能组件在load方法中主动向处理引擎中注册action名称、功能组件的类名及方法。处理引擎在运行时动态地查阅注册表,找到action对应的类名和方法,生成功能组件的实例,并调用相应的处理方法。

按照上面的设计,一个web界面的完整运行流程为:
  • 程序开始运行,生成全局的hybrid共享注册表(action名称到类名及方法名的映射),各个hybrid功能组件向注册表中注册action名称;
  • 需要使用web页,应用程序生成web载体页;
  • web载体页生成web view实例和hybrid处理引擎实例,并强持有这两个实例,将处理引擎实例设为web view实例的代理对象,将自身设为处理引擎的代理对象;
  • web页发起native调用请求;
  • 处理引擎实例截获native调用请求,并在共享注册表中查到可以处理本次请求的类名和方法名;
  • 处理引擎生成查找到的hybrid功能组件类的实例,强持有之,并将自身的代理对象设为功能组件的代理对象,调用该实例的处理方法;
  • hybrid功能组件解析全部的调用参数,处理请求,并通过代理对象将处理结果回调给web页。
  • web页生命周期完成,释放web view实例、hybrid处理引擎实例、hybrid引擎实例释放所有的hybrid功能组件实例。
通过使用组件主动注册和运行时动态查找的方式,固化了新增组件的流程,保证已有代码的完备性,使hybrid框架在增加新的功能上严格遵守开闭原则。

关于注册表,目前是采用全局共享的方式保存。在最初设计时,还有另一种动态组合注册的方案。该方案不使用共享的注册表,而是每一个hybrid处理引擎保存一份独立的注册表,在web载体页生成hybrid处理引擎的时候,根据业务场景选择部分hybrid功能组件注册到处理引擎中。这种动态组合的方案对功能组件的组合进行了细化,每个web载体页对象根据各自的业务场景按需注册组件。动态组合注册的方案考虑的主要问题是:在hybrid框架中,有许多专用hybrid功能组件,大部分web页并不需要使用这些组件,另外58 app被拆分为主app和多个业务线共同维护和开发,有一些hybrid功能组件是业务线独有的,其他业务线并不需要使用。动态组合注册的方案可以达到隔离业务线的目的,同时不使用全局注册表,在不使用web页时不占用内存资源,也减小了单张注册表的大小。

现在的hybrid框架采用全局注册方案,而没有采用动态组合注册的方案,原因是动态组合注册方案需要在生成web载体页时区分业务场景,web页的使用方必须提供需要注册的组件信息,而这是比较困难的,也加大了调用方调用web页的复杂程度。另外,大部分组件是否会被使用都是处于模糊状态,并不能保证使用或者不使用,这种模糊性越大,使用动态组合注册方案的意义也就越小。

最终58 app采用了全局注册的方案,虽然注册表体积较大,但是由于使用散列算法,并不会增加查找的复杂度而影响性能,同时避免了调用方需要区分业务场景的不便,简化了后续的开发成本。

改造原hybrid框架,防止web载体页进一步扩大

为了保证业务逻辑的稳定,不能直接淘汰老的hybrid框架,老业务中会有一部分新的需求需要在老的框架上继续扩展。为了防止老的web载体页因为这些新需求进一步扩大,决定将原hybrid通讯框架改装为双向支持的结构。在保持原web功能接口处理逻辑不变的情况下,支持以组件的方式新增web功能接口。具体的实现是在hybrid事件分发器中也添加了与新hybrid框架的处理引擎相似的逻辑,增加了全局共享注册表,支持组件向其中注册。在分发处理中添加了查找和调用注册组件的逻辑。改造后的hybrid事件分发器在收到action请求后,先按老的逻辑进行分发,如果分发成功则调用载体页的处理逻辑,如果分发失败,则查找共享注册表,找到可以处理该action的组件进行实例化,并调用相应的处理逻辑。

虽然web载体页由于继承的关系变得很分散,但是事件分发器一直只有一份,逻辑比较集中。进了这样的改造后,有效扼制了web载体的进一步扩大,也不再需要使用继承来复用action处理逻辑了。

总结

本文重点介绍了58 app中hybrid框架在设计和发展过程中遇到的问题及采用的买球软件推荐的解决方案。目前的hybrid框架是一个比较简单实用的框架,前端没有对native提供的功能进行一一封装,这样可以在扩展新action协议时尽量少地改动代码。且封装层次少,执行效率比较高。目前的hybrid框架依然很友好地支撑着58业务的发展,所以暂时还没引入javascriptcore.framework。在未来的发展中,会逐步引入新技术,搭建更好的hybrid。

参考资料
    • 大小: 41.2 kb
    • 大小: 38.9 kb
    • 大小: 94.5 kb
    • 大小: 67.7 kb
    • 大小: 56.4 kb
    2
    0
    评论 共 3 条 请登录后发表评论
    3 楼 2017-07-18 09:00
    噶备择!!!
    2 楼 2017-07-17 09:53
    跟没说一样    
    1 楼 2017-07-17 09:47
    从头到尾也不知道用的什么hybird框架。

    发表评论

    您还没有登录,请您登录后再发表评论

    相关推荐

    • 58 同城 ios 客户端的 hybrid 框架在最初设计和演进的过程中,遇到了许多问题。为此,整个 hybrid 框架产生了很大的变化。本文作者将遇到的典型问题进行了总结,并重点介绍 58 ios 采用的买球软件推荐的解决方案,希望能给读者搭建...

    • 58 同城 ios 客户端随着业务量和用户量的持续增长,架构也是不断受到挑战,采用什么样的架构去适应这些变化,对技术人员来说也是一大考验。58 app 的架构先后经历了纯 native、引入 hybrid 框架、底层服务组件化、...

    • 1. 通讯方式以及通讯框架 58 app 最初采用的 web ...另外,如何搭建一个简单、实用、扩展性好的 hybrid 框架是一个重点内容。这些内容将在通讯部分详细介绍。 2. 缓存原理及缓存框架 提升 web 页面响应

    • 58 同城 ios 客户端组件化演变历程

    • 58 同城 ios 客户端随着业务量和用户量的持续增长,架构也是不断受到挑战,采用什么样的架构去适应这些变化,对技术人员来说也是一大考验。58 app 的架构先后经历了纯 native、引入 hybrid 框架、底层服务组件化、...

    • 作者简介: 彭飞,58 同城 ios 客户端架构师。专注于新技术的研发,主要负责 app 端组件化架构以及性能优化,并已推广 react native 在 58 同城 app 中业务场景的应用。在 mdcc 2016 ios 开发峰会上分享《58 同城 ...

    • 【导读】58 赶集集团旗下拥有多个 app,且全部使用同一套账号体系,通过 passport 部门提供的接口进行通信。经过多年迭代,各个 app 中关于 passport 的功能均出现了一些流程和接口上的差异。为了提高账号安全,统一...

    • 2017,顶着刘海的 iphone x 带着“史上升级变动最大”的 ios 11,依然碎片化严重的 android 带着“更快、更强大、更安全” 的8.0来到我们面前。回首过去十年,从诺记的 symbian、摩托罗拉的 linux、苹果的 ios、微软...

    • 随着移动浪潮的兴起,各种app层出不穷,极速的业务扩展提升了团队对开发效率的要求,这个时候使用ios&andriod开发一个app似乎成本有点过高了,而h5的低成本、...

    • 第一版 app 架构 早在 2010 年 58 同城诞生第一版 ios 客户端,按照传统的 mvc 模式去设计,纯 native 页面,这时的功能较为简单,架构也是如此,从上至下分为 ui 展现、业务逻辑、数据访问三层,如图 1 所示。...

    • 58同城ios客户端hybrid框架探索(杜艳新,刘文军) 58同城ios客户端的hybrid框架在最初设计和演进的过程中,遇到了许多问题。为此,整个hybrid框架产生了很大的变化。本文作者将遇到的典型问题进行了总结,并...

    • 58 同城 美团 滴滴出行 知乎 哔哩哔哩 新浪微博 搜狐 极光开发者(jpush) 开源中国 当当 豆瓣 饿了么 网易 七牛 环信 yy 爱奇艺 聚美优品 美丽联合集团...

    • 20050126 postgresql数据库里面的timestamp(时间戳)类型的数据需要用timestamp::varchar形式输出,才能正常显示在网页上。

    • 1。  java中对数据库进行update,delete,insert的时候都得用commit()方法确认操作。2。 将bigdecimal对象转换为string类型的方法     string ninzu = "";     bigdecimal bninzu =(bigdecimal)request.getattribute("ninzu");      if(bninzu != nu

    • 在 linux 上安装 postgresql2004年4月12日  作者:nios  matrix-与java共舞           from nios的blog: http://www.matrix.org.cn/blog/nios/在 linux 上安装 postgre

    • jsp连接数据库大全cammsia 转贴  (参与分:6144,专家分:2160)   发表:2005-4-9 上午6:09   版本:1.0   阅读:523次 正文现在有好多初学jsp的网友经常会问数据库怎么连接啊,怎么老出错啊?所以我集中的在这写篇文章供大家参考,其实这种把数据库逻辑全部放在jsp里未必是好的做法,但是有利于初学者学习,所以我就这样做了,当大家学到一定程度的时候,可以考虑用m

    • transact_sql小手册             wtadminxjeri [原作]      原文位置 : http://dev.csdn.net/article/29/29381.shtm --语 句 功 能--数据操作select --从数据库表中检索数据行和列insert --向数据库表添加新数据行delete --从数据库表中删除数据行update --更新数据库表中的数据--数据

    • 1 。 postgresql中,将输出的值转换为char型:count(jyoutaiflg)::varchar as cflg 

    global site tag (gtag.js) - google analytics