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

OneV's Den發表於2015-03-29

故事的開始
“一次編碼,處處執行” 永遠是程式設計師們的理想鄉。二十年前 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)

相關文章