如何開(kāi)發(fā)一個(gè)移動(dòng)跨平臺(tái)庫(kù)
“一次編寫,到處運(yùn)行” 是為闡明 Java 的跨平臺(tái)功能而編寫的著名標(biāo)語(yǔ)。程序員都希望他們的代碼能到處運(yùn)行。盡管如此,要跟上 CPU 體系結(jié)構(gòu)的變化是如此困難,新的程序語(yǔ)言日日涌現(xiàn),框架來(lái)了又去,并且如果你想跟操作系統(tǒng)打交道,代碼的重用性就不必再提。
但是也許,如果我們只把范圍限制在移動(dòng)設(shè)備,可能還有機(jī)會(huì)!近年來(lái)移動(dòng)化的趨勢(shì)日趨明顯,所以開(kāi)發(fā)一個(gè)移動(dòng)跨平臺(tái)的一定會(huì)對(duì)一些開(kāi)發(fā)者有所幫助。
如果我們要進(jìn)一步縮小,到iOS和Android上,它們目前的市場(chǎng)占有率為93.9%。 這給我們鎖定了7個(gè)目標(biāo)CPU架構(gòu)/ABI(ARMv7、armv7s、IOS的arm64、armeabi、armeabi-V7A、x86和針對(duì) Android的MIPS),以及兩種編程語(yǔ)言(iOS的Objective-C和Android的Java)。至于框架和操作系統(tǒng),支持最新的兩個(gè) iOS版本應(yīng)該就足夠了,因?yàn)樾碌膇OS版本有著較高的使用率,但當(dāng)涉及到Android時(shí),要有一個(gè)不錯(cuò)覆蓋率的話我們需要支持Froyo(或者Gingerbread)以后的所有版本。正如你所看到的,這不是件容易的事,但我們需要這樣做。
我們想要做的總結(jié)在下圖中;在有一些適配特定平臺(tái)膠水代碼的情況下在兩個(gè)平臺(tái)間共享一個(gè)庫(kù)。由于Skyscanner嚴(yán)重依賴在于互聯(lián)網(wǎng),一些網(wǎng)絡(luò)函數(shù)是不可少的。
通常情況下,在iOS中,一個(gè)庫(kù)可以通過(guò)Objective-C源代碼或預(yù)編譯的靜態(tài)二進(jìn)制庫(kù) 中導(dǎo)入,并要有相應(yīng)的頭文件。在Android中,除了Java源代碼,一個(gè)庫(kù)也可以通過(guò).class(字節(jié)碼)文件和靜態(tài)/共享二進(jìn)制庫(kù)導(dǎo)入。然而,由 于這些選擇是限制性的,這里的研究將更進(jìn)一步,去探索在Android和iOS中導(dǎo)入代碼的替代方法。
那么,我們?cè)撊绾伍_(kāi)始呢?有什么選項(xiàng)呢?是否有簡(jiǎn)化的工具呢?
選項(xiàng)1-移動(dòng)跨平臺(tái)開(kāi)發(fā)工具
如果你是一名移動(dòng)開(kāi)發(fā)者,你一定聽(tīng)說(shuō)過(guò)大量的移動(dòng)跨平臺(tái)開(kāi)發(fā)工具,比如PhoneGap,Appcelerator Titanium 和Xamarin。它們中的一些工具允許我們開(kāi)發(fā)類庫(kù),對(duì)嗎?
具備這一功能的工具通常存在的主要問(wèn)題如下:
1輸出到終端產(chǎn)品(.app/ .ipa或者 .apk)而不是類庫(kù)中
2嵌入到運(yùn)行時(shí)的環(huán)境中運(yùn)行跨平臺(tái)的代碼,這些代碼與在環(huán)境中的本地代碼交互非常的困難.
-
網(wǎng)頁(yè)視圖工具-這些工具使用視圖做為運(yùn)行時(shí)環(huán)境,用JavaScript/HTML5來(lái)編寫代碼.如果它們與運(yùn)行在網(wǎng)頁(yè)視圖的代碼交互困難時(shí),它們將會(huì)立即失效.(然而,它總是試圖這樣,你隨后將會(huì)發(fā)現(xiàn).)這樣的工具包括PhoneGap, RhoMobile,Secha Touch,appMobi, Telerik.
-
Adobe AIR--這的運(yùn)行時(shí)環(huán)境中Adobe集成運(yùn)行時(shí)環(huán)境.它的失效取決與與AIR中代碼交互的困難程度.
-
Xamarin--它只能輸出到中間的Xamarin 庫(kù)中,而不是本地庫(kù)中.
-
Appcelerator Titanium--創(chuàng)建的本地類庫(kù)并不是官方支持的,但是它可能是可以工作的,如果我們寫Titanium的擴(kuò)展,這些擴(kuò)展允許與本地代碼交互.太多的麻煩,存在問(wèn)題的那些結(jié)果,同時(shí)不確保在下一個(gè)版本Titanium的升級(jí)中這些功能是否保留.
-
Corona--Corona的員工聲稱它已支持Android,同時(shí)它未來(lái)將支持iOS.
-
MoSync--與Corona類似
-
Kony--不支持.
-
Trigger.io--不支持
-
OpenFL--不支持
-
DragonRad--已過(guò)時(shí),似乎不支持
因此,失敗了,沒(méi)什么真正可行的:(
但是等一下,難道C/C++的代碼不能訪問(wèn)iOS和Android嗎?
選項(xiàng)2--C++
使用C++來(lái)開(kāi)發(fā)類庫(kù)是可行的兩個(gè)解決方案之一。
在Android平臺(tái),本地開(kāi)發(fā)套件(NDK)和Java本地套件框架(JNI)允許Java與C/C++的代碼運(yùn)行和交互.NDK的負(fù)責(zé)為 Android的每個(gè)目標(biāo)對(duì)象(armeabi,armeabi-v7a,x86和mips)編譯C++代碼; 而JNI允許這兩種語(yǔ)言溝通交流.使用JNI相當(dāng)?shù)膯?程序員必須遵守命名規(guī)則,而且需要用Java和c++兩層包裝.一方面,通過(guò)用Java語(yǔ)言暴露 所有的c++類和方法(包括了本地關(guān)鍵字),Java封裝提供了一個(gè)用于c++類庫(kù)的Java的接口.另一方面,c++封裝提供了Java封狀與c++類 庫(kù)之間的橋梁,這兩種語(yǔ)言的對(duì)象可以相互轉(zhuǎn)化。
在iOS中,事情就變得簡(jiǎn)單多了。在此系統(tǒng)中,沒(méi)有命名規(guī)則,只需要采用 “Objective-C++”進(jìn)行額外一層的封裝就可以。“Objective-C++”是一種允許變量在單一的源文件中既使用“Objective- C”代碼,也可以使用“C++”代碼的語(yǔ)言。所以,所有的對(duì)象翻譯都只發(fā)生在這個(gè)單一封裝層中。你可以查看略微修改后的Android/iOS應(yīng)用的流程 圖如下:
引入第三方庫(kù)也是不常規(guī)的,因?yàn)槌绦騿T不能直接訪問(wèn)JRE/Android以及Cocoa Touch框架。在這種情況下,第三方庫(kù)可以通過(guò)兩種方式引入,源代碼或預(yù)編譯的二進(jìn)制文件(或者找到它們,或者編譯它們)。其中的一個(gè)特例是執(zhí)行網(wǎng)絡(luò)操 作(HTTP請(qǐng)求),在標(biāo)準(zhǔn)模板庫(kù)(STL)中它是不被支持的,所以我們整合了libcurl到跨平臺(tái)庫(kù)中。libcurl不能以源代碼引入,只能作為一 個(gè)可執(zhí)行的配置腳本。幸運(yùn)的是能夠找到為iOS預(yù)編譯的二進(jìn)制文件。在Android中,我們使用NDK工具鏈/編譯器為每個(gè)Android目標(biāo)系統(tǒng)編譯 libcurl。為7個(gè)目標(biāo)架構(gòu)(3適用于iOS,4為Android)編譯庫(kù)是很費(fèi)時(shí)的,但這個(gè)過(guò)程的一部分可以用腳本實(shí)現(xiàn)自動(dòng)化。
這個(gè)措施相當(dāng)奏效,C++是種流行的語(yǔ)言,它有一個(gè)龐大數(shù)量的可用的第三方庫(kù),并且所有使用的 工具(Android的NDK、JNI、Objective-C++)都有官方的解決方案,由谷歌和蘋果的支持。這個(gè)措施的唯一的缺點(diǎn)是在Android 上,如果我們想保持Java包裝對(duì)象對(duì)C++對(duì)象的引用,我們必須在Java對(duì)象釋放前手動(dòng)回收C++對(duì)象(通常叫刪除C++對(duì)象)。然而,如果沒(méi)有理由 保留C++對(duì)象的話,它們可以在被復(fù)制成Java中對(duì)應(yīng)部分后立即銷毀。
選項(xiàng) 3 - 代碼移植
另一個(gè)考慮過(guò)的選擇是,只維護(hù)一個(gè)代碼庫(kù),然后用適當(dāng)?shù)墓ぞ甙汛a翻譯為平臺(tái)對(duì)應(yīng)的語(yǔ)言。這種選擇也有它的缺陷:
-
生成代碼效率不會(huì)像原生開(kāi)發(fā)者寫的那樣高。
-
翻譯過(guò)程很容易引入Bug,而且必須手動(dòng)修復(fù)。
-
導(dǎo)入的二進(jìn)制文件很難被翻譯,因?yàn)榇蠖鄶?shù)的工具只能翻譯源代碼。
以下是幾種移動(dòng)平臺(tái)代碼移植工具。遺憾的是,沒(méi)有一種能滿足需求:
-
J2ObjC - Google 開(kāi)發(fā)的工具,用于翻譯 Java 代碼為 Objective-C 。看起來(lái)質(zhì)量比較高(與以下其他相比)。目前為止,它能把部分 Java 類翻譯為 Objective-C ,但開(kāi)發(fā)還沒(méi)有完成。不幸的是,它目前翻譯不了 Java 的 HTTP 請(qǐng)求,但是如果我們?yōu)槊總€(gè)平臺(tái)單獨(dú)實(shí)現(xiàn)這部分功能,也有實(shí)現(xiàn)的可能。這個(gè)項(xiàng)目從2012年9月建立至今。
-
Hyperloop - 將 JavaScript 翻譯為平臺(tái)原生代碼的工具。目前為止,它只支持 iOS ,而且并不穩(wěn)定,但他們的計(jì)劃是擴(kuò)展到所有流行的平臺(tái)。這個(gè)項(xiàng)目從2013年8月建立至今。
-
ObjC2J - 將 Objective-C 翻譯為 Java 的工具。這本來(lái)也是個(gè)很好的思路,但不幸的是,它還不夠成熟,含有很多bug,經(jīng)常輸出不能編譯的代碼。
-
XMLVM - 將 JVM 字節(jié)碼交叉編譯為 Objective-C 的工具。這個(gè)工具不僅不夠完善,用起來(lái)很復(fù)雜,并且需要下載/導(dǎo)入很多l(xiāng)egacy jar。
-
Apportable - 將 iOS 應(yīng)用轉(zhuǎn)化為 Android 應(yīng)用的工具。不幸的是,它達(dá)不到我們的要求,因?yàn)樗荒芊g整個(gè)應(yīng)用,無(wú)法翻譯庫(kù),而且直接輸出 .apk(Android 應(yīng)用安裝包)文件。
-
Avian - 輕量級(jí)的 Java 虛擬機(jī),可以嵌入 iOS app bundle 并運(yùn)行 Java 代碼。這個(gè)方案滿足不了需求,因?yàn)橄胍?iOS 上跑的 UI 代碼與虛擬機(jī)中跑的 Java 庫(kù)代碼交互非常困難。
-
in the box - 在 iOS 上運(yùn)行的移植 Dalvik 虛擬機(jī)和 Android Gingerbread (2.3) API。這個(gè)選項(xiàng)被否決了,因?yàn)檫@個(gè)項(xiàng)目已經(jīng)失效。
選項(xiàng)4 - WebView中的JavaScript
JavaScript是近幾年普及很快的語(yǔ)言,其初衷是作為客戶端的腳本語(yǔ)言,但是現(xiàn)在也用于服務(wù)器端應(yīng)用程序(node.js),并成為了上述的移動(dòng)跨平臺(tái)工具的一部分。它可能成為解決我們問(wèn)題的跨平臺(tái)語(yǔ)言嗎?
所有的移動(dòng)跨平臺(tái)都能在web-browser視圖里執(zhí)行JacaScript腳本 (WebViews),并且WebView的API通常都呈現(xiàn)在開(kāi)發(fā)者眼前。
我們?cè)贘avaScript中需要的最少功能如下:
-
執(zhí)行函數(shù)
-
調(diào)用腳本
-
計(jì)算全局變量和返回的結(jié)果
-
執(zhí)行回調(diào) (到本地代碼)
我們來(lái)單獨(dú)地探討各個(gè)平臺(tái)。
在Android中,WebView能執(zhí)行腳本串。回調(diào)到Java代碼是JavaScript實(shí)現(xiàn)的,它注解(用 @JavascriptInterface)可以調(diào)用的Java類中的確定方法,并添加這些類的實(shí)例到WebView的JavaScript全局作用域的 引用(用addJavascriptInterface()方法)。然而計(jì)算變量或者函數(shù)調(diào)用,并不是這么簡(jiǎn)單,因?yàn)闆](méi)有一種像腳本一樣直接計(jì)算的方法。 應(yīng)對(duì)這個(gè)問(wèn)題的唯一措施是向JavaScript傳遞一個(gè)回調(diào)函數(shù),這樣當(dāng)結(jié)果計(jì)算出來(lái)之后,回調(diào)函數(shù)被調(diào)用,傳遞結(jié)果到Java的方法作為參數(shù)。詳見(jiàn)這里
在iOS中,UIWebView能執(zhí)行腳本串。與Android不同的是,IOS中可以計(jì)算全 局變量和函數(shù)調(diào)用(用stringByEvaluatingJavaScriptFromString:),,但是要作為字符串返回,因此當(dāng)結(jié)果不是字符 串的時(shí)候要做一些適當(dāng)?shù)霓D(zhuǎn)換。然而,回調(diào)函數(shù)卻不像Android中的那樣簡(jiǎn)單,這是因?yàn)樵赨IWebView中沒(méi)有這種機(jī)制。從JavaScript中 調(diào)用Objective-C的唯一應(yīng)對(duì)方案,是試圖在JavaScript中打開(kāi)一個(gè)帶有定制協(xié)議的URL(例如skycallback://) ,并在Objective-C中捕捉這一事件,然后解析URL,看協(xié)議中是否含有回調(diào)協(xié)議的名稱,或者解析URL的資源路徑的字符串值,或者計(jì)算存放結(jié)果 的全局變量。詳見(jiàn)這里answer.
你可以看到,JavaScript和本地代碼之間的交互是十分困難的,并因平臺(tái)而已,而且當(dāng)代碼量的增長(zhǎng),這種交互很容易導(dǎo)致bug,并不可避免地變得難以維護(hù)。因此,這個(gè)選項(xiàng)被拋棄掉。
選項(xiàng)5 - JS引擎中的Javascript
讓JavaScript運(yùn)行在一個(gè)獨(dú)立的JavaScript引擎中也可以工作。
和Web視圖的方式相比,Javascript直接與Js引擎交互更為直接。但不幸的是,純凈的JS引擎缺少網(wǎng)絡(luò)功能。JS中處理Http請(qǐng)求的 XMLHttpRequest對(duì)象無(wú)效,原因是它是web瀏覽器的一部分而并非嚴(yán)格的JavaScript規(guī)范。因此,通過(guò)代理特定平臺(tái)(膠水)代碼的網(wǎng) 絡(luò)功能,一個(gè)與眾不同的架構(gòu)應(yīng)運(yùn)而生。雖然這使得有些事情變得錯(cuò)綜復(fù)雜,但是我們特別感興趣的是可以開(kāi)發(fā)跨平臺(tái)的JavaScript庫(kù)。下面是它的工作 原理:
在 iOS 中,JavaScriptCore 引擎通過(guò)極佳的 JavaScriptCore 框架被使用。這個(gè)框架在 iOS 7 中被引入,并且它在幾秒鐘之內(nèi)就可以很容易的可以集成到應(yīng)用中,就如你處理任何 Cocoa Touch framework 一樣。它的 API 非常簡(jiǎn)單,所需的綁定代碼也很簡(jiǎn)潔。
在 Android 中,事情還是有些復(fù)雜,因?yàn)闆](méi)有 JavaScript 引擎,所以我們必須手工嵌入一個(gè)。兩個(gè) JavaScript 引擎都可以被嵌入成功,Rhino 和 V8。Rhino 用 Java 編寫,所以它很容易嵌入,并且它僅僅增加了 2.6MB 的應(yīng)用程序大小。它由 Mozilla 基金會(huì)開(kāi)發(fā),但它的開(kāi)發(fā)現(xiàn)在有一段時(shí)間不活躍了。 V8 嵌入難度要大些,它用 C++ 編寫。因此,必須使用 Android NDK 和 JNI 來(lái)供 Java 與其交互,又增加了一個(gè)轉(zhuǎn)換層(Java<->C++<->JavaScript,而非 Java<->JavaScript)。此外,應(yīng)用程序大小增加了 7.1MB,這對(duì)于一些應(yīng)用程序并不是可以忽略的。不管怎樣,它的開(kāi)發(fā)非常活躍。
跨平臺(tái)庫(kù)以Http請(qǐng)求處理為存根由JavaScript開(kāi)發(fā)。這個(gè)存根一開(kāi)始工作為一個(gè)占位符,而在庫(kù)載入到JavaScript引擎中后會(huì)被重新寫入。它被一個(gè)調(diào)用實(shí)現(xiàn)這個(gè)請(qǐng)求的本地方法(特定平臺(tái))的方法替換。
“JavaScript引擎中的JavaScript"解決方案的全部說(shuō)明將出現(xiàn)在這個(gè)系列文章的第三部分,這些文章將在接下來(lái)的幾周內(nèi)發(fā)布。
結(jié)論
雖然研究了很多工具和技術(shù),但是其中只有兩個(gè)可以工作。在一方面,C + +的解決方案是一種廣泛使用的,可靠的,靈活的解決方案,但在Java中手動(dòng)垃圾收集(提出了一種解決方法的)的一個(gè)顯著的缺點(diǎn)。另一方 面,JavaScript的解決方案更容易實(shí)現(xiàn),但在復(fù)雜的體系結(jié)構(gòu)缺少功能,并且是依賴于并非積極開(kāi)發(fā)中的Rhino,或在V8這對(duì)應(yīng)用程序大小有顯著 影響。如果您使用這些方法中的一種,請(qǐng)對(duì)這些缺點(diǎn)統(tǒng)籌考慮,謹(jǐn)慎行事。
一些項(xiàng)目看上去很有前途,將來(lái)值得重復(fù)查看:
-
Corona
-
MoSync
-
J2ObjC
-
Appcelerator Hyperloop
-
Nashorn (Oracle用Java重寫的Javascript引擎)