喵神談:跨平臺(tái)開發(fā)時(shí)代的 (再次) 到來
這篇文章主要想談?wù)勛罱止纹鸬囊苿?dòng)開發(fā)跨平臺(tái)之風(fēng),并著重介紹和對(duì)比一下像是 Xamarin,NativeScript 和 React Native 之類的東西。不會(huì)有特別深入的技術(shù)討論,大家可以當(dāng)作一篇科普類的文章來看。
故事的開始
“一次編碼,處處運(yùn)行” 永遠(yuǎn)是程序員們的理想鄉(xiāng)。二十年前 Java 正是舉著這面大旗登場(chǎng),擊敗了眾多競(jìng)爭(zhēng)對(duì)手。但是時(shí)至今日,事實(shí)已經(jīng)證明了 Java 笨重的體型和緩慢的發(fā)展顯然已經(jīng)很難再抓住這個(gè)時(shí)代快速躍動(dòng)的腳步。在新時(shí)代的移動(dòng)大潮下,一個(gè)應(yīng)用想要取勝,***的使用體驗(yàn)可以說必不可少。使用 native 的方式固然對(duì)提升用戶體驗(yàn)很有幫助,但是移動(dòng)的現(xiàn)狀是必須針對(duì)不同平臺(tái) (至少是 iOS 和 Android) 進(jìn)行開發(fā)。這對(duì)于開發(fā)來說妥妥的是隱患和額外的負(fù)擔(dān):我們不僅需要在不同的項(xiàng)目間努力用不同的語言實(shí)現(xiàn)同樣代碼的同步,還要承擔(dān)由此帶來的后續(xù)維護(hù)任務(wù)。如果僅只限制在 iOS 和 Android 的話還行,但是如果還要繼續(xù)向 Windows Phone 等平臺(tái)拓展的話,所需要付出的代價(jià)和工數(shù)將幾何級(jí)增長(zhǎng),這顯然是難以接受的。于是,一個(gè)其實(shí)一直斷斷續(xù)續(xù)被提及但是從沒有占據(jù)過統(tǒng)治地位的概念又一次走進(jìn)了移動(dòng)開發(fā)者們的視野,那就是跨平臺(tái)開發(fā)。
本地 HTML 和 JavaScript
因?yàn)槊總€(gè)平臺(tái)都有瀏覽器,也都有 WebView 控件,所以我們可以使用 HTML,CSS 和 JavaScript 來將 web 的內(nèi)容和體驗(yàn)搬到本地。通過這樣做我們可以將邏輯和 UI 渲染部分都統(tǒng)一,以減少開發(fā)和維護(hù)成本。這種方式開發(fā)的 app 一般被稱為 Hybrid app,像 PhoneGap 或者 Cordova 這樣的解決方案就是典型的應(yīng)用。除了使用前端開發(fā)的一套技巧來構(gòu)建頁面和交互以外,一般這類框架還會(huì)提供一些訪問設(shè)備的接口,比如相機(jī)和 GPS 等。
雖然使用全網(wǎng)頁的開發(fā)策略和環(huán)境可以帶來代碼維護(hù)的便利,但是這種方式是有致命弱點(diǎn)的,那就是緩慢的渲染速度和難以駕馭的動(dòng)畫效果。這兩者對(duì)于用戶體驗(yàn)是致命而且難以接受的。隨著三年前 Facebook 使用 native 代碼重新構(gòu)建 Facebook 的手機(jī) app 這一標(biāo)志性事件的發(fā)生,曾經(jīng)一度占領(lǐng)半壁江山的網(wǎng)頁套殼的 app 的發(fā)展也日漸式微。特別在現(xiàn)在對(duì)于用戶體驗(yàn)的追求幾近苛刻的現(xiàn)在,呆板的動(dòng)畫效果和生硬的交互體驗(yàn)已經(jīng)完全無法滿足人民群眾對(duì)高質(zhì)量 app 的心理預(yù)期了。
跨平臺(tái)之心不死的我們?cè)撛趺崔k
想要解決用戶體驗(yàn)的問題,基本還是需要回到 native 來進(jìn)行開發(fā),但是這種行為必然會(huì)與平臺(tái)綁定。世界上總是有聰明人的,并且他們總會(huì)利用看起來更加聰明但是實(shí)際上卻很笨的電腦來做那些很笨的事情 (恰得其所)。其中一件事情就是自動(dòng)將某個(gè)平臺(tái)的代碼轉(zhuǎn)換到另外的平臺(tái)上去。有一家英國(guó)的小公司正在做這樣的事情,MyAppConverter 想做的事情就是把 iOS 的代碼自動(dòng)轉(zhuǎn)成 Java 的。但是很可惜,如果你嘗試過的話,就知道他們的產(chǎn)品暫時(shí)還處于無法實(shí)用的狀態(tài)。
在這條路的另一個(gè)分叉上有一家公司走得更遠(yuǎn),它叫做 Apportable。他們?cè)谟螒虻霓D(zhuǎn)換上已經(jīng)取得了很大的成果,像是 Kingdom Rush 或者 Mega Run 這樣的大作都使用了這家的服務(wù)將游戲從 iOS 轉(zhuǎn)換到 Android,并且非常成功??梢院敛豢鋸埖卣f,Apportable 是除開直接使用像 Unity 或者 Cocos2d-x 以外的另一套誘人的游戲跨平臺(tái)解決方案?;旧夏憧梢允褂?Objective-C 或者 Swift 來在熟悉的平臺(tái)上開發(fā),而不必去觸碰像是 C++ 這樣的怪獸 (雖然其實(shí)在游戲開發(fā)中也不會(huì)碰到很難的 C++)。
但是好消息終結(jié)于游戲開發(fā)了,因?yàn)橛螒蛟诓煌脚_(tái)上體驗(yàn)不會(huì)差別很大,也很少用到不同平臺(tái)的不同特性,所以處理起來相對(duì)容易。當(dāng)我們想開發(fā)一個(gè)非游戲的 app 時(shí),事情就要復(fù)雜得多。雖然 Apportable 有一個(gè)計(jì)劃讓 app 轉(zhuǎn)換也能可行,但是估計(jì)還需要一段時(shí)間我們才能看到它的推出。
新的希望
其實(shí)跨平臺(tái)開發(fā)***的問題還是針對(duì)不同的平臺(tái) UI 和體驗(yàn)的不同。如果忽視掉這個(gè)最困難的問題,只是共用邏輯部分的代碼的話,問題一下子就簡(jiǎn)單不少。十多年前,當(dāng) .NET 剛剛被公布,大家對(duì)新時(shí)代的開發(fā)充滿期待的同時(shí),一群喜歡搗鼓的 Hacker 就在盤算要如何將 .NET 和 C# 搬到 Linux 上去。而這就是 Mono 的起源。Mono 通過在其他平臺(tái)上實(shí)現(xiàn)和 Windows 平臺(tái)下功能相同的 Common Language Runtime 來運(yùn)行 .NET 中間代碼?,F(xiàn)在 Mono 社區(qū)已經(jīng)足夠強(qiáng)大,并且不僅僅支持 Linux 平臺(tái),對(duì)移動(dòng)設(shè)備也同樣支持。Mono 背后的支撐企業(yè) Xamarin 也順理成章并適時(shí)地推出了一整套的移動(dòng)跨平臺(tái)解決方案。
Xamarin 的思路相對(duì)簡(jiǎn)單,那就是使用 C# 來完成所有平臺(tái)共用的,和平臺(tái)無關(guān)的 app 邏輯部分;然后由于各個(gè)平臺(tái)的 UI 和交互不同,使用預(yù)先由 Xamarin 封裝好的 C# API 來訪問和操控 native 的控件,進(jìn)行分別針對(duì)不同平臺(tái)的 UI 開發(fā)。
雖然只有邏輯部分實(shí)現(xiàn)了真正的跨平臺(tái),而表現(xiàn)層已然需要分別開發(fā),但這確實(shí)也是一種在完整照顧用戶體驗(yàn)的基礎(chǔ)上的好方式 -- 至少開發(fā)語言得到了統(tǒng)一。因?yàn)?Xamarin 解決方案中的純 C# 環(huán)境和有深厚的 .NET 技術(shù)背景做支撐,這個(gè)項(xiàng)目現(xiàn)在也受到了微軟的支持和重視。
不過存在的致命問題是針對(duì)某個(gè)特定平臺(tái)你所能使用的 API 是由 Xamarin 所決定的。也就是說一旦 iOS 或者 Android 平臺(tái)推出了新的 SDK,加入了新的功能,你必須要等 Xamarin 的工程師先進(jìn)行封裝,然后才能在自己的項(xiàng)目中使用。這種延遲往往可能是致命的,因?yàn)楝F(xiàn)在 AppStore 對(duì)于新功能的首頁推薦往往只會(huì)有新系統(tǒng)上線后的一兩周,錯(cuò)過這段時(shí)間的話,可能你的 app 就再無翻身之日。而且如果你想使用一些第三方框架的話,將不得不自己動(dòng)手將它們打包成二進(jìn)制,并且寫 binding 為它們提供 C# 的封裝,除非已經(jīng)有別人幫你做過這件事情了。
另外,因?yàn)?UI 部分還是各自為戰(zhàn),所以不同的代碼庫(kù)依然存在于項(xiàng)目之中,這對(duì)工作量的減少的幫助有限,并且之后的維護(hù)中還是存在無法同步和版本差異的隱患。但是總體來說,Xamarin 是一個(gè)很不錯(cuò)的解決跨平臺(tái)開發(fā)的思路了。(如果拋開價(jià)格因素的話)
#p#
NativeScript
NativeScript 是一家名叫 Telerik 的名不見經(jīng)傳保加利亞公司剛剛宣布的項(xiàng)目。雖然 Telerik 并不是很出名,但是卻已經(jīng)在 hybrid app 和跨平臺(tái)開發(fā)這條路上走了很久。
JavaScript 因?yàn)閺V泛的群眾基礎(chǔ)和易學(xué)易用的語言特點(diǎn),已經(jīng)大有一統(tǒng)天下的趨勢(shì)。而現(xiàn)在主流移動(dòng)平臺(tái)也都有強(qiáng)勁的處理 JavaScript 的能力 (iOS 7 以后的 JavaScriptCore 以及 Android 自帶的 V8 JavaScript Engine),因?yàn)槭褂?JavaScript 來跨平臺(tái)水到渠成地成為了一個(gè)可選項(xiàng)。
在此要吐槽一下,JavaScript 真的是一家公司,一個(gè)項(xiàng)目拯救回來的語言。V8 之前誰能想到 JavaScript 能有今日...
NativeScript 的思路就是使用移動(dòng)平臺(tái)的 JavaScript 引擎來進(jìn)行跨平臺(tái)開發(fā)。邏輯部分自然無需多說,關(guān)鍵在于如何使用平臺(tái)特性,JavaScript 要怎樣才能調(diào)用 native 的東西呢。NativeScript 給出的答案是通過反射得到所有平臺(tái) API,預(yù)編譯它們,然后將這些 API 注入到 JavaScript 運(yùn)行環(huán)境,接下來在 Javascript 調(diào)用后攔截這個(gè)調(diào)用,并運(yùn)行 native 代碼。
在此不打算展開說 NativeScript 詳細(xì)的原理,如果你對(duì)它感興趣,不妨去看看 Telerik 的員工的寫的這篇博客以及發(fā)布時(shí)的 Keynote。
這么做***的好處是你可以任意使用***的平臺(tái) API 以及各種第三方庫(kù)。通過對(duì)元數(shù)據(jù)的反射和注入,NativeScript 的 JavaScript 運(yùn)行環(huán)境總能找到它們,觸發(fā)相應(yīng)的調(diào)用以及最終訪問到 iOS 或者 Android 的平臺(tái)代碼。***版本的平臺(tái) SDK 或者第三方庫(kù)的內(nèi)容總是可以被獲取和使用,而不需要有什么限制。
舉個(gè)簡(jiǎn)單的例子,比如創(chuàng)建一個(gè)文件,為 iOS 開發(fā)的話,可以直接在 JavaScript 里寫這樣的代碼:
- var fileManager = NSFileManager.defaultManager();
- fileManager.createFileAtPathContentsAttributes( path );
而對(duì)應(yīng)的 Android 版本也許是:
- new java.io.File( path );
你不需要擔(dān)心 NSFileManager 或者 java.io 這類東西的存在,而是可以任意地使用它們!
如果僅只是這樣的話,使用上還是非常不便。NativeScript 借助類似 node 的一套包管理系統(tǒng),用 modules 對(duì)這些不同平臺(tái)的代碼進(jìn)行了統(tǒng)一的封裝。比如上面的代碼,可以統(tǒng)一使用下面的形式替換:
- var fs = require( "file-system" );
- var file = new fs.File( path );
寫過 node 的同學(xué)肯定對(duì)這樣的形式很熟悉了,這里的 file-system 就是 NativeScript 進(jìn)行的統(tǒng)一平臺(tái)的封裝?,F(xiàn)在的完整的封裝列表可以參見這個(gè) repo。因?yàn)閷懛ê芎?jiǎn)單,所以開發(fā)者如果有需要的話,也可以創(chuàng)建自己的封裝,甚至使用 npm 來發(fā)布和共享 (當(dāng)然也有獲取別人寫的封裝)。因?yàn)橐蕾囉谝延械某墒彀芾硐到y(tǒng),所以可以認(rèn)為擴(kuò)展性是有保證的。
對(duì)于 UI 的處理,NativeScript 選擇了使用類似 Android 的 XML 的方式進(jìn)行布局,然后用 CSS 來控制控件的樣式。這是一種很有趣的想法,雖然 UI 的布局靈活性上無法與針對(duì)不同平臺(tái)的 native 布局相比,但是其實(shí)和傳統(tǒng)的 Android 布局已經(jīng)很接近。舉個(gè)布局文件的例子就可見一斑:
- <Page loaded="onPageLoaded">
- <GridLayout rows="auto, *">
- <StackLayout orientation="horizontal" row="0">
- <TextField width="200" text="{{ task }}" hint="Enter a task" id="task" />
- <Button text="Add" tap="add"></Button>
- </StackLayout>
- <ListView items="{{ tasks }}" row="1">
- <ListView.itemTemplate>
- <Label text="{{ name }}" />
- </ListView.itemTemplate>
- </ListView>
- </GridLayout>
- </Page>
熟悉 Android 或者 Window Phone 開發(fā)的讀者可能會(huì)感到找到了組織。你可能已經(jīng)注意到,相比于 Android 的布局方式,NativeScript 天生支持 MVVM 和 data binding,這在開發(fā)中會(huì)十分方便 (但是性能上暫時(shí)就未知了)。而像是 Button 或者 ListView 這樣的控件都是由 modules 映射到對(duì)應(yīng)平臺(tái)的系統(tǒng)標(biāo)準(zhǔn)控件。這些控件的話都是使用 css 來指定樣式的,這與傳統(tǒng)的網(wǎng)頁開發(fā)沒太大區(qū)別。
nativescript-ui
NativeScript 代表的思路是使用大量 web 開發(fā)的技巧來進(jìn)行 app 開發(fā)。這是一個(gè)很值得期待的方向,相信也會(huì)受到很多前端開發(fā)者的歡迎 -- 因?yàn)楣ぞ哝満驼Z言都非常熟悉。但是這個(gè)方向依然面臨的***挑戰(zhàn)還是 UI,現(xiàn)在看來開發(fā)者是被限制在預(yù)先定義好的 UI 控件中的,而不能像傳統(tǒng) Hybrid app 那樣使用 HTML5 的元素。這使得如何能開發(fā)出高度自定義的 UI 和交互成為問題。另一個(gè)可能存在的問題是最終 app 的尺寸。因?yàn)槲覀冃枰獙⒄麄€(gè)元數(shù)據(jù)注入到運(yùn)行環(huán)境中,也存在很多在不同語言中的編譯,所以不可避免地會(huì)造成較大的 app 尺寸。***一個(gè)挑戰(zhàn)是對(duì)于像 app 這樣的工程,沒有類型檢查和編譯器的幫助,開發(fā)起來難度會(huì)比較大。另外在調(diào)試的時(shí)候也可能會(huì)有傳統(tǒng) app 開發(fā)中不曾遇到的問題。
總體來看,NativeScript 是很有希望的一個(gè)方案。如果它能實(shí)現(xiàn)自己的愿景,那必將是跨平臺(tái)這塊大蛋糕的有力競(jìng)爭(zhēng)者。當(dāng)然,現(xiàn)在 NativeScript 還太年輕,也還有很多問題。不妨多給這個(gè)項(xiàng)目一點(diǎn)時(shí)間,看看正式版本上線后的表現(xiàn)。
React Native
Facebook 幾個(gè)月前公布了 React Native,而今天這個(gè)項(xiàng)目終于在萬眾期待下發(fā)布了。
React Native 在一定程度上和 NativeScript 的概念類似:都是使用 JavaScript 和 native UI 來實(shí)現(xiàn) app (所以說 JavaScript 真是有一桶漿糊的趨勢(shì)..如果你現(xiàn)在還不會(huì)寫幾句 JavaScript 的話,建議盡早學(xué)一學(xué))。但是它們的出發(fā)點(diǎn)略有不同,React Native 在首頁上就寫明了,使用這個(gè)庫(kù)可以:
- learn once, write anywhere
而并不是 "run anywhere"。所以說 React Native 想要達(dá)成的目標(biāo)其實(shí)并不是一個(gè)跨平臺(tái) app 開發(fā)方案,而是讓你能夠使用相似的方法和同樣的語言來在不同平臺(tái)進(jìn)行開發(fā)的工具。另外,React Native 的主要工作是構(gòu)建響應(yīng)式的 View,其長(zhǎng)處在于根據(jù)應(yīng)用所處的狀態(tài)來決定 View 的表現(xiàn)狀態(tài)。而對(duì)于其他一些系統(tǒng)平臺(tái)的 API 來說,就顯得比較無力。而正是由于這些要素,使得 React Native 確實(shí)不是一個(gè)跨平臺(tái)的好選擇。
那為什么我們還要在這篇以 “跨平臺(tái)” 為主題的文章里談及 React Native 呢?
因?yàn)殡m然 Facebook 不是以跨平臺(tái)為出發(fā)點(diǎn),但是卻不可能阻止工程師想要這么來使用它。從原理上來說,React Native 繼承了 React.js 的虛擬 DOM 的思想,只不過這次變成了虛擬 View。事實(shí)上這個(gè)框架提供了一組 native 實(shí)現(xiàn)的 view (在 iOS 平臺(tái)上是 RCT 開頭的一系列類)。我們?cè)趯?JavaScript (更準(zhǔn)確地說,對(duì)于 React Native,我們寫的是帶有 XML 的 JavaScript:JSX) 時(shí),通過將虛擬 View 添加并綁定到注冊(cè)的模塊中,在 native 側(cè)用 JavaScript 運(yùn)行環(huán)境 (對(duì)于 iOS 來說也就是 JavaScriptCore) 執(zhí)行編譯并注入好的 JavaScript 代碼,獲取其對(duì) UI 的調(diào)用,將其截取并橋接到 native 代碼中進(jìn)行對(duì)應(yīng)部件的渲染。而在布局方面,依然是通過 CSS 來實(shí)現(xiàn)的。
這里整個(gè)過程和思路與 NativeScript 有相似之處,但是在與 native 橋接的時(shí)候采取的策略完全相反。React Native 是將 native 側(cè)作為渲染的后端,去提供統(tǒng)一的 JavaScript 側(cè)所需要的 View 的實(shí)體。NativeScript 基本算反其道行之,是在 JavaScript 里寫分開的中間層來分別對(duì)應(yīng)不同平臺(tái)。
對(duì)于非 View 的處理,對(duì)于 iOS,React Native 提供了 RCTBridgeModule 協(xié)議,我們可以通過在 native 側(cè)實(shí)現(xiàn)這個(gè)協(xié)議來提供 JavaScript 中的訪問可能。另外,回調(diào)和事件發(fā)送等也可以通過相應(yīng)的 native 代碼來完成。
總結(jié)來說,如果想要把 React Native 作為一個(gè)跨平臺(tái)方案來看的話 (實(shí)際上也并不應(yīng)當(dāng)如此),那么單靠 JavaScript 一側(cè)是難以完成的,因?yàn)橐豢钣幸饬x的 app 不太可能完全不借助平臺(tái) API 的力量。但是畢竟這個(gè)項(xiàng)目背后是 Facebook,如果 Facebook 想要通過自己的影響力自立一派的話,必定會(huì)通過不斷改進(jìn)和工具鏈的完善,將 app 開發(fā)的風(fēng)向引導(dǎo)至自己旗下。對(duì)于原來就使用 React.js 的開發(fā)者來說,這個(gè)框架降低了他們進(jìn)入 app 開發(fā)的門檻。但是對(duì)于已經(jīng)在做 native app 開發(fā)的人來說,是否值得和需要投入精力進(jìn)行學(xué)習(xí),還需要觀察 Facebook 接下來動(dòng)作。
不過現(xiàn)在 React Native 的正式發(fā)布才過去了不到 24 小時(shí),我想我們有的是時(shí)間來思考和檢閱這樣一個(gè)框架。
總結(jié)
當(dāng)然還有一些其他方案,比如 Titanium 等?,F(xiàn)在使用跨平臺(tái)方案開發(fā) app 的案例并不算很多,但是無論在項(xiàng)目管理還是維護(hù)上,跨平臺(tái)始終是一種誘惑。它們都解決了一些 Hybrid app 的遺留問題,但是它們又都有一些非 native app 的普遍面臨的陰影。誰能找到一個(gè)好的方式來解決像是自定義 UI,API 擴(kuò)展性以及 app 尺寸這樣的問題,誰就將能在這個(gè)市場(chǎng)中取得領(lǐng)先或者勝利,從而引導(dǎo)之后的開發(fā)潮流。
但是誰又知道***誰能取勝呢?也有可能大家在跨平臺(tái)的道路上再一次全體失敗。伺機(jī)而動(dòng)也許是現(xiàn)在開發(fā)者們很好的選擇,不過我的建議是提前學(xué)點(diǎn)兒 JavaScript 總是不會(huì)出錯(cuò)的。