周辉 – 大众点评客户端的混合开发尝试

7 篇文章 0 订阅
5 篇文章 0 订阅

http://2014.ioscon.org/?p=380

【周辉】:大家好!今天非常荣幸能跟这么多iOS爱好者和开发者,以及所有的朋友一起分享我们在大众点评客户端混合开发方面的尝试。开始演讲之前我想先给大家看一下大众点评在混合框架方面做的尝试和使用情况。
    
    现在大众点评已经有三款应用使用到了混合框架,大众点评客户端不知道大家有没有用过团购模块,团购模块全部用H5写的,用到的混合框架可以跟H5交互,而且这块儿的体验优化得非常好,大家有兴趣的话可以看一下,其他模块是Native写的,你可以看一下这个体验,我可以自信地告诉大家在这里H5和Native的体验是分不出区别的;CRM这个软件全部由我们混合框架模板生成的,中间所有的内容,除了和Native相关的小功能之外,绝大部分是我们的H5团队开发的;还有点评管家这款商户端的应用,和主App类似,也是混合了Native和H5。
     
     我们今天的分享大概分享为这四个部分:我们在面临的挑战,面对混合框架的开发思路,此外还有收集了很多同学的问题之后总结的难点,最后介绍一下我们后续的思路和未来的展望。
    
    首先看一下挑战,对于绝大部分公司来说,为什么要用混合框架,很大方面是人力方面的挑战,尤其是客户端开发团队的人力压力。对于我们大众点评来说,由于采用了事业部制,每个事业部团队都需要很多人员,所以开发人员就非常吃力。我们希望把H5开发融入到客户端开发,为他们提供更好的开发平台。
    
    另外一个是在线上发布的挑战。首当其冲的是iOS审核开发时间非常长,两三个星期都是常有的事。还有一个是安卓方法数限制,当你的安卓客户端规模发展到足够大,你软件中定义的方法数很容易就到达了方法数瓶颈。

     为了解决线上发布方面的挑战,我们进行了一些尝试。这里有我们的一些总结,对于iOS线上应用我们可以用WAX或者NU进行线上App的修复。但是线上App的修复实际上是用特殊语言来进行iOS动态语言的替换,开发难度和测试难度非常之高。
    
    安卓开发方面,根据我们实践,我们建议使用插件的方式进行开发。使用插件能够把我们一个APP划分到足够小,各个相关模式可以使用动态下载的方式,把Native Load进来。具体参考我们公司屠毅敏同学的开源地址:https://github.com/mmin18/Create-a-More-Flexible-App。还有一个是叫做NU的开源框架:http://programming.nu/。

     在挑战方面以目前的现状来看,我们部分的团队已经使用PhonGap开发了CRM软件。你可以用一条命令就生成一个模块App,非常重要的是它有Web容器,搭建了H5和Native交互的桥,与之相适应的是它提供了丰富的插件库供大家使用。
    
    从某种意义上来说,PhoneGep就相当于浏览器+JSBridge。我们在使用中发现它有设计方面的缺陷,一个很重要的缺陷是,它的程序模块是单页面。单页面本身问题并不大,H5的开发者早就适应了对每个页面的单独开发,中间加以跳转导航的形式,在开发每个页面的时候需要处理页面被跳转之后数据的缓存,跳回来后还要重新加载回来。但是在引入到Native之后,我们发现单页面就是很大的桎梏。因为我们后面会遇到在PhoneGap应用中穿插一些Native页面,我们就希望从H5页面到Native页面再跳转到一个新的H5页面。这种需求对于单页面来说这是很难做到的。
    
    还有一个问题是页面第一次打开等待时间过长,当然可以使用一些缓存方面的机制,来对页面进行缓存加速。但是我们希望它表现得很更好,我们希望大家在使用H5内容的时候,根本感觉不到它是从网上下载下来的内容,也希望在没有网络时候也能够跟Native一样的使用。

     基于以上两个缺陷,我们确定了一个改造思路。我们希望在PhoneGap的模块基础上增加两个重要的功能:一个是多页面,另一个是页面加速。
    
    所以,我今天给大家分享的重点,是对于PhoneGep多页面和页面加速改造方面的思路分析和问题总结。

     首先让我们来关注第一个难点-多页面。提到多页面,这里有一个很大的问题,就是打开方式。打开方式对于单页面来说很简单,一个URL就可以跳转了,而对多页面来说就涉及到新页面打开的问题。另外有了多页面之后又会遇到多页面之间的通信问题。最后我还会为大家分享我们遇到的其他的问题。
   
    我们先来看页面打开方式。

    第一种方式是对URL进行解析。这是两个标准的URL,它的前面是scheme,中间有指向资源的路径,最后是参数列表。为什么第二个url的前面写的是EFTE?这是我们为我们的混合框架的命名。EFTE是古英语中蝾螈的意思,我们希望混合框架就像两栖两栖动物一样混迹在H5和Native的世界。为什么没有用现代英语呢?是因为我们在注册域名的时候发现现代所有的两栖动物的域名都被注册了,所以只能到古英语中去找了。在iOS中有一个URL的拦截器。我们定义,以http开头的URL在本页面进行跳转,如果是以EFTE开头的URL就让它在多页面中打开。
    
    第二种方式是使用JSBridge,它也是一种非常通用的做法。这是我们自己定义的标志,它的host就是下行杠,所有WEB里面会有一个对URL的拦截器,会截取到从H5 call过来的URL。我简要介绍一下它的过程,首先在H5的页面中为了不影响H5的体验,可以在H5中创造一个隐藏的iFrame,然后让这个iFrame打开一个url,有了URL之后Native容器中的拦截器就会起作用,于是我们就可以对URL进行分析。在这个例子中,我们分析到它的method为OpenPage,我们就可以利用OpenPage之前注册好的Native方法来继续执行。
    
    在多页面方面还有一个很重要的打开方式。在大众点评内部,我们为所有的页面都定义了一个URL,我们希望把所有的页面跳转全部用URL来实现。这样做有一个很大的好处,就是如果用URL的话,我们可以直接从页面A跳转到页面C,而不用从A经过B一直到C。我们先做的URL的划分方式,其后才引入了混合框架。在引入混合框架之后,我们发现它很好的契合了我们混合框架中的思想,所有的页面都是用URL来表示的。我们为Native页面也定义了新的方式,我们为它注册了一个新的URL Scheme,叫做dianping。
    
    我们使用映射表绑定Native的Class,其实我们的映射关系是可以动态改变的。想想看,如果我们可以在线上动态调整映射表,是不是就可以实现产品经理希望做到的灰度发布和AB Test等功能了?

     然后是通信问题。

     我这里给大家画了两幅示意图,我们解决方案是为每一个页面都架设广播发射器和广播接收器。Native页面有原生广播机制,WEB页面需要借助JSBridge的帮助来实现广播功能。所以我们解决方式是使用广播或者其他的Native方式来进行通信。
    
    下面举一个例子,这个多页面通信实例来自大众点评的CRM软件。我们有这样一个需求,在页面A点击到银行名称之后跳转到页面B选择银行的列表页面,选完之后再跳回页面A。我们的解决思路是在第一个页面A为它注册一个广播监听,页面B选择完之后发出广播,页面A收到广播就可以更新数据,并且返回。
    
    在多页面开发中我们遇到一些问题,首先一个是安卓独享的问题:页面在后台被卸载之后如何接收消息?因为安卓所有的页面都是独立存在的,而且也是被系统所管理的,所以在页面内存不足或者其他机制的作用下,有可能页面在后台被隐藏的时候就会被卸载掉,这时候我们发出的广播它就没有办法接收了。对这个问题,我们给出的解决方案是使用数据中心,我们大众点评现在使用的是一款叫做EventBus的第三方开源工具,这个模块可以独立于页面生命周期之外起作用,用它来监听数据,并且在后台页面恢复回来的时候再对它进行通知,就可以解决这样的问题。
    
    为了处理页面被后台卸载的问题,还会遇到的问题数据缓存的问题,所以这里就会需要Local cache的插件。当然H5的同学也可以使用http自带的Local cache机制。我们希望在页面卸载的时候对页面内容进行现场保护。

     这中间我们又引用了页面生命周期的概念。我们为它定义了一系列生命周期中间的方法,对H5的页面进行通知,这里最重要的是有显示和隐藏的事件和现场保护的事件,这里展示一张我们定义的生命周期图,从上到下是页面从启动到销毁的过程。它主要参考了安卓的生命周期图,也兼顾了iOS的生命周期。在这张图中涉及到页面显隐事件和现场保护事件,这对多页面是非常重要的。
    
     此外,大家刚刚看到我们内部CRM的页面是组合页面,它使用到常见的Tab框架。在混合开发中,我们思路可以开拓一点,并不是说做混合开发就一定要用纯WEB技术来开发,我们也可以为混合App提供定制化的Native代码。我们可以在本地或者线上为它配置每一个Tab所需要的的URL、Icon和显示的文字。这样每个Web页面就可以被我们Native代码驱使,进行切换。
    
    接下来进入我们另一个核心的重点,也就是对于URL页面的加速问题。我们给出的思路是使用静态包的方式。我们使用增量发布方式,同样我们也会有相应问题的分享。首先看一个流程,这个流程是非常典型的,在浏览器中打开一个URL的完整流程图。我们发现开发页面主要消耗的时间是在下载HTM页面、图片、多媒体资源方面。我们的加速思路是,可以不可以把这些静态的资源下载到本地,这样的话就可以迅速地进行留存。但是在使用本地资源的时候我们遇到最重要的课题就是,如何来使用和管理本地资源,以及如何开发如何发布,到最后的如何升级的问题。
    
    我们现在再回过头来看看刚刚提到的URL的两个例子。我们发现可以把URL分解为两部分:本地文件路径和参数列表。有了文件路径之后我们可以直接访问到文件所在的位置,为什么我在这个地方要对它在package和path之间加0.01,我们希望用版本的方式对静态包进行版本管理,我们希望它是透明化的管理。由包管理工具来决定使用哪个版本的包,而页面开发人员只需要关心包名和具体的页面路径就可以了。
    
    在静态包的开发和发布方面,我们有这样的思路:第一个是把静态包定义为一个一个Package。它的好处是各个团队之间也可以独立开发。我们期望对它进行持续集成,能够自动化的打包和发布。进行静态包的下载加速,最后希望能用增量的方式节省流量。下面给大家看一下我们在这方面开发和发布的示意图。
    
    这张图的左上角是我们提供的一些H5的开发框架和开发工具。我们内部使用了Cortex来管理模块依赖,这是我们的前端团队开发的,它是开源的,大家可以参考一下。我们使用到了git进行代码管理,有了git之后我们使用持续集成,对代码模块进行编译和打包。最后我们进行包的管理工作。对外提供两个接口,一个是CDN包地址管理,还有一个是包管理后台,供APP请求包更新。
    
    关于静态包的发布,给大家看一个我们做的很漂亮的管理页面。这是我们静态包的发布网站,这个网站我们会对APP进行Package的划分,当我们点进去一个Package的时候,可以对它需要发布的版本进行选择。我们内部还提供一个解析器,提供包的渠道划分功能,例如,如果你在北京可以你下载0.01版本,在上海就下载0.01版本。有了版本的发布工具之后,我们客户端方面可以在启动的时候进行下载,静态包模块的动作,下载完成之后可以对用户进行提示。
    
    刚刚提到静态包使用到了增量更新,在增量更新方面大概有这样的流程,首先我们在使用静态包之前会对本地的静态包进行校验,如果校验通过服务器就会下发增量更新包,如果校验失败的话就会下发全包,让被篡改的包恢复到原样。下面是两种情况的示意图,第一个是在版本增量更新的示意图,左边是客户端,右边是服务端。当我们发出静态包的更新请求的时候,我们会把它和MD5的校验结果发给服务端,服务端就会校验这个MD5是不是正确的。然后服务端会生成一个增量包,然后对增量包进行压缩和加密,最后供客户端来进行下载和解压,下载解压之后我们再执行增量更新的操作,最后还会进行MD5的再次校验,确保本地端和服务端是一模一样的。另一种情况的示意图如下,如果本地包被篡改,在发出版本更新请求的时候,服务端就会发现它MD5校验不通过,这时候就可以直接把0.02的全包压缩让客户端进行升级,升级之后进行MD5的校验,从而实现增量包和全包的升级过程。
    
    下面画了一个客户端更新的流程图,为了安全我们使用到了https,以及多次的MD5的校验,还有签名的机制对这个包进行校验。它的更新是在程序打开时,恢复时和登录切换的时候。另外需要提的是,我们从腾讯AK Team那里学到的,使用断点续传和重试机制,保证提供更优化的下载流程。
    
    讲这么多在实际开发中肯定会遇到一些问题,接下来分享一些问题。第一个问题是当我们打开页面容器的时候,有时候会发现静态包竟然没有下载完毕。我们给出的解决方案是在程序发布的时候,把最新的静态包打包进来,当我们程序启动的时候,如果发现内部没有静态包,我们就从将打包的最新的静态包拷贝到指定的位置,确保程序打开的时候就可以使用最新的静态包,规避下载没有完成的问题。
    
    包更新完成之后要发出通知页面进行刷新,这方面很有讲究的,如果用户正在页面中填写资料,程序后台突然发生出更新完毕的通知,大家可以考虑一下如何确保刷新的时候数据不要妨碍用户操作体验。

     还有一个是常见的问题是跨域问题,我们使用到内部的依赖管理,在生成静态包的时候把它依赖所有的静态资源全部打包下来。 网络请求方面,我们是使用到了Native插件进行网络请求,所以在页面A中想请求任何一个域名的数据都是可以的,因为它已经跳脱了web容器的限制。

     在web开发调试方面。我们推荐使用iOS模拟器进行web开发,因为iOS这块儿确实做得很好。Safari对iOS真机和模拟器的Web页面提供了原生的调试支持,我们只需要在设置中把它的调试开关打开就好了。在安卓方面,则只有在4.2以及以上的版本上,才能在Chrome中进行直接调试。安卓还有其他的一些调试方案,这里提供给大家。
    
    接下来分享一些在安卓适配中遇到的问题。安卓在Web兼容方面做得确实不太好。首先是它有URL长度的限制,最长不能超过2K,所以在我们使用Local cache或者进行大数据传输的时候都会遇到问题。我们推荐两种解决方案:一种是使用分割的方式分段传输数据,这种方式实现起来非常困难。还有一种方式是,使用静态文件。一般要传递给Native的大数据是一些静态数据,我们可以把静态数据打包在静态包中,当Native需要使用这些静态数据的时候,直接访问它的文件路径即可。
    
    在某些系统版本的web页面中有时候出现间歇性无法点击的问题。我们解决的方案是对安卓页面最好注入这样的代码,实现原理就是在页面启动的时候旋转零度,这样Web内部就会重新计算web的页面大小,从而允许你点击。
    
    混合框架目前在我们公司内部是处于内部推广的阶段,我们内部应用已经使用到App的混合框架,出于谨慎的态度,还不敢直接开源给大家。我们内部的源码在这里给出一个地址,大家可以看。对于框架的划分,给大家看一下我们画的简图,我们把它封装成三大块:第一个是我们核心的Framework,web容器、数据中心等等。APP模块方面我们目前只提供了tab模板,其他的模板还需要进行额外的工作。我们已经实现了绝大部分插件的开发,这块儿我们以后还会进行努力的工作。

    谢谢大家!

    
    【提问】:你好!你提到在Android上碰到的UCL长度的问题,也碰到蛮多障碍,不知道你有没有注意到在iOS上,安卓上虽然不熟,但不会有类似的,包括长数据攻击的问题,我自己有过类似的混合框架,我所有的页面进行注入只有两种方式:一种是直接通过模板,本地嵌入模板。第二种是页面加载完成之后,直接向页面调用数据。
    
    【周辉】:你刚刚提到这个非常好,我们内部也是这样做的,我们刚刚提到的这些只是一些思路的分享,更细节的跟你的思路是相似的,从Native到H5是使用截获的方式,如果从H5到Native使用了JS注入的方式,你刚刚提到用模板的方式把它能够转换成JS代码,我们现在也是这样做的。
    
    【提问】:我们有一个App model的情况下,一个页面相当于buch,可以直接替换。
    【周辉】:我们也有这种解决方案,我们也是提供一些特殊的关键字,在打开页面的时候我们对它直接进行切换。

    

    【提问】:还有一个问题是datamodel方面是怎样的?

    
    【周辉】:在H5方面我们使用的是JSON,在Native和H5之间我们有做数据转换的工作。

    【提问】:你们有没有考虑过Native直接使用JSON?

    【周辉】:我们App的第一版本是这样做的,我们看到世面上很多的应用使用了JSON,所以在这块儿的JSON应用是非常成熟的。我推荐大家能够使用,因为大众点评历史的原因一直没有使用JSON,此外我们也有自己安全等方面的考虑。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值