跨平台開發時代的 (再次) 到來?

這篇文章主要想談談最近又颳起的移動開發跨平台之風,並著重介紹和對比一下像是 Xamarin,NativeScript 和 React Native 之類的東西。不會有特別深入的技術討論,大家可以當作一篇科普類的文章來看。

故事的開始

「一次編碼,處處運行」 永遠是程序員們的理想鄉。二十年前 Java 正是舉著這面大旗登場,擊敗了眾多競爭對手。但是時至今日,事實已經證明了 Java 笨重的體型和緩慢的發展顯然已經很難再抓住這個時代快速躍動的腳步。在新時代的移動大潮下,一個應用想要取勝,完美的使用體驗可以說必不可少。使用 native 的方式固然對提升用戶體驗很有幫助,但是移動的現狀是必須針對不同平台 (至少是 iOS 和 Android) 進行開發。這對於開發來說妥妥的是隱患和額外的負擔:我們不僅需要在不同的項目間努力用不同的語言實現同樣代碼的同步,還要承擔由此帶來的後續維護任務。如果僅只限制在 iOS 和 Android 的話還行,但是如果還要繼續向 Windows Phone 等平台拓展的話,所需要付出的代價和工數將幾何級增長,這顯然是難以接受的。於是,一個其實一直斷斷續續被提及但是從沒有佔據過統治地位的概念又一次走進了移動開發者們的視野,那就是跨平台開發。

本地 HTML 和 JavaScript

因為每個平台都有瀏覽器,也都有 WebView 控制項,所以我們可以使用 HTML,CSS 和 JavaScript 來將 web 的內容和體驗搬到本地。通過這樣做我們可以將邏輯和 UI 渲染部分都統一,以減少開發和維護成本。這種方式開發的 app 一般被稱為 Hybrid app,像 PhoneGap 或者 Cordova 這樣的解決方案就是典型的應用。除了使用前端開發的一套技巧來構建頁面和交互以外,一般這類框架還會提供一些訪問設備的介面,比如相機和 GPS 等。

雖然使用全網頁的開發策略和環境可以帶來代碼維護的便利,但是這種方式是有致命弱點的,那就是緩慢的渲染速度和難以駕馭的動畫效果。這兩者對於用戶體驗是致命而且難以接受的。隨著三年前 Facebook 使用 native 代碼重新構建 Facebook 的手機 app 這一標誌性事件的發生,曾經一度佔領半壁江山的網頁套殼的 app 的發展也日漸式微。特別在現在對於用戶體驗的追求幾近苛刻的現在,呆板的動畫效果和生硬的交互體驗已經完全無法滿足人民群眾對高質量 app 的心理預期了。

跨平台之心不死的我們該怎麼辦

想要解決用戶體驗的問題,基本還是需要回到 native 來進行開發,但是這種行為必然會與平台綁定。世界上總是有聰明人的,並且他們總會利用看起來更加聰明但是實際上卻很笨的電腦來做那些很笨的事情 (恰得其所)。其中一件事情就是自動將某個平台的代碼轉換到另外的平台上去。有一家英國的小公司正在做這樣的事情,MyAppConverter 想做的事情就是把 iOS 的代碼自動轉成 Java 的。但是很可惜,如果你嘗試過的話,就知道他們的產品暫時還處於無法實用的狀態。

在這條路的另一個分叉上有一家公司走得更遠,它叫做 Apportable。他們在遊戲的轉換上已經取得了很大的成果,像是 Kingdom Rush 或者 Mega Run 這樣的大作都使用了這家的服務將遊戲從 iOS 轉換到 Android,並且非常成功。可以毫不誇張地說,Apportable 是除開直接使用像 Unity 或者 Cocos2d-x 以外的另一套誘人的遊戲跨平台解決方案。基本上你可以使用 Objective-C 或者 Swift 來在熟悉的平台上開發,而不必去觸碰像是 C++ 這樣的怪獸 (雖然其實在遊戲開發中也不會碰到很難的 C++)。

但是好消息終結於遊戲開發了,因為遊戲在不同平台上體驗不會差別很大,也很少用到不同平台的不同特性,所以處理起來相對容易。當我們想開發一個非遊戲的 app 時,事情就要複雜得多。雖然 Apportable 有一個計劃讓 app 轉換也能可行,但是估計還需要一段時間我們才能看到它的推出。

新的希望

Xamarin

其實跨平台開發最大的問題還是針對不同的平台 UI 和體驗的不同。如果忽視掉這個最困難的問題,只是共用邏輯部分的代碼的話,問題一下子就簡單不少。十多年前,當 .NET 剛剛被公布,大家對新時代的開發充滿期待的同時,一群喜歡搗鼓的 Hacker 就在盤算要如何將 .NET 和 C# 搬到 Linux 上去。而這就是 Mono 的起源。Mono 通過在其他平台上實現和 Windows 平台下功能相同的 Common Language Runtime 來運行 .NET 中間代碼。現在 Mono 社區已經足夠強大,並且不僅僅支持 Linux 平台,對移動設備也同樣支持。Mono 背後的支撐企業 Xamarin 也順理成章並適時地推出了一整套的移動跨平台解決方案。

Xamarin 的思路相對簡單,那就是使用 C# 來完成所有平台共用的,和平台無關的 app 邏輯部分;然後由於各個平台的 UI 和交互不同,使用預先由 Xamarin 封裝好的 C# API 來訪問和操控 native 的控制項,進行分別針對不同平台的 UI 開發。

雖然只有邏輯部分實現了真正的跨平台,而表現層已然需要分別開發,但這確實也是一種在完整照顧用戶體驗的基礎上的好方式 -- 至少開發語言得到了統一。因為 Xamarin 解決方案中的純 C# 環境和有深厚的 .NET 技術背景做支撐,這個項目現在也受到了微軟的支持和重視。

不過存在的致命問題是針對某個特定平台你所能使用的 API 是由 Xamarin 所決定的。也就是說一旦 iOS 或者 Android 平台推出了新的 SDK,加入了新的功能,你必須要等 Xamarin 的工程師先進行封裝,然後才能在自己的項目中使用。這種延遲往往可能是致命的,因為現在 AppStore 對於新功能的首頁推薦往往只會有新系統上線後的一兩周,錯過這段時間的話,可能你的 app 就再無翻身之日。而且如果你想使用一些第三方框架的話,將不得不自己動手將它們打包成二進位,並且寫 binding 為它們提供 C# 的封裝,除非已經有別人幫你做過這件事情了。

另外,因為 UI 部分還是各自為戰,所以不同的代碼庫依然存在於項目之中,這對工作量的減少的幫助有限,並且之後的維護中還是存在無法同步和版本差異的隱患。但是總體來說,Xamarin 是一個很不錯的解決跨平台開發的思路了。(如果拋開價格因素的話)

NativeScript

NativeScript 是一家名叫 Telerik 的名不見經傳保加利亞公司剛剛宣布的項目。雖然 Telerik 並不是很出名,但是卻已經在 hybrid app 和跨平台開發這條路上走了很久。

JavaScript 因為廣泛的群眾基礎和易學易用的語言特點,已經大有一統天下的趨勢。而現在主流移動平台也都有強勁的處理 JavaScript 的能力 (iOS 7 以後的 JavaScriptCore 以及 Android 自帶的 V8 JavaScript Engine),因為使用 JavaScript 來跨平台水到渠成地成為了一個可選項。

在此要吐槽一下,JavaScript 真的是一家公司,一個項目拯救回來的語言。V8 之前誰能想到 JavaScript 能有今日...

NativeScript 的思路就是使用移動平台的 JavaScript 引擎來進行跨平台開發。邏輯部分自然無需多說,關鍵在於如何使用平台特性,JavaScript 要怎樣才能調用 native 的東西呢。NativeScript 給出的答案是通過反射得到所有平台 API,預編譯它們,然後將這些 API 注入到 JavaScript 運行環境,接下來在 Javascript 調用後攔截這個調用,並運行 native 代碼。

在此不打算展開說 NativeScript 詳細的原理,如果你對它感興趣,不妨去看看 Telerik 的員工的寫的這篇博客以及發布時的 Keynote。

這麼做最大的好處是你可以任意使用最新的平台 API 以及各種第三方庫。通過對元數據的反射和注入,NativeScript 的 JavaScript 運行環境總能找到它們,觸發相應的調用以及最終訪問到 iOS 或者 Android 的平台代碼。最新版本的平台 SDK 或者第三方庫的內容總是可以被獲取和使用,而不需要有什麼限制。

舉個簡單的例子,比如創建一個文件,為 iOS 開發的話,可以直接在 JavaScript 里寫這樣的代碼:

var fileManager = NSFileManager.defaultManager(); fileManager.createFileAtPathContentsAttributes( path );

而對應的 Android 版本也許是:

new java.io.File( path );

你不需要擔心 NSFileManager 或者 java.io 這類東西的存在,而是可以任意地使用它們!

如果僅只是這樣的話,使用上還是非常不便。NativeScript 藉助類似 node 的一套包管理系統,用 modules 對這些不同平台的代碼進行了統一的封裝。比如上面的代碼,可以統一使用下面的形式替換:

var fs = require( "file-system" ); var file = new fs.File( path );

寫過 node 的同學肯定對這樣的形式很熟悉了,這裡的 file-system 就是 NativeScript 進行的統一平台的封裝。現在的完整的封裝列表可以參見這個 repo。因為寫法很簡單,所以開發者如果有需要的話,也可以創建自己的封裝,甚至使用 npm 來發布和共享 (當然也有獲取別人寫的封裝)。因為依賴於已有的成熟包管理系統,所以可以認為擴展性是有保證的。

對於 UI 的處理,NativeScript 選擇了使用類似 Android 的 XML 的方式進行布局,然後用 CSS 來控制控制項的樣式。這是一種很有趣的想法,雖然 UI 的布局靈活性上無法與針對不同平台的 native 布局相比,但是其實和傳統的 Android 布局已經很接近。舉個布局文件的例子就可見一斑:

<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 開發的讀者可能會感到找到了組織。你可能已經注意到,相比於 Android 的布局方式,NativeScript 天生支持 MVVM 和 data binding,這在開發中會十分方便 (但是性能上暫時就未知了)。而像是 Button 或者 ListView 這樣的空間都是由 modules 映射到對應平台的系統標準控制項。這些控制項的話都是使用 css 來指定樣式的,這與傳統的網頁開發沒太大區別。

NativeScript 代表的思路是使用大量 web 開發的技巧來進行 app 開發。這是一個很值得期待的方向,相信也會受到很多前端開發者的歡迎 -- 因為工具鏈和語言都非常熟悉。但是這個方向依然面臨的最大挑戰還是 UI,現在看來開發者是被限制在預先定義好的 UI 控制項中的,而不能像傳統 Hybrid app 那樣使用 HTML5 的元素。這使得如何能開發出高度自定義的 UI 和交互成為問題。另一個可能存在的問題是最終 app 的尺寸。因為我們需要將整個元數據注入到運行環境中,也存在很多在不同語言中的編譯,所以不可避免地會造成較大的 app 尺寸。最後一個挑戰是對於像 app 這樣的工程,沒有類型檢查和編譯器的幫助,開發起來難度會比較大。另外在調試的時候也可能會有傳統 app 開發中不曾遇到的問題。

總體來看,NativeScript 是很有希望的一個方案。如果它能實現自己的願景,那必將是跨平台這塊大蛋糕的有力競爭者。當然,現在 NativeScript 還太年輕,也還有很多問題。不妨多給這個項目一點時間,看看正式版本上線後的表現。

React Native

Facebook 幾個月前公布了 React Native,而今天這個項目終於在萬眾期待下發布了。

React Native 在一定程度上和 NativeScript 的概念類似:都是使用 JavaScript 和 native UI 來實現 app (所以說 JavaScript 真是有一桶漿糊的趨勢..如果你現在還不會寫幾句 JavaScript 的話,建議儘早學一學)。但是它們的出發點略有不同,React Native 在首頁上就寫明了,使用這個庫可以:

learn once, write anywhere

而並不是 "run anywhere"。所以說 React Native 想要達成的目標其實並不是一個跨平台 app 開發方案,而是讓你能夠使用相似的方法和同樣的語言來在不同平台進行開發的工具。另外,React Native 的主要工作是構建響應式的 View,其長處在於根據應用所處的狀態來決定 View 的表現狀態。而對於其他一些系統平台的 API 來說,就顯得比較無力。而正是由於這些要素,使得 React Native 確實不是一個跨平台的好選擇。

那為什麼我們還要在這篇以 「跨平台」 為主題的文章里談及 React Native 呢?

因為雖然 Facebook 不是以跨平台為出發點,但是卻不可能阻止工程師想要這麼來使用它。從原理上來說,React Native 繼承了 React.js 的虛擬 DOM 的思想,只不過這次變成了虛擬 View。事實上這個框架提供了一組 native 實現的 view (在 iOS 平台上是 RCT 開頭的一系列類)。我們在寫 JavaScript (更準確地說,對於 React Native,我們寫的是帶有 XML 的 JavaScript:JSX) 時,通過將虛擬 View 添加並綁定到註冊的模塊中,在 native 側用 JavaScript 運行環境 (對於 iOS 來說也就是 JavaScriptCore) 執行編譯並注入好的 JavaScript 代碼,獲取其對 UI 的調用,將其截取並橋接到 native 代碼中進行對應部件的渲染。而在布局方面,依然是通過 CSS 來實現的。

這裡整個過程和思路與 NativeScript 有相似之處,但是在與 native 橋接的時候採取的策略完全相反。React Native 是將 native 側作為渲染的後端,去提供統一的 JavaScript 側所需要的 View 的實體。NativeScript 基本算反其道行之,是在 JavaScript 里寫分開的中間層來分別對應不同平台。

對於非 View 的處理,對於 iOS,React Native 提供了 RCTBridgeModule 協議,我們可以通過在 native 側實現這個協議來提供 JavaScript 中的訪問可能。另外,回調和事件發送等也可以通過相應的 native 代碼來完成。

總結來說,如果想要把 React Native 作為一個跨平台方案來看的話 (實際上也並不應當如此),那麼單靠 JavaScript 一側是難以完成的,因為一款有意義的 app 不太可能完全不藉助平台 API 的力量。但是畢竟這個項目背後是 Facebook,如果 Facebook 想要通過自己的影響力自立一派的話,必定會通過不斷改進和工具鏈的完善,將 app 開發的風向引導至自己旗下。對於原來就使用 React.js 的開發者來說,這個框架降低了他們進入 app 開發的門檻。但是對於已經在做 native app 開發的人來說,是否值得和需要投入精力進行學習,還需要觀察 Facebook 接下來動作。

不過現在 React Native 的正式發布才過去了不到 24 小時,我想我們有的是時間來思考和檢閱這樣一個框架。

總結

當然還有一些其他方案,比如 Titanium 等。現在使用跨平台方案開發 app 的案例並不算很多,但是無論在項目管理還是維護上,跨平台始終是一種誘惑。它們都解決了一些 Hybrid app 的遺留問題,但是它們又都有一些非 native app 的普遍面臨的陰影。誰能找到一個好的方式來解決像是自定義 UI,API 擴展性以及 app 尺寸這樣的問題,誰就將能在這個市場中取得領先或者勝利,從而引導之後的開發潮流。

但是誰又知道最後誰能取勝呢?也有可能大家在跨平台的道路上再一次全體失敗。伺機而動也許是現在開發者們很好的選擇,不過我的建議是提前學點兒 JavaScript 總是不會出錯的。


推薦閱讀:

萬億元智慧物流市場待瓜分,你的信息化需要全新升級
掌門1對1口碑怎麼樣?
這才是你要的未來簡史-讀《Machine Platform Crowd》
互聯網金融十周年:金聯儲榮獲天涯財富盛典「最受關注互金平台獎」

TAG:時代 | 平台 | 跨平台 |