【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

跨终端 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:508584

评论

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

十大排序算法--归并排序

Ayue、

排序算法 8月日更

手把手教你在Windows和Linux下安装Redis及了解Redis基本操作

Regan Yue

redis Linux windows 8月日更

架构实战营 毕业设计

👈

架构实战营

oeasy教您玩转vim - 3 - # 打开文件

o

Zilliz 陈室余:音视频相似性检索的技术实现丨ECUG Meetup 回顾

七牛云

AI 音视频 ECUG 七牛云

怎样评估选型一个企业软件产品?

明道云

测试开发之系统篇-按需创建测试虚拟机

禅道项目管理

虚拟机 自动化测试 测试开发

云计算重塑生命科学行业,北鲲云加速生物制药企业转型

北鲲云

基于香港云服务器的解决方案可以增强金融服务公司在降低成本的同时降低风险

九河云安全

kubernetes入门:使用kubeadm搭建master,亲测无异常

小鲍侃java

8月日更

中国大学 MOOC Android 性能优化:冷启动优化总结

有道技术团队

大前端 安卓 网易有道

架构实战营 学习总结

👈

架构实战营

大佬分享开发经验!2021年华为Android面试真题解析

欢喜学安卓

android 程序员 面试 移动开发

oeasy教您玩转vim - 3 - # 打开文件

o

【Flutter 专题】77 图解历史 Android Native 项目接入 Flutter Module

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 8月日更

互联网寒冬!大厂Android开发面试解答

欢喜学安卓

android 程序员 面试 移动开发

Vue进阶(七):走近 package.json

No Silver Bullet

Vue npm 8月日更

手撸二叉树之平衡二叉树

HelloWorld杰少

数据结构与算法 8月日更

为什么将网络虚拟化与实现服务器虚拟化不同?

九河云安全

netty系列之:netty架构概述

程序那些事

Java Netty nio 程序那些事

Python代码阅读(第3篇):列表的最小公倍数

Felix

Python 编程 Code Programing 阅读代码

kafka日志写入logstash

Rubble

Logstash Kafk 8月日更

跟我学AI建模:分子动力学仿真模拟之DeepMD-kit框架

华为云开发者联盟

AI 仿真 分子动力学 分子 建模

团队对质量负责,“我”可以不负责?

BY林子

敏捷测试 责任流程模型

Flutter 的 runApp 与三棵树诞生流程源码分析

工匠若水

flutter android 8月日更

oeasy教您玩转vim - 2 - # 使用帮助

o

vim

沙场秋点兵——MySQL容器化性能测试对比

焱融科技

MySQL 云计算 容器 高性能 分布式存储

秒杀系统设计

Vincent

架构训练营

聊聊Go语言中的数组与切片

架构精进之路

8月日更

Nginx的常用功能总结

程序员阿杜

Java nginx 8月日更

YYDS!浪潮云蝉联中国政务云服务运营市场占有率第一

浪潮云

云计算

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