写点什么

跨终端 Web 之 Hybrid App

  • 2015-05-26
  • 本文字数:7368 字

    阅读完需:约 24 分钟

编者按: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 劣势表现为:

  1. CPU/GPU 密集类应用目前看更适合 Native,例如极品飞车这样的游戏。这种劣势是在不断弱化的,正如 “CSS Transform 3D”引入 GPU 大大缓解了 Web 动画不流畅的问题。
  2. 静态资源从服务器端加载导致的 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]

  1. 在受访的 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 还需要不少知识。

  1. Mobile Web 开发基础:HTML、CSS、JavaScript。
  2. Native App 开发基础:Android、iOS。
  3. 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 种。

  1. 重写 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 的实现原理如下。

  1. Web 端调用 Bridge.callByJS({name:’func1’, callback: function(){}, param:{}}),由 Bridge 根据特定“Web 调用 Native”方式通知 Native 执行相应方法(图 8-5 中的“func1”)。
  2. Native 执行完毕后通过“Native 调用 Web”的方式调用 Bridge. callByNative({token: ‘t1234’ })。如图 8-6 所示为 Native 调用 Web 时的 Bridge 时序图。
  3. 其中 JavaScript 回调函数会映射为字符串型的 token,通过这个方式来保证最终触发 JavaScript 的回调函数(包括匿名函数和通过闭包实现的私有函数)。

图 8-6 Native 调用 Web 时的 Bridge 时序图

可以看到,Bridge 实现“Native 调用 Web”是类似的。

  1. Native 端调用 Bridge.callByNative({token:’t1234’, script: ‘//todo’}),由 Bridge 根据特定“Native 调用 Web”方式通知 Web 执行相应脚本。
  2. Web 执行完毕后通过“Web 调用 Native”的方式调用 Bridge. callByJS({token: ‘t1234’ })。
  3. 如果 Bridge.callByNative 的 script 中执行了异步操作,需要在 script 主动调用 Bridge.callByJS,并且不需要传 token 参数。

笔者已经在 Android 上实现了完整的 Bridge[10],Bridge 由 JavaScript 实现可以运行在 Android 和 iOS 的 WebView 中,同时也非常容易扩展到 Windows Phone 等新平台,如代码 8-7 所示。

  1. Bridge 代码在产品环境下使用时请设置 DEBUG = false。
  2. 避免在 iOS 下快速变化 URL 时造成的数据丢失,可以考虑使用队列机制缓存命令。
  3. 扩展至 Windows Phone 等平台时 JavaScript 部分只需要扩展 invoke,Native 代码可以参考 Android 的实现。
  4. 目前 Bridge 单次通信后会删除回调函数,如果需要多次调用缓存的回调函数(如连续监控传感器数据),可以扩展 Bridge.callByNative。

代码 8-7 bridge.js

复制代码
functionwindow{
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 invokecmd{
log('invoke', cmd);
if (ANDROID) {
prompt(cmd);
}
else if (IOS) {
location.href = 'bridge://' + cmd;
}
else if (WP) {
// TODO ...
}
}
var Bridge = {
callByJS: functionopt{
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: functionopt{
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 框架通常提供以下功能。

  1. Device API:封装 Native 的功能,跨平台提供一致的 Device API。
  2. 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 主要的功能为:

  1. 提供 Hybrid API,可由 JavaScript 直接调用诸如加速度、摄像头、指南针、GPS、联系人等系统级 API,完整的 API 列表请访问 PhoneGap API Reference。
  2. 使用 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 设计的核心思路如下。

  1. 有一套核心的移动开发 API,它们可以跨平台进行规范,这些方面的重点应放在代码重用上。
  2. 有针对特定平台的 API、用户界面约定以及功能特性,开发者在针对该特定平台从事开发时采用,应该有针对特定平台的代码,以便这些用例提供最佳的用户体验。

Titanium 从设计理念上不追求“written once, run everywhere”,这是它的缺点,但同时它追求平台差异的更佳的用户体验,因而也受到一部分用户的追捧。Titanium 的另一个缺陷是插件难于扩展,要想支持新平台则更加困难。

2.2 工作流程

工作流程如下。Titanium 工作流如图 8-9 所示。

  1. 使用 Titanium SDK 在自带的 IDE(ALLOY)中开发。
  2. 使用工具编译为平台相关的 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 做了第二次分享。


2015-05-26 21:508988

评论

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

兑现 Service Mesh 的新价值:精确控制“爆炸半径”

阿里巴巴云原生

读《A Philosophy of Software Design》(01-07)

术子米德

架构师成长笔记

剑指Offer之面试题57: 和为s的数字

宇宙之一粟

算法刷题 4月月更 剑指Offer

Linux之ssh-add命令

入门小站

分布式链路追踪

yuexin_tech

链路追踪

【图解数据结构】栈全面总结

知心宝贝

c++ 数据结构 算法 4月月更

[Day6]-[动态规划] 俄罗斯套娃

方勇(gopher)

LeetCode 数据结构和算法

赶紧给你的文件加个密吧!

Jackpop

通过npm+Vant Weapp 构建微信小程序

kcnf

RocketMQ—Producer(二)路由动态更新

IT巅峰技术

Apache RocketMQ

Spring容器的灵魂

IT巅峰技术

Spring 框架漏洞

Spring容器的核心组件

IT巅峰技术

Linux驱动开发-安装驱动参数传递

DS小龙哥

4月月更

mysql的FIND_IN_SET group_concat 函数

Rubble

MySQL 4月日更

云原生时代如何用 Prometheus 实现性能压测可观测-Metrics 篇

阿里巴巴云原生

MapReduce 学习思考

en

mapreduce

阿里云OSS图床搭建

懒时小窝

阿里云 OSS 图床

在线时间加减计算器

入门小站

计算器

浏览器突然好用多了。。。

Jackpop

关于线程池,面试的时候你时候还打怵,这里我有话要说保证让你对线程池的各个参数一边就懂

派大星

线程池

Spring Boot 整合Dubbo + Zookeeper 实现分布式 消费者与服务者的业务调用

Bug终结者

Java dubbo springboot

Spinner: 往Pinterest新工作流平台的大规模迁移

俞凡

架构 工作流引擎 大厂实践 Pinterest

WEB3的DAO生态服务平台,SeekTiger展现新的契机

小哈区块

Rust的对象安全性

Shine

rust

通达系统架构设计文档

小锅米线

明道云如何实现银行内部评级管理

明道云

大话后端开发的奇技淫巧大集合

SFLYQ

架构 Web 后端 服务端 经验分享

通达快递系统设计

peter

让页面跳转更有趣 —— 实现自定义页面切换转场动画

岛上码农

flutter 移动端开发 4月月更 跨平台开发 安卓 ios

在线XML压缩工具

入门小站

工具

你真的会用搜索引擎吗?

Jackpop

跨终端Web之Hybrid App_移动_徐凯_InfoQ精选文章