跨終端Web之Hybrid App

Native App(如下簡稱Native)和Mobile Web(如下簡稱Web)兩者混合開發的產物被稱爲Hybrid App(如下簡稱Hybrid)。Hybrid並非什麼新概念,最先能夠追溯到Symbian時代,直到iOS和Android出現以後才充分展示出價值。javascript

Hybrid簡史

1. 背景

Hybrid既利用了Native App豐富的設備API(Device API),又能擁有Mobile Web的跨平臺、高效開發、快速發佈的能力,對於至關龐大的應用場景而言都是適用的。html

Hybrid優點在於:前端

  • 跨平臺

    Web內容能夠作到開發一次,全部平臺生效,諸多產品須要這種能力。html5

  • 快速發佈

    iOS平臺,Apple Store平均審覈週期1~2周不等,甚至更長,產品的發佈週期從2周到1月,這對須要快速發佈的產品而言難以接受。java

    Android平臺,應用商店衆多,發佈過程煩瑣。雖然能夠應用內升級,可是帶來的問題是新App須要經過應用商店,此外APK體積龐大,2G/3G環境下體驗差。android

  • 高效開發

    Web開發通過20年的發展,已經將結構(HTML)、表現(CSS)、行爲(JavaScript)3部分很好地分離開,在分工協做、開發效率上會具明顯優點。git

  • 豐富的Device API

    Web(HTML5)強調通用性,受限於標準和瀏覽器實現,許多有用的系統功能未能獲得支持(或部分支持)。而Native最大的優點在於設備API的調用能力,只要橋接Native和Web,Web也就可以擁有這種能力。github

Hybrid劣勢表現爲:web

  1. CPU/GPU密集類應用目前看更適合Native,例如極品飛車這樣的遊戲。這種劣勢是在不斷弱化的,正如 「CSS Transform 3D」引入GPU大大緩解了Web動畫不流暢的問題。
  2. 靜態資源從服務器端加載致使的UI展現延遲問題。這個問題能夠經過Native攔截WebView通訊加載已打包的公共庫來緩解。

2. 簡史

  • 雛形

    雛形階段大體爲:chrome

    • 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

(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框架一般提供如下功能。

  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作了第二次分享。


  • [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%E2%80%99s-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
相關文章
相關標籤/搜索