淘宝彩票移动项目开发实践

jopen 7年前

作者按:如今越来越多的互联网产品开始在移动终端发力,终端产品越来越丰富。但是,平台差异带来的开发成本浪费很让人头疼。一段时间以来,淘宝彩票前端组也在努力寻求移动终端项目开发最佳实践,尽管诸多方面不甚成熟,但抛砖引玉,希望我们的总结整理会对大家有所启发。

        淘宝彩票客户端产品目前有两条体系:

  1. 原生应用:包括 iPhone 和 Andoird 上原生的客户端应用
  2. 嵌入式应用:作为子应用的软件包形式提供,嵌入到第三方的客户端软件中,比如淘宝主站客户端以及支付宝客户端等

        由于产品的共通性,这两类应用都是需要考虑较多复用性和开发成本的。在原生 App 中,有不少内容是可以用简单的 HTML5 来实现的,比如 iPhone 客户端中的“订单页”:

淘宝彩票移动项目开发实践淘宝彩票移动项目开发实践

        而在子应用的软件包里,则有更多的地方可以运用 HTML5 来实现,包括一些复杂的富交互。例如支付宝客户端中各彩种的实现:

淘宝彩票移动项目开发实践淘宝彩票移动项目开发实践

        App 的形式

        目前移动终端上的应用主要以 Native App 为主,这种应用的优势是:

        1,性能快,体验好;

        2,可访问本地资源,更有效的利用设备,节省流量;

        3,并且已有一定规模(App Store),付费模式明朗。

        缺点是:

        1,开发成本高,移植性差;

        2,所有发布需要经过 App Store 的审核。

        当然,目前还存在一些 Web App,用户可以直接通过浏览器来访问,例如 Gmail。这类 App 的开发和维护的成本低,可以天然的在各种终端上执行,并且容易迭代更新,无需用户进行安装。不过就现阶段而言,无论从速度上还是交互上,用户体验与 Native App 的差距还是比较大的。既然两种 App 都各有利弊,那么我们就干脆将二者相结合:用原生控件做外壳和交互,保证流畅的用户体验和完整的推广渠道;使用 HTML5 来展示内容,保证内容的迅速迭代更新,即时响应用户需求。于是就诞生了 Hybrid  App,这也是目前最流行最合适的一种 App 形式。

        对于前端工程师而言,在移动平台上没有了 IE 系列的困扰,HTML5的很多特性都可以大胆的运用。不同的是,我们需要设置一些针对移动终端的 Meta 属性,以及更合理的利用 Media Query(主流移动设备的分辨率信息)。

        JS 框架的选择

        找到适合使用 HTML5 的场景,这时就需要考虑 JS 框架的选择。首先需要明确的是,类似 jQuery 和 YUI 这种“重型”的库,由于包含了很多处理兼容性方面的代码,且 API 设计过于细致而显得太过庞大,并不适合移动端。我们可以选取一些专业的用于 Mobile 开发的 JS 类库,例如 jQuery Mobile,Sencha Touch 等。jQuery Mobile 是目前最流行的一个移动开发的框架,文档丰富,社区活跃,有很多的 UI 控件供我们使用,并且提供对多页面的支持(通过 Ajax 方式读取内容,并提供页面切换的过渡动画)。我认为 jQuery Mobile 的最强大之处就在于其 UI 方面的支持,但这一部分恰恰不是我所需要的。Sencha Touch 是 ExtJs 的移动版,对于不熟悉 ExtJs 的人来说有一定的学习成本。

        我们选择了zepto.js作为底层库,使用sea.js进行模块的管理和发布,原因是基于 CMD 规范的模块文件最终需要打包入应用的软件包里, 或是打包后发布到线上;此外,我们使用backbone.js为基础的 MVC 架构,用来剥离应用的数据部分;使用underscore.js做为前端模板引擎(backbone 重度依赖);使用iScroll.js为我们提供模拟滚动的功能。这些都是一些专业的“小库”,很适合移动端的开发。当然,具体情况需要具体分析,没有万能的框架,只有万能的开发者。如果时间允许,也可以自己来定制一套满足自身需求的基础库。毕竟在移动端,一切以 “精简”为主。

        应用架构的选择

        首先抛出两个小问题:

        1、  OPOA 近两年来在互联网得到了广泛应用,相比于之前的多页面切换和跳转的方式,这个方法也更优雅,用户体验更好。那么在移动端,OPOA 是否依旧适用呢?

        2、HTML5最大的优势就是跨平台,只需要开发一套代码,就可以完全通用于不同的终端。但是,真的一套代码就能“吃”尽所有移动终端么?

        先解答第一个问题。其实我们在开始尝试的时候,完全是参照 PC 端的 OPOA 来进行的,把很多的内容都放在一个 Page 里面,通过 URL 或 Hash 的方式来管理页面的显示。但实践表明,在移动端这样做是有很大弊端的。首先,在 PC 上显示区域比较大,我们可以先给用户展示一个框架,然后在分块的去显示内容,但是移动终端由于屏幕变小,往往同时只能展示一个区域,那么我们首先要根据 URL 判断显示哪个区域,然后再去加载这个区域的内容, 这样一来,让本来在移动端就变慢了许多的页面更加的雪上加霜。其次,为了减少请求,我们通常会把大量的前端模板都塞到这个仅有的 Page 中,不仅导致页面体积变大,性能降低,而且维护困难。因此,我认为,在移动端最好的方式还是传统的多页面开发。

        那么肯定有人会问,这样子的话岂不是会损失页面切换的流畅性,让用户不爽么。这个问题我们是可以解决的。第一就是采用 jQuery Mobile 的方式(Web App 中非常适用),切换时用 Ajax 去请求新的内容,然后再渲染到页面中。这种方法在某些特定条件下会有问题:我们仍然需要使用 URL 来进行历史的管理,如果将这些页面作为静态文件打包在客户端中的话,在一些 Android 系统的手机中,硬件会将这些带后缀的文件当成一个完整的文件去查找(例如 index.html?page=XXX),然后发生错误。第二个方法就是在 Hybrid  App 中,我们可以借助客户端来帮助我们进行页面的切换。你只需要告诉客户端即将要跳转的 URL 路径(可以是网络请求,也可以是本地静态文件),然后由客户端进行跳转。这样就可以在客户端代码里面设置 Webview 切换的动画效果,使客户端的整体风格更加的统一。

        针对第二个问题,我们刚开始的目标就是用一套代码来自适应所有的客户端。结果发现,在 iPhone 和 Android 上,尽管风格差别较大,但是整体的结构和布局是很相似的,那么只需要设置两份 CSS 文件,再提供一份 API,分别在两个平台上做实现就可以了,维护起来很方便;但是在 iPad 上,由于布局和交互变化比较大,为了复用,不得不在原代码上增加分支,不但破环了原代码的完整性,而且维护时会产生一些意想不到的错误。这样就得不偿失 了。因此,如果手机客户端和平板电脑客户端差异很大的话,最好还是分开单独进行处理,而不是为了“复用”而复用,大家可以根据具体的情况制定自己的策略。 例如彩票的 iPad 客户端,我们就是纯粹基于 HTML5 为 pad 量身定做的:

淘宝彩票移动项目开发实践

        Webview 及其与 HTML5 的通信

        Webview 是手机中内置的一个基于 webkit 内核的 SDK,是搭载 Web 页面的容器,也是负责 Web 页面和 Native App 之间相互通信的桥梁。在不同平台上 Webview 的实现是有较大差异的。

        在 Android 平台中是支持 JS 方法和 Java 方法的直接调用的,我们通过下面的代码即可实现互相调用。

//将 JAVA 对象绑定到 JavaScript 中    mWebView.addJavascriptInterface (new JsToJava (), 'stub');    //在 JavaScript 中调用 Java 方法    window.stub ();

-

function invokedByJava (data){          //do something    }    //在 Java 中调用 JavaScript 方法    public void onClick (View v) {          mWebView.loadUrl ("javascript:invokedByJava ('java_data')");    }    //打开 webview,调用页面    mWebView.loadUrl ("file:///xxx.html");

        但在 iOS 平台上,SDK 没有原生提供 JavaScript 调用 Native 代码的 API,只有反向调用的方法。要想在 JavaScript 中调用 Native,一般有两种方法:一种是 Phonegap 采用的 iframe 方法,也是比较推荐的,如下所示;另一种是直接修改页面的 location,在大部分情况是可用的,但是有时候会产生一些莫名其妙的错误。这两种方法的原理都是相同的:利用 Webview 去截获 JS 发起的特殊的网络请求,然后对其进行处理。

//Objective-C  - (BOOL) webView:(UIWebView *) webView      shouldStartLoadWithRequest:(NSURLRequest *) request      navigationType:(UIWebViewNavigationType) navigationType {          NSURL * url = [request URL];          if ([[url scheme] isEqualToString:@"gap"]) {              //在这里做 js 调用 native 的事情              //...              //完成之后回调 js              //[webView stringByEvaluatingJavaScriptFromString:                         @"alert ('done')"];              return NO;          }          return YES;  }    //通知 iPhone UIWebView 加载 url 对应的资源,url 格式为: gap:something  function loadURL (url) {      var iFrame;      iFrame = document.createElement ('iframe');      iFrame.setAttribute ('src', url);      iFrame.setAttribute ('style', 'display:none;');      iFrame.setAttribute ('height', '0px');      iFrame.setAttribute ('width', '0px');      iFrame.setAttribute ('frameborder', '0');      document.body.appendChild (iFrame);      //发起请求后将其从 DOM 上移除      iFrame.parentNode.removeChild (iFrame);      iFrame = null;  };

        除此之外,不同平台的 Webview 还需要进行一些特殊的设置,才能让其和原生的浏览器的行为表现相一致。例如:在 Android Webview 中,JavaScript 和 localStrorage 都是默认禁止的,需要提前启用;Android 的物理后退键默认是会关闭当前的 Bebview 的,而不是执行 history.back ();在 iOS5.0 以下的系统中,设置格式检查的 Meta 属性会偶尔失效,需要在 Webview 中直接关闭其格式检查等等。在 D2 沙龙的 PPT 中有详细的描述,大家有感兴趣的可以看下。

        速度/性能

        对于一个互联网产品的用户体验来说,速度和性能几乎是最重要的因素。但令人遗憾的是,这也是目前阻碍 Web App 发展的最大的障碍。原因主要有以下几个方面:

        1    低端设备多,受硬件的限制。根据木桶原理,一个木桶的水量是受最短的板的限制的。与之类似,我们在衡量页面性能的时候,也需要看其在中低端 机型上的表现,尤其是广大的 Android 千元智能机。如果 Android 所有的机型都能有 Samsung Galaxy 的表现,我们也就不用愁眉苦脸了,但是在更多时候,我们不得不为了在 HTC 野火上能够正常的展示而放弃在 Samsung 上看起来非常帅气的效果。

        2    简化的浏览器实现。本来相比桌面版的浏览器,移动版的浏览器已经在功能上做了一些简化,性能上有些差距。但是,与原生的移动浏览器相 比,Webview 会更慢。下图为一个页面分别在 Mobile Safari 和 非死book 的 iPhone 客户端中的运行结果,我们可以发现,UIWebView 中的 JS 运行时间大概是 Mobile Safari 的3-4倍,这恐怕也是 非死book 放弃 HTML5 转向原生应用的一个原因之一。

淘宝彩票移动项目开发实践

        针对上述原因,我们总结出一些性能优化的小 Tip:

        1    用 HTML5 的离线存储和本地存储进行缓存,或者将页面直接打包到客户端中,减少在网络下载中的耗时。

        2    减少 DOM 数量,尽可能少的使用 position:relative,减少对 DOM 的操作

        3    用 CSS 动画代替 JS 动画,在 Android 平台上可以平稳退化,放弃动画效果(包括 CSS3 动画)

        4    避免 GIF 图片的使用(消耗内存)

        5    如果只是在移动端使用的话,请使用 iScroll-lite 来代替 iScroll(iScroll 里面增加了很多额外的功能,比如在 PC 上模拟滚动),在允许的情况下,可以关闭滚动条(滚动条也是创建的 DOM 元素)

        关于滚动,还有一些其他的想法,大家有兴趣可以一试:

        1、将页面拆分成多个 Webview,这样子在每个 Webview 中就都是原生的实现了,但会增加一些页面间互相通信的代价。

        2、用 Canvas 来代替 Scroll,但是文字的渲染可能会有些问题。

        由于篇幅原因,这里就不赘述更详细的性能问题了~

        移动终端的网络环境

        移动应用最大的特点就是方便、随处可用。但无论是 2G 还是 3G,我国目前的网络状况都难以满足移动应用的要求。相比于 Native App,Web App 除了要下载数据和图片外,还需要下载一些额外的 js/css 文件,这些动辄上百K的静态文件对于平均速度还不到 10K 的 2G 网络来说实在是太庞大了。 因此,目前最好的应用场景还是将页面打包到客户端中,版本变化时提醒用户让其主动进行更新。尽管这样会损失一部分 web 页面的灵活性。

        调试

        开发时的调试相对方便,可以基于浏览器进行。但开发环境和最终的执行环境还是有一些差异的,需要对终端的 View 中的“页面”进行调试,现在我们的做法是 “打点”,即使用一个 Log 代理控件给代码埋点,输出 log,以此来辅助我们的调试。目前常用的工具有 Weinre 和 JSconsole 等。这些工具的原理是比较类似的:通过网络在其他机器上连接了一个调试的 GUI,捕获待调试网页的 JavaScript 运行环境来查看代码输出或者对代码求值,并可以监控和修改调试目标的 DOM 和 CSS 样式。但是由于调试不是真正的发生在移动设备上,所以无法设置和捕捉断点进行 debug。

        在最新的 iOS6 中,Safari(仅限 Mac 桌面版)中自带了 web 检查器工具,可以让我们更方便的对 iPhone/iPad 上的 Safari 进行远程调试。