百度轻应用已经推出半年多了,在市场上已有一定的影响力。但同时我们发现,开发者之所以使用轻应用,看中的是百度的渠道分发能力,而在体验上和Native app有比较大的差异,这个问题在应用复杂以后变得尤为明显。
前些年,在做移动web站点时,我们都会追求一些无刷新的跳转、换页效果,但后来发现这样做在市场顶级的手机上尚不能运行流畅,就更不用说那些相对廉价和低端的智能机了。所以近一年来,大家倾向于不在移动web站点中采用复杂的特效,只在最关键的部分,用最小的代价来实现。
现在你已经很少在Android中看到用Javascript实现的换页效果了;又比如百度的多数产品线放弃了Javascript实现的局部滚动效果,转而采用原生滚动实现,或者只应用到页面的少数交互元素中。与此同时,很多公司已经将重点精力投入到Native原生应用的开发中,减缓了在web端的投入。
而对我们,当然希望轻应用的体验和交互能够和Native媲美,那我们要做什么才能达到这个目标呢?做什么才是最有效的呢?
我们首先看到了w3c的(一份报告),这是来自缩短与本地应用差距(closing the gap with native) 的任务组产出。这份报告指出了webapp相对Native app的优势与劣势。
我们接着寻找了很多份数据,概括得最好、数据量也很丰富的是Developer Economics的这份调研数据详细说说其中两组关键的数据:
经过对6000+开发者的调查,他们发现,46%的开发者认为性能是影响他们选择的因素,这也是在所有因素中占比最高的,其次是API的缺乏,占37%,再次是与Native元素的整合,占29%。这和做为移动开发者的我们的主观感受相当一致,性能是Webapp的巨大瓶颈,不流畅的app一定不受用户喜欢;其次是一些功能无法用web实现,比如语音、定位等;再次,即使这些功能可以用Native原生代码实现,也无法将他们整合到Webapp中。
而第二个问题是第一个问题的延展:抛开性能问题,webapp的能力和Native app相比有多大差距呢?他们调研了Google Play中的30339个app。如果只使用HTML5技术,能实现37%的app,如果使用Phonegap,能实现49%,如果使用Appcelerator,能实现63%。这个结果让人觉得很乐观,只要我们能用一些技术,将设备的一些基础功能开放到web中,能大幅提高webapp的应用范围。
与此同时,我们调研了百度内部的16款webapp,希望了解做为国内一线厂商的工程师,他们认为webapp的瓶颈所在。结论出奇的简单和一致,有87.5%的工程师认为,在自己的业务场景中,转场和动画问题是最首要的问题。
其实移动web站点经过这些年的发展(我在2010年就参与过百度内部移动前端基础库的研发工作),工程师和产品经理已经很清晰地认识到什么交互是web无法实现的,比如3D转屏,动态的卡片式操作,他们会灵活地调整产品设计,避免这些交互。
但有一点是无法避开的,那就是页面的转场动画。web是基于链接构建的,从一个页面点击链接跳转到另一个页面,如果通过有刷新的打开方式,用户要面对一个空白的页面等待,如果通过无刷新的方式,用Javascript移入DOM节点,在Demo状态下能做得很好,但一旦产品化,就要冒着很高的性能风险:页面太大,可能转场不流畅甚至浏览器crash;单个webview中DOM节点过多,同时还要保存多个场景的状态,会占用过多内存,在使用的过程中会变得越来越卡;更不用提那些低端机型和低端浏览器了……
所以,我们面临的首要目标是,如何能webapp的转场能像Native那般流畅。
在说实现思路之前,要先给大家说说轻应用的业务场景,以便理解。
轻应用有两大入口:移动搜索和百度搜索app,接入轻应用的开发者他们最为看重的就是这个入口,尤其看重百度搜索app的入口。因为在普通浏览器中,通过移动搜索到达轻应用,这跟普通的webapp没有本质区别;而在百度搜索app中,由于轻应用的运行环境是百度框,我们能为轻应用的开发者暴露一些Native API的接口,这就是Clouda API,包括提供设备能力的Device API,提供百度云服务能力的MBASS API。
由于轻应用的使用场景天然和Native结合得很好,我们很自然地想到利用Native技术来做转场。相对用web技术,这条路更为现实:市面上有很多转场相关的Javascript库,百度内部也有若干实现,但最后产品化以后是否成功,完全取决于具体业务开发者。开发者能否有效地缩减DOM数量和层次,能否找到性价比较高的方案是关键。而轻应用开发者的水平良莠不齐,用web技术来解决此问题显然不可行。
而用Native技术来做转场,我们也经历了一些波折。最初,我们重点考虑的是类似Appcelerator的方案,不过我们不像Appcelerator那么激进地将Javascript编译成Native code,而希望暴露一组基本的Native API,供前端工程师调用以实现流畅的app效果。但这样随之而来的问题是,开发者的可定制性会变得特别差,极其依赖Native API,而且开发感受也会很糟糕,毕竟他操作的不再是DOM,而是一个Java like的东西。这个方案很快被我们否定。
根据前面所说的调研,我很快发现其实大家对普通的web元素,并没有太多性能方面的担忧。不管页面多复杂,很少有会担心页面内部的性能,而担忧统统来自跨页面的转场效果。因此,我提出了一个概念:**Every element can be a webview**.
从逻辑上来看,webapp是由多个view组成,而每个view又由多个Element组成。但在原生的web技术中,没有View这个概念,我们只能利用Javascript,将View用Element实现,这样,一个webapp中就有超大量的DOM节点。在view切换时,需要进行大量的重绘,性能就差了,在移动设备上表现尤为明显。
而BlendUI的这个概念让View有了「原生」支持,任何的Element都可以用一个独立的webview来实现,浏览内核的负担就减轻了,而且切换时的过场动画用原生代码实现,也保证了性能。
举个例子,这是已经采用BlendUI上线的百度阅读app,采用BlendUI来实现的话,原来是单页的webapp将分成很多不同的webview。
在首页,背后是一个webview,只用来显示头部,而底部由多个webview组成,以实现流畅的滑动效果。
打开图书页以后,覆盖在上面的是一个新的webview。
甚至可以将某个复杂的控件做成一个独立的webview或者由Native控件实现。
也就是说,页面中任何一个独立出现的元素,都可以用一个独立的webview来实现。简单一个概念,解决了三大问题:
最后的结果证明,这条路用极小的成本解决了关键问题。
在我们开发出了Demo后,Basecamp的工程师发了一篇博客,他里面有两个核心观点:
最终,basecamp的这个版本大受好评,37singals只投入了1-2个工程师和半个设计师。他们的思路竟与我们出奇地一致。
因为把握住了问题的核心,所以BlendUI的核心实现特别简单,向其他人解释这个问题也特别容易。我不用BlendUI有多少原生接口和能力这样的数据吓唬人,也不用长篇大论地解释内部有多么复杂的实现,只要演示我们的Demo,然后告诉听众,我们解决了页面转场和卡头卡尾的问题就可以了。
我们秉承的设计原则有三条:模型从简;事件驱动;为开发者保留最大的灵活性。
系统亦很简单,分成上下两层。下层(蓝色部分)由Native code实现,上层(绿色部分)由web实现,下层提供核心能力,上层完成封装。
核心能力包括两部分,Runtime和BlendUI。前者是轻组件的运行环境,插件机制是它的核心,设备能力、BlendUI都是插件,这样能最大限度地减少SDK的体积(极限状态下100多K),所有插件都可以动态联网下载和更新。BlendUI的核心自然是其Webview控制力,除此之外还包括扩展BlendUI的Native组件的能力,这和Runtime的插件机制类似,它能通过Javascript调用动态地加载外挂的组件。
在上层封装中,我们除了封装iOS和Android两个平台的能力,提供统一的API之外,还提供了一套退化的web实现,这样我们能让基于BlendUI开发的应用在普通浏览器下也能正常运行。
Layer和LayerGroup是BlendUI两个关键概念,Layer即webview,LayerGroup即一组webview的集合。Layer可以通过Javascript设定其显示的位置和大小,也可以指定滑入时的动画;LayerGroup实现了最典型的组合,通过滑动切换不同的Layer。
要让某元素固定在可视区域的某位置,比如顶部的Tab切换条,用一个独立的Layer实现Tab,并固定其位置和大小即可:
new Layer({ url: "/path/to/tab.html", top: 0, left: 0, height: 30px })
剩余的区域用一个LayerGroup实现:
new LayerGroup({ url: "/path/to/content.html", top: 30, left: 0, layers: [ { url: "/path/to/content1.html" },{ url: "/path/to/content2.html" }, ] })
事实上,这些Javascript API都是通过调用Native植入window上下文的window.lc_bridge方法来实现的。
此外,通过监听BlendUI提供的事件,就可以实现内容切换时,Tab页高亮区域转变的效果:
document.addEventListener('groupScrolled', function(index){ highlightSwitch(index); });
我们很快也会提供ListView Header类似的滑动卡头的能力。
除了Layer和LayerGroup,就是原生组件的嵌入了。
new Slider({ "images" : [ { url: "/path/to/photo1.jpg" } , { url: "/path/to/photo2.jpg" } ] })
前文已经提到,原生组件是动态选择和加载的,这部分封装其实调用了 window.lc_bridge.addComponent("com.baidu.blendui.component.slider"),addComponent方法会去动态地加载Slider,并且调用其中的execute方法。
这项技术,首先对轻应用开发者来说是巨大的福音。在不接触任何Native代码的前提下,就能让轻应用的体验有质的提高,这项技术将很快通过轻应用Runtime下发到所有轻应用的运行环境中。一旦BlendUI集成到百度搜索app中,直接在app中打开的轻应用,这样既能使用百度的分发渠道,也能有较好的浏览体验。
另外,BlendUI很适合大app中的独立频道单独发布app上架(比如豆瓣那一大堆子频道),既能节省人力成本,能保证基本的用户体验,又有很高的定制性,迭代速度和web相当。这样就能快速试错,即使产品决策失误,开发成本也不高,也容易调整 —— 这是互联网产品的核心优势。
云无心以出岫,鸟倦飞而知还。希望能给你带来一点灵感。