看Dropbox如何使用C++進行iOS和Android跨平臺開發

http://www.cocoachina.com/applenews/devnews/2014/0606/8708.html

 
在數週前的UIKonf 2014中,令我印象最爲深刻的就是Dropbox關於如何使用C++來開發iOS和Android非界面代碼的演講。(另外,UIKonf 2014是一個很棒的活動,明年你絕不能錯過它)
 
接下來我要說的主要是總結一下兩個演講的筆記,一是Mailbox的開發者Steven Kabbes講的如何將Mailbox移植到Android平臺,另外一個是Stephen PolettoTina Wen演講的關於在iOS和Android同步發行Carousel首個版本的故事。有些地方我可能理解的不對,如果有什麼錯誤請Dropbox團隊原諒我。
 
動機
"面對現實吧,Android不會離開",Stephen Polletto面對一屋子的iOS開發者如是說。然而,對好幾個平臺都進行native開發是既浪費時間又昂貴的選擇,另外,對同一個問題還要針對不同平臺作出不同的實現。因此同一個問題也會出錯多次,也需要修改多次。在一個平臺上出現的問題,另一個平臺上一會出現,卻有可能不被發現。本來同一個應用在不同平臺要有相同的表現,但是由於實現方法的不同,因此它們的行爲可能會有很大的不同。另外,同時在不同平臺上推出新的特性也很困難。
 
當開始開發Android上的Mailbox應用時,他們決定將大部分非界面代碼用C++而非Java來實現,這樣他們就可以在iOS和Android之間共享這部分C++代碼。當時iOS應用使用了Core Data,因此將這部分Core Data代碼用C++庫來實現也成了工作的一部分。兩個平臺對C++的支持都不錯,同時團隊成員相對Java更喜歡用C++。
 
因爲在iOS和Android平臺同時推出產品很重要,所有Carousel團隊也選擇了C++。
 
工具鏈
iOS原生支持C++,通過Objective-C++可以很簡單的混用Objective-C和C++。
 
在Android平臺,可以通過NDK來調用C++,然而據說不是很好用,Dropbox發現谷歌的構建系統gyp還是蠻好用的。同時你不得不接受:Java原生接口很不好用。但是上面的問題都不應該成爲你的絆腳石,Steven希望谷歌或社區能在以後建立更好的工具鏈。
 
在Objective-C社區中,C++的名聲並不好,然而Steven指出C++11已經有了很多改進,如果你近期沒有使用它,那你絕對有必要再看一看。事實上,Objective-C的很多特性(比如blocks和ARC)都在C++11中有了類似的實現(lambdas智能指針)。當然,C++依然是一種很複雜的語言,對很多團隊來說需要時間學習。
 
SQLite作爲一種收到廣泛支持的數據存儲,似乎是一種必然的選擇。儘管標準的SQLite C API有點笨拙,但存在很多C++庫將其封裝成一個面向對象的接口,就像Objective-C中FMDB那樣。
 
架構
客戶端-服務器設計
用戶界面都是使用相應平臺原生的API寫的(iOS上的Objective-C/UIKit,Android上的Java),而大多數的"模型層"使用共享的C++庫實現。與直接調用模型層不同,Steven將其設計成一個客戶端-服務器的結構,服務器端(即C++共享庫)一直在線且延遲爲零。將UI代碼和共享庫庫看成兩個分開的實體有助於在兩者之間設計清晰的接口和兩者的解耦。
 
這種架構同時預定義了數據在UI和C++層兩者之間傳遞的方式,它們不會訪問相同的數據對象,而是通過消息機制在其間傳遞數據。
 
想要清楚地劃分這兩部分並不容易,你必須準備好犯一些錯誤。有的表面上看上去應該是在共享層的,卻需要依賴平臺特定代碼來實現。例如網絡(iOS上使用NSURLSession後臺下載)、應用後臺行爲、文件系統訪問(iCloud默認備份所有文件)等。這些地方,他們在共享庫做了抽象,又使用平臺原生的方式在客戶端實現它。
 
對於其他的平臺相關API,他們必須選擇其他的方式。其中一個例子就是用於存儲首選項和設置項(storing preferences and settings in Cocoa)的NSUserDefaults系統,由於不能再共享庫中使用,Dropbox使用了谷歌的LevelDB來替代它。
 
重寫Core Data
C++共享庫的核心部分是一個查詢存儲框架,差不多和Core Data的作用類似。以我的理解來看,Mailbox團隊並不想完全重寫Core Data:首先,他們只需要一部分功能,其次,他們發現Core Data的一部分API並不能很好的適應Android。於是,他們基於SQLite開發了自己的框架。
 
Steven給出了Mailbox中這個架構的大致輪廓:
1.查詢對象基本上代表了一個SQL語句的參數,執行一個查詢返回一個結果集。
2.結果集會被轉化成DataView,在形式上代表數據的view models可以被用戶界面使用(比如在table view中)。正如你看到的,View模型也是C++共享庫的一部分,iOS和Android使用相同的View模型。Dataview具有不可變性。
3.當查詢結果發生變化時,會返回一個新的DataView。該框架會計算出一個ChangeSetthat來描述從舊形式到新DataView的變化。前端應用可以使用theseChangeSets來更新界面而不是重載所有數據,同時這也有利於使用動畫來體現視圖的變化,就像Core Data中NS?Managed?Object?Context?Objects?Did?Change?Notification 或 NS?Fetched?Results?Controller那樣。
4.當界面想改變數據時,它會給服務器發一個消息,服務器會處理這個消息,然後客戶端會通知新的ChangeSet的DataView,從而界面可以相應的作出動作。
 
對於每個平臺,團隊使用原生語言編寫了簡單封裝類以便從UI代碼中剝離C++類。在Objective-C中,藉助Objective-C++很容易實現。Objective-C封裝類中的大部分方法基本上都是直接傳遞參數,根據需要改成本地數據類型就行了。對於客戶端來說,它並不關心服務器是如何實現的。
 
圖像緩存
作爲一個照片應用,Carousel應用在共享庫中實現了圖像緩存。用戶界面層將大小和位置信息傳遞給C++層,C++層據此可以選擇相應的圖像。這種算法可以在iOS和Android之間共享,因爲應用使用基本相同的視圖佈局。
 
併發性
客戶端-服務器的架構簡化了線程模型。由於UI和C++層不共享數據,他們可以獨立地維護自己的線程。主線程控制UI,C++層基本上只是一個在後臺運行的單線程。
 
這種設計使得鎖的使用變少,從而簡化了架構。同時由於多數設備都具有兩個CPU核心,使用一個來運行主線程,另外一個來運行存儲線程是很自然的。以後多核設備普及之後,設計或許會繼續改變,但是設計初期保持簡單是正確的決定。
 
存在的挑戰
顯然,Mailbox和carousel團隊的做法不一定適合於所有的應用。而且,魔鬼總是存在於細節中。當你真正去實現類似於這樣的架構時一定會遇到一些以前沒有考慮到的問題,你將不得不調整自己的方法和流程。
 
寫這篇文章的目的,在於拋磚引玉,社區應該多一些關於此話題的討論。
 
示例代碼
非常感謝Steven Kabbes寫了這個文章的初稿,Steven還在git上發佈了他演講的筆記,同時慷慨的設立了一個git源來分享如何在iOS、Android和OS X上實現這個過程的代碼。這會對我們很有幫助,至少幫你設置好了工具鏈。你們可以在GitHub上參與相關的討論
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章