编者按:InfoQ 开设新栏目“品味书香”,精选技术书籍的精彩章节,以及分享看完书留下的思考和收获,欢迎大家关注。本文节选自徐凯著《跨终端Web》第八章“Hybrid App”,主要讲述Hybrid App 的发展现状以及技术实现,最后还介绍了两种主流Hybrid 开发框架PhoneGap/Cordova 和Titanium。
Native App(以下简称 Native)和 Mobile Web(以下简称 Web)二者混合开发的产物被称为 Hybrid App(以下简称 Hybrid)。Hybrid 并不是什么新概念,最早可以追溯到 Symbian 时代,直到 iOS 和 Android 出现之后才充分展现出价值。
Hybrid 简史
1. 背景
Hybrid 既利用了 Native App 丰富的设备 API(Device API),又能拥有 Mobile Web 的跨平台、高效开发、快速发布的能力,对于相当庞大的应用场景而言都是适用的。
Hybrid 优势在于:
-
跨平台 Web 内容可以做到开发一次,所有平台生效,诸多产品需要这种能力。
-
快速发布 iOS 平台,Apple Store 平均审核周期 1~2 周不等,甚至更长,产品的发布周期从 2 周到 1 月,这对需要快速发布的产品而言难以接受。
Android 平台,应用商店众多,发布过程烦琐。虽然可以应用内升级,但是带来的问题是新 App 需要通过应用商店,此外 APK 体积庞大,2G/3G 环境下体验差。
-
高效开发 Web 开发经过 20 年的发展,已经将结构(HTML)、表现(CSS)、行为(JavaScript)3 部分很好地分离开,在分工协作、开发效率上会具明显优势。
-
丰富的 Device API Web(HTML5)强调通用性,受限于标准和浏览器实现,许多有用的系统功能未能得到支持(或部分支持)。而 Native 最大的优势在于设备 API 的调用能力,只要桥接 Native 和 Web,Web 也就能够拥有这种能力。
Hybrid 劣势表现为:
- CPU/GPU 密集类应用目前看更适合 Native,例如极品飞车这样的游戏。这种劣势是在不断弱化的,正如 “CSS Transform 3D”引入 GPU 大大缓解了 Web 动画不流畅的问题。
- 静态资源从服务器端加载导致的 UI 展示延迟问题。这个问题可以通过 Native 拦截 WebView 通信加载已打包的公共库来缓解。
2. 简史
-
雏形 雏形阶段大致为:
- Symbian V3/5 时代已经有 Hybrid 雏形。
- iOS 最初的 App 都是由 Objective-C 编写而成的,受限应用商店的发布周期,内容经常变化的部分开始通过使用内置浏览器控件(WebView)加载服务端页面来实现。
- Android 出现并流行之后,可以将更多的 App 功能通过 Hybrid 来实现,这样在不同平台上就可以只维护一个版本。
-
发展 “跨平台”成了 Hybrid 最大的卖点,以 PhoneGap[1] 为首的 Hybrid 框架陆续出现,带来了诸多改变。
- 访问设备功能。
- Web(HTML5)不支持的功能可以让 Native 实现,再通过 Native 和 Web 之间通信,通过这种方式可以让 Web 获得和 Native 相同的设备 API 调用能力,这是 PhoneGap 这类 Hybrid 框架的基本工作原理。
- 与此同时,将 Web 代码转为 Native 的 Hybrid 框架(如 Tianium[2])也出现了。
- PhoneGap 子项目 weinre 是一种远程调试工具,极大地缓解了 Hybrid 难于调试的问题,进一步促进了 Hybrid 的发展。
- Hybrid 框架提供了应用打包功能,开发者可以完全使用 HTML、CSS、JavaScript 开发 Native App。
- 访问设备功能。
-
成熟 随着 PhoneGap 这类 Hybrid 框架在全球的流行,一些问题暴露了出来,也正是这些问题的解决,让 Hybrid 走向成熟。
- 开发体验提升。
- weinre 这类调试工具仍属于插件性质,诸如“网络”、“本地资源”等高级调试功能无法支持,WebView 的原生调试需求越来越强烈。
- iOS 6.0+ 已经支持原生的远程调试 [3]。
- Chrome for Android 在原生远程调试上处于领先地位 [4]。
- 从 Android 4.4 开始,WebView 也支持原生的远程调试 [5]。
- 提升 WebView 性能的呼声日益增强。
- 某些追求极致性能的功能转由 Native 实现,如转场(页面间切换)动画。
- 静态资源本地化是理想状态,其他场景下 Native 拦截 WebView 的请求,并让公共资源重定向到 App 内置资源,同样能实现为 Web 提速。
- 开发体验提升。
3. 现状
以上便是 Hybrid 的发展概述,从国内最新的资料可以看出,Hybrid 的趋势也是非常明显的。从图 8-1 可以看到越来越多的开发者决定使用 Hybrid(跨平台技术),最近两年的总量已经有 54%;而接近 60% 的开发者在 Hybrid 的技术方案上选择了 PhoneGap。
图 8-1 Hybrid 在国内的发展情况 [6]
- 在受访的 2309 个 Mobile 开发者中,到 2013 年 8 月为止完全使用 Native 开发的只有 8%,而剩余的 92% 都可以被认为使用的是 Hybrid,如图 8-2 所示。
图 8-2 Hybrid 使用情况
2. App 的跨平台特性成为一个重要的考虑,如图 8-3 所示。
图 8-3 跨平台特性受关注
图 8-4 显示了 Hybrid 惊人的增长速度:2013 年无论是开发中、已发布的 Hybrid(或 HTML App)均相比于 2012 年出现了超过 125%~400% 的增长率 [8]。
图 8-4 Hybrid 增长迅猛 [9]
Hybrid 技术
无论 Android 还是 iOS,实现一个最简单的 Hybrid App 只需要几行代码:实例化 WebView、加载页面,之后便是页面自身的代码。要想实现更为复杂的、完整的 Hybrid 还需要不少知识。
- Mobile Web 开发基础:HTML、CSS、JavaScript。
- Native App 开发基础:Android、iOS。
- Native 与 Web 双向通信机制。
Mobile Web 开发基础可以参考本书第 2 章,Native App 开发基础已经超出本书的讨论范围,同样有很多可选择的书籍,本节来讲剩余的第 3 个问题 “Native 与 Web 双向通信机制”。
1. Native 调用 Web
无论 Android 还是 iOS ,Native 调用 Web(JavaScript) 都有很好的原生支持,如代码 8-1 和代码 8-2 所示。Android 中的调用方式如下,其中 webView 是 Webview 的实例。
代码 8-1 Android 调用 JavaScript
webView.loadUrl("javascript:(function(){ alert(‘ok’); })()”);
iOS 中的调用方式如下,其中 webView 是 UIWebview 的实例。
代码 8-2 iOS 调用 JavaScript
[webView stringByEvaluatingJavaScriptFromString: @"alert('ok')" ];
2. Web 调用 Native
“Native 调用 Web”本质上是 JavaScript 脚本的动态执行,在“Web 调用 Native”的场景下由于目前 Native 语言(Java 和 Objective-C)不容易像 JavaScript 那样便于动态执行,所以需要另辟蹊径。
2.1 Android
Android 上常见的方式有 3 种。
- 重写 WebViewClient.shouldOverrideUrlLoading(如代码 8-3 所示)。 代码 8-3 重写 WebViewClient.shouldOverrideUrlLoading
webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading (WebView view, String url){ // TODO 解析 URL 并触发 Native 代码 return true; } });
当页面内的 URL 发生变化时,如点击链接、执行 JavaScript(如 _location.href=”http://”)_ 等均会触发 WebViewClient.shouldOverrideUrlLoading,通过将 Web 调用 Native 的数据封装在 URL,再由 Native 解析数据并执行响应 Native 方法。
2. 重写 WebChromeClient.onJsPrompt,或 onJsConfirm,或 onJsAlert,以 WebChromeClient.onJsPrompt 为例,如代码 8-4 所示。 代码 8-4 重写 WebChromeClient.onJsPrompt
webView.setWebChromeClient(new WebChromeClient() { public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { // TODO 解析 message 并触发 Native 代码 result.confirm(""); return true; } });
当执行“window.prompt(“{}”)”这样的 JavaScript 代码时,将会触发 WebChromeClient.onJsPrompt。
3. WebView.addJavascriptInterface,这种方式和前两种都不同,通过将 Java Object(A) 映射为 JavaScript Object(B),从而调用 B.func1 时将会自动触发 A.func1,通过这种原生的方式实现了 “Web 调用 Native”,如代码 8-5 所示。 代码 8-5 WebView.addJavascriptInterface
webView.addJavascriptInterface(new Object() { public void func1() { } public void func2() { } }, "webViewObj");
以上 3 种方式,最常用的是方式 2;方式 2 相比方式 1 有内置的队列支持,不会出现高频访问数据丢失的情况;方式 3 是 Android 原生方式,但是不如前两种方式灵活。
2.2 iOS
iOS 中可用的方式类似 Android 中的 WebViewClient.shouldOverrideUrlLoading, 通过监控 WebView 的 URL 变化实现 Web 调用 Native,如代码 8-6 所示。
代码 8-6 shouldStartLoadWithRequest
- (BOOL)webView:(UIWebView *) webView shouldStartLoadWithRequest: (NSURLRequest *)request navigationType: (UIWebViewNavigationType)navigationType { }
3. Bridge
有了前两节的知识,可以实现一个通用模块(Bridge)来维护不同平台上的“Web 与 Native 双向通信机制”功能。如图 8-5 所示为 Web 调用 Native 的 Bridge 时序图。
图 8-5 Web 调用 Native 的 Bridge 时序图
Web 调用 Native 的实现原理如下。
- Web 端调用 Bridge.callByJS({name:’func1’, callback: function(){}, param:{}}),由 Bridge 根据特定“Web 调用 Native”方式通知 Native 执行相应方法(图 8-5 中的“func1”)。
- Native 执行完毕后通过“Native 调用 Web”的方式调用 Bridge. callByNative({token: ‘t1234’ })。如图 8-6 所示为 Native 调用 Web 时的 Bridge 时序图。
- 其中 JavaScript 回调函数会映射为字符串型的 token,通过这个方式来保证最终触发 JavaScript 的回调函数(包括匿名函数和通过闭包实现的私有函数)。
图 8-6 Native 调用 Web 时的 Bridge 时序图
可以看到,Bridge 实现“Native 调用 Web”是类似的。
- Native 端调用 Bridge.callByNative({token:’t1234’, script: ‘//todo’}),由 Bridge 根据特定“Native 调用 Web”方式通知 Web 执行相应脚本。
- Web 执行完毕后通过“Web 调用 Native”的方式调用 Bridge. callByJS({token: ‘t1234’ })。
- 如果 Bridge.callByNative 的 script 中执行了异步操作,需要在 script 主动调用 Bridge.callByJS,并且不需要传 token 参数。
笔者已经在 Android 上实现了完整的 Bridge[10],Bridge 由 JavaScript 实现可以运行在 Android 和 iOS 的 WebView 中,同时也非常容易扩展到 Windows Phone 等新平台,如代码 8-7 所示。
- Bridge 代码在产品环境下使用时请设置 DEBUG = false。
- 避免在 iOS 下快速变化 URL 时造成的数据丢失,可以考虑使用队列机制缓存命令。
- 扩展至 Windows Phone 等平台时 JavaScript 部分只需要扩展 invoke,Native 代码可以参考 Android 的实现。
- 目前 Bridge 单次通信后会删除回调函数,如果需要多次调用缓存的回调函数(如连续监控传感器数据),可以扩展 Bridge.callByNative。
代码 8-7 bridge.js
(function(window) { var DEBUG = true; var callbacks = {}; var guid = 0; var ua = navigator.userAgent; // TODO 精确性待改进 var ANDROID = /android/i.test(ua); var IOS = /iphone|ipad/i.test(ua); var WP = /windows phone/i.test(ua); //ANDROID = 0; IOS = 1; /** * 方便在各个平台中看到完整的 log */ function log() { if (DEBUG) { console.log.call(console, Array.prototype.join.call(arguments, ' ')); } } /** * 平台相关的 Web 与 Native 单向通信方法 */ function invoke(cmd) { log('invoke', cmd); if (ANDROID) { prompt(cmd); } else if (IOS) { location.href = 'bridge://' + cmd; } else if (WP) { // TODO ... } } var Bridge = { callByJS: function(opt) { log('callByJS', JSON.stringify(opt)); var input = {}; input.name = opt.name; input.token = ++guid; input.param = opt.param || {}; callbacks[input.token] = opt.callback; invoke(JSON.stringify(input)); }, callByNative: function(opt) { log('callByNative', JSON.stringify(opt)); var callback = callbacks[opt.token]; var ret = opt.ret || {}; var script = opt.script || ''; // Native 主动调用 Web if (script) { log('callByNative script', script); try { invoke(JSON.stringify({ token: opt.token, ret: eval(script) })); } catch (e) { console.error(e); } } // Web 主动调用 Native,Native 被动响应 else if (callback) { callback(ret); try { delete callback; log(callbacks); } catch (e) { console.error(e); } } } }; window.Bridge = Bridge; window.__log = log; })(window);
Hybrid 框架
目前一个 Hybrid 框架通常提供以下功能。
- Device API:封装 Native 的功能,跨平台提供一致的 Device API。
- App 打包:将 HTML5 编写的代码打包为 App(Titanium 会转换代码)。
PhoneGap 几乎成了 Hybrid 的代名词,Titanium 和 PhoneGap 的设计理念差异较大,图 8-7 形象地展示了 PhoneGap 和 Titanium 的组成部分。
图 8-7 Hybrid 框架 [11]
1. PhoneGap
1.1 PhoneGap 和 Cordova
PhoneGap 开发商 Notibi 2010 年将 PhoneGap 代码贡献给 Apache 软件基金(ASF),PhoneGap 核心引擎成为新的开源项目 Cordova,同时 PhoneGap 成了 Cordova 的一个发行版本 [12]。2011 年 10 月,Notibi 被 Adobe 收购 [13],但没有影响到 PhoneGap 和 Cordova 的开源性质。
1.2 原理
written once,run everywhere
如引文所述“一处编写,多处运行”,PhoneGap 主要的功能为:
- 提供 Hybrid API,可由 JavaScript 直接调用诸如加速度、摄像头、指南针、GPS、联系人等系统级 API,完整的 API 列表请访问 PhoneGap API Reference。
- 使用 Web(HTML、CSS、JavaScript)开发的内容经过 PhoneGap 编译打包为各个平台的 Native App,如图 8-8 所示。
图 8-8 PhoneGap 编译打包功能
1.3 经典案例
来自 PhoneGap Showcase[14] 和其他数据源的资料显示:
- Facebook Mobile SDK[15] 和 SalesForce Mobile SDK[16] 均是基于 Cordova 的分支开发的。
- Facebook 客户端中 Web 代码超过 90%[17]。
- LinkedIn iPad 客户端中 Web 代码甚至超过 95%。
- Wikipedia 更是直接用 PhoneGap 开发了自己的 iOS/Android Hybrid App[18],并将代码在 GitHub 上开源 [19]。
2. Titanium
Titanium 设计思路和 PhoneGap 有很大不同,Titanium 目的为移动开发提供一种跨平台的 JavaScript 运行时环境和 API。
2.1 设计思路
Titanium 设计的核心思路如下。
- 有一套核心的移动开发 API,它们可以跨平台进行规范,这些方面的重点应放在代码重用上。
- 有针对特定平台的 API、用户界面约定以及功能特性,开发者在针对该特定平台从事开发时采用,应该有针对特定平台的代码,以便这些用例提供最佳的用户体验。
Titanium 从设计理念上不追求“written once, run everywhere”,这是它的缺点,但同时它追求平台差异的更佳的用户体验,因而也受到一部分用户的追捧。Titanium 的另一个缺陷是插件难于扩展,要想支持新平台则更加困难。
2.2 工作流程
工作流程如下。Titanium 工作流如图 8-9 所示。
- 使用 Titanium SDK 在自带的 IDE(ALLOY)中开发。
- 使用工具编译为平台相关的 App。
图 8-9 Titanium 工作流
书籍简介
移动互联网不可阻挡地进入了我们的生活。作者将自己在百度和天猫期间的跨终端 Web 的开发实践转化为书中的技术方案和实现,呈现给各位读者。第 1 章提出了跨终端 Web 的概念以及实现跨终端 Web 的多重途径,第 2 章主要介绍 Mobile Web 的技术基础,第 3~7 章是全书的核心,按照开发流程组织逐步讲解了实现跨终端 Web 所需要的各类技术基础设施,第 8 章主要介绍了 Hybrid App 的发展历程、实现细节以及成熟的框架,第 9 章介绍的跨终端存储方案(Storage)是作者曾经的冠军作品,第 10 章完整介绍了如何通过脚本录制和回放来实现跨终端动作同步。
《跨终端 Web》讲解深入浅出,通畅易懂,适合有一定 PC Web 基础,希望迅速了解 Mobile Web,致力于 PC 和 Mobile Web 技术融合的读者。
作者简介
鬼道(原名徐凯),2011 年毕业于同济大学计算机系,模式识别方向硕士研究生。曾就职百度,现为天猫前端通用组技术 Leader。本书源于 2013 年 7 月在 D2 上的主题分享“移动优先的跨终端 Web”,2013 年 11 月在 W3CTECH 2013 做了第二次分享。
- [1] PhoneGap 是主流 Hybrid 框架。
- [2] http://www.appcelerator.com/titanium/
- [3] http://www.36kr.com/p/117773.html
- [4] http://www.html5rocks.com/en/tutorials/developertools/mobile/
- [5] https://developers.google.com/chrome-developer-tools/docs/remote-debugging#debugging-webviews
- [6] 摘自《友盟 2013 上半年报告》2013.09。
- [7] http://www.kendoui.com/surveys/html5-native-debate-is-over.aspx
- [8] http://www.businessinsider.com/html5-vs-native-apps-for-mobile-2013-6?op=1
- [9] 此图来自 http://www.businessinsider.com/html5-vs-native-apps-for-mobile-2013-6?op=1。
- [10] http://luics.github.io/cew/Bridge.zip
- [11] 此图来自 http://www.businessinsider.com/html5-vs-native-apps-for-mobile-2013-6?op=1。
- [12] http://phonegap.com/2012/03/19/phonegap-cordova-and-what’s-in-a-name/
- [13] http://www.adobe.com/aboutadobe/pressroom/pressreleases/201110/AdobeAcquiresNitobi.html
- [14] http://phonegap.com/app/
- [15] https://developers.facebook.com/docs/guides/mobile/
- [16] http://wiki.developerforce.com/page/Mobile_SDK
- [17] http://www.geekpark.net/read/view/164456
- [18] http://itunes.apple.com/us/app/wikipedia-mobile/id324715238?mt=8
- [19] https://github.com/wikimedia/WikipediaMobile
评论