写点什么

BlendUI,让 webapp 的体验和交互得到质的提升

2014 年 7 月 15 日

故事背景

百度轻应用已经推出半年多了,在市场上已有一定的影响力。但同时我们发现,开发者之所以使用轻应用,看中的是百度的渠道分发能力,而在体验上和 Native app 有比较大的差异,这个问题在应用复杂以后变得尤为明显。

前些年,在做移动 web 站点时,我们都会追求一些无刷新的跳转、换页效果,但后来发现这样做在市场顶级的手机上尚不能运行流畅,就更不用说那些相对廉价和低端的智能机了。所以近一年来,大家倾向于不在移动 web 站点中采用复杂的特效,只在最关键的部分,用最小的代价来实现。

现在你已经很少在 Android 中看到用 Javascript 实现的换页效果了;又比如百度的多数产品线放弃了 Javascript 实现的局部滚动效果,转而采用原生滚动实现,或者只应用到页面的少数交互元素中。与此同时,很多公司已经将重点精力投入到 Native 原生应用的开发中,减缓了在 web 端的投入。

而对我们,当然希望轻应用的体验和交互能够和 Native 媲美,那我们要做什么才能达到这个目标呢?做什么才是最有效的呢?

调研

我们首先看到了 w3c 的 (一份报告) ,这是来自缩短与本地应用差距(closing the gap with native) 的任务组产出。这份报告指出了 webapp 相对 Native app 的优势与劣势。

我们接着寻找了很多份数据,概括得最好、数据量也很丰富的是 Developer Economics 的这份调研数据详细说说其中两组关键的数据:

  1. 为什么开发者不使用 HTML5 技术开发应用?
  2. 多少 Native app 可以用 web 技术实现?

经过对 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.

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 来实现。简单一个概念,解决了三大问题:

  1. 转场不再卡顿。从我们事后的实测数据来看,FPS 提高了一倍,从 30 fps 提高到 60fps。
  2. 随处都可以使用原生滚动,因为我们可以将卡头卡尾的页面区域用独立的 webview 实现
  3. 单个 web 页的 DOM 节点数减少,从而使页面本身的性能大幅提高

最后的结果证明,这条路用极小的成本解决了关键问题。

在我们开发出了 Demo 后,Basecamp 的工程师发了一篇博客,他里面有两个核心观点:

  1. Decisions based on computing speeds quickly decay. 硬件制约是一时的,语言和平台的生命力才最重要。
  2. Native shell + native navigation. 这是 Hybrid app 的一个简单但重要的改进,他们把 Native code 用在了对性能最为有利的部分。

最终,basecamp 的这个版本大受好评,37singals 只投入了 1-2 个工程师和半个设计师。他们的思路竟与我们出奇地一致。

核心实现

因为把握住了问题的核心,所以 BlendUI 的核心实现特别简单,向其他人解释这个问题也特别容易。我不用 BlendUI 有多少原生接口和能力这样的数据吓唬人,也不用长篇大论地解释内部有多么复杂的实现,只要演示我们的 Demo,然后告诉听众,我们解决了页面转场和卡头卡尾的问题就可以了。

设计原则

我们秉承的设计原则有三条:模型从简;事件驱动;为开发者保留最大的灵活性

  • 模型从简,这条原则贯穿始终。一句话来说,BlendUI 让 Javascript 拥有了控制 webview 的能力。
  • 事件驱动,这也是 Javascript 的核心。跨 webview 通讯、Javascript 和 Native code 通讯,几乎全是事件。
  • 为开发者保留最大的灵活性。这意味着 BlendUI 不会影响开发者对其他库、框架的选择,更不会在 UI 风格上面有任何限制,所有的东西都是 Web 的,定制性无限。

系统概览

系统亦很简单,分成上下两层。下层(蓝色部分)由 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 相当。这样就能快速试错,即使产品决策失误,开发成本也不高,也容易调整 —— 这是互联网产品的核心优势。

作者专栏

行云出岫

云无心以出岫,鸟倦飞而知还。希望能给你带来一点灵感。


感谢王保平对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014 年 7 月 15 日 12:454514

评论

发布
暂无评论
发现更多内容

架构师训练营第八周作业

叶鹏

oeasy 教您玩转linux 010304 图形界面 xfce

o

年度开源盛会 ApacheCon 来临,Apache Pulsar 专场大咖齐聚

Apache Pulsar

开源 云原生 Apache Pulsar 消息中间件

Spring 5 中文解析数据存储篇-编程式事物管理

青年IT男

Spring5

用户密码验证函数

叶鹏

简述 CAP 原理

叶鹏

Springboot 定时任务

hepingfly

定时任务 springboot 注解

两天,我把分布式事务搞完了

yes

分布式事务 seata

食堂卡就餐卡系统

叶鹏

常用设计模式

叶鹏

关于Java调用类的main方法

谷鱼

Java 包位置

高难度对话读书笔记—情绪篇

wo是一棵草

整合Elastic-Job(支持动态任务)

Nil

springboot SpringCloud 分布式任务调度 Elastic-job

一个草根的日常杂碎(9月21日)

刘新吾

生活 现实纪录 随笔

实战中学习浏览器工作原理 — 排版与渲染

三钻

CSS 前端 浏览器

微服务的框架(Dubbo)架构

叶鹏

被我玩坏的git:除了之前的工作、当网盘用,还能这么玩

小Q

Java git 程序员 架构 开发

一文学懂递归和动态规划!

小齐本齐

算法 数据结构和算法

从零开始搭建完整的电影全栈系统(五)——WEB网站、Api以及爬虫的部署

刘强西

爬虫 网站搭建 部署与维护

ECMAScript 6新特性简介

程序那些事

nodejs ES6 ECMAScript 6

Spring 5 中文解析数据存储篇-@Transactional使用

青年IT男

spring

小白理财先转变思维理念

boshi

理财 收入 财富自由

anyRTC云端录制功能上线

anyRTC开发者

WebRTC 语音 直播 RTC 安卓

技术译文|如何将 Pulsar 用作消息队列

Apache Pulsar

开源 云原生 pulsar Apache Pulsar 消息中间件

架构师训练营第四周作业

叶鹏

前端如何优雅处理类数组对象?

pingan8787

Java web前端

简述JVM垃圾回收

叶鹏

架构师训练营12周作业

叶鹏

架构师训练营第7周作业

叶鹏

18 张图,一文了解 8 种常见的数据结构

沉默王二

Java 数据结构

猛料!腾讯架构师手写“Java成长秘籍”,做开发也没那么难

互联网架构师小马

Java 编程 程序员 腾讯 软件开发

BlendUI,让webapp的体验和交互得到质的提升-InfoQ