iOS混合應(yīng)用開發(fā)入門
介紹
上周(譯者:原文成于2012.07.06),紐約時(shí)報(bào)透露說Facebook一直在致力于對其iOS應(yīng)用進(jìn)行重大升級。這個(gè)事實(shí)本身沒有什么新聞價(jià)值。Facebook當(dāng)然一直在致力于對其iOS應(yīng)用進(jìn)行重大升級。但是,這次的升級相當(dāng)有新聞價(jià)值。就如何構(gòu)建和維護(hù)越來越多的移動應(yīng)用套件而言,F(xiàn)acebook正在計(jì)劃一個(gè)意義重大的航線修正(譯者:技術(shù)轉(zhuǎn)型)。
到目前為止,F(xiàn)acebook公開的移動策略是為了避免「重復(fù)寫四次相同代碼」(之后會更多),而發(fā)布混合應(yīng)用 (hybrid apps)到各主流 平臺。理論上講,這個(gè)想法很完美:當(dāng)你可以為相同的功能只維護(hù)一個(gè)代碼庫的時(shí)候,誰愿意為此維護(hù)多個(gè)呢?但是,實(shí)際的情況是,F(xiàn)acebook的應(yīng)用在任 何平臺上都沒有「原生」的感覺,同時(shí)也被嚴(yán)重的性能問題所折磨,并普遍的被其用戶所辱罵。
這篇文章將會對混合應(yīng)用的主題進(jìn)行更深入的探討。我們將會以講解混合應(yīng)用的構(gòu)成做為開始。然后,我們將會看下混合應(yīng)用能帶來的優(yōu)勢,并且也會提供更 多消極方面的細(xì)節(jié)。我們將會檢驗(yàn)一些可以使混合應(yīng)用在感覺上更加像一個(gè)原生應(yīng)用的選項(xiàng),然后給你提供一些用來優(yōu)化性能,外觀以及體驗(yàn)的建議。最后,我們會把注意力轉(zhuǎn)回到Facebook上,為了理解他們是如何成為今天這個(gè)樣子的,我們將會更細(xì)致的查看他們iOS應(yīng)用的歷史。
從個(gè)人的角度來說,我不會為iOS構(gòu)建一個(gè)混合應(yīng)用,除非我必須這么做不可。我不認(rèn)為混合應(yīng)用在iOS上表現(xiàn)良好: 它們比較慢,也比較笨拙,并且從 來沒有原生應(yīng)用那樣的外觀和體驗(yàn)。但是,在合適的場景下,它們所能提供的優(yōu)勢能抵掉它們的劣勢。為了學(xué)習(xí)關(guān)于混合應(yīng)用的所有內(nèi)容,什么是能做的,什么是不 能做的,并且它們是否滿足你的需求,請繼續(xù)往下讀。
什么是混合應(yīng)用?
通常來說,一個(gè)iPhone應(yīng)用是利用Cocoa Touch框架以純Objective C來構(gòu)建的。你可能會有一個(gè)UITabBarController,上面安放了一些視圖控制器(view controllers)。這些視圖控制器可能是UITableViewController的子類,或者也有可能是UIViewController, 其UI是使用XIB所定義或者在代碼中定義。有時(shí)候,一個(gè)視圖控制器上可能會安放一個(gè)UIWebView控件,用來在一個(gè)應(yīng)用的內(nèi)部展示web內(nèi)容或長文本內(nèi)容。
混合應(yīng)用用HTML,CSS和Javascript而不是Objective-C來實(shí)現(xiàn)部分或者所有的客戶端代碼。一個(gè)特定應(yīng)用的屏幕實(shí)際上可能包含一個(gè)UIWebView,用它來渲染服務(wù)端返回的標(biāo)記語言(譯者:即HTML)并且 解釋服務(wù)端返回的代碼(譯者:JavaScript)而不是Objective C。
一些應(yīng)用,比如像Instapaper和Pocket,是高度依賴web視圖來實(shí)現(xiàn)其關(guān)鍵的應(yīng)用功能的。 Instapaper和Pocket對 web視圖的使用采取了一個(gè)務(wù)實(shí)的方案:兩個(gè)應(yīng)用的主函數(shù)都是展示重新格式化的web內(nèi)容,所以只用web視圖來展示HTML是有道理的。
就像在下面的類型列表中即將看到的那樣,F(xiàn)acebook盡可能的在它們的iOS應(yīng)用中使用HTML,其本質(zhì)上是把http://touch.facebook.com的一個(gè)版本填充進(jìn)了一個(gè)Objective C的殼里邊。它們在一些限定的應(yīng)用特性中使用Objective C,比如登錄窗口和它們曾經(jīng)獨(dú)一無二的滑出菜單界面。
最后,還存在可以添加到iPhone主屏的移動站點(diǎn)。這些站點(diǎn)是100%用HTML構(gòu)建的,并且特定的運(yùn)行在移動版Safari中。
應(yīng)用的類型應(yīng)用類型
類型
|
例子
|
完全原生
|
Path 2.0和其他完全利用Cocoa Touch框架用Objective C所實(shí)現(xiàn)的應(yīng)用。
|
一些web視圖
|
Instapaper和Pocket都是使用web視圖來實(shí)現(xiàn)關(guān)鍵的應(yīng)用功能,但是其他的功能是用Objective C來實(shí)現(xiàn)的。
|
最小化Objective C
|
比如Facebook。盡可能的使用HTML/JS/CSS來實(shí)現(xiàn)的應(yīng)用,并且只有很少的組件是原生的。
|
純HTML
|
就像它描述的那樣
|
混合應(yīng)用的優(yōu)勢
為什么你想要創(chuàng)建一個(gè)混合應(yīng)用?這兒有一些原因,比如產(chǎn)品開發(fā)和更新的速度,輕松的進(jìn)行A/B測試,以及通過讓前端web開發(fā)人員加入到iOS開發(fā)中,可以增加可用的開發(fā)人員數(shù)量。
開發(fā)速度
盡管開發(fā)一個(gè)外觀完美,行為原生的移動web體驗(yàn)是不太可能一夜完成 的,但是在較短的時(shí)間內(nèi)開發(fā)一個(gè)合宜的體驗(yàn)相對來說是比較簡單的。開發(fā)一個(gè)得體的web體驗(yàn),并且把它加載到一個(gè)UIWebView中肯定比開發(fā)一個(gè) JSON API,并用Objective C來寫一些前端代碼要容易。由于這個(gè)原因,一些開發(fā)者可能比較喜歡用HTML來交付那些用來檢驗(yàn)想法的功能,然后接下來當(dāng)功能的價(jià)值被證實(shí)了的時(shí)候再把功 能的代碼替換成原生代碼。
投入市場的時(shí)間是公司之間的關(guān)鍵區(qū)分點(diǎn),發(fā)布日期延遲幾周或者甚至幾個(gè)月對于資源受限(limited runway)的創(chuàng)業(yè)公司來說這可能意味著生與死的區(qū)別。
發(fā)布更新的速度
通過HTML來提供產(chǎn)品功能的另一個(gè)優(yōu)勢是你可以在不向蘋果發(fā)起 應(yīng)用更新請求的情況下去更新你應(yīng)用的功能集合,并且不需要等待蘋果幾周的審批過程。 快速的進(jìn)行改變,修復(fù)問題以及能擴(kuò)展你應(yīng)用的功能,這可以為你提供一個(gè)用來超越對手的重要競爭優(yōu)勢,你的競爭對手由于每個(gè)小的改動都需要經(jīng)過蘋果的審批過 程,所以它們的靈活性就比較低。
早上想要的新功能,這天結(jié)束的時(shí)候就可以發(fā)布,并且晚上就可以觀測用戶的使用情況,以便第二天早上就可以對其進(jìn)行優(yōu)化,調(diào)整或者刪除,這是對體驗(yàn)進(jìn)行打磨非常強(qiáng)大的方法,而體驗(yàn)恰恰是你的應(yīng)用可以在市場上取得成功的必需條件。
A/B測試
與開發(fā)的速度和發(fā)布更新的速度都相關(guān)的是快速的對用戶進(jìn)行A/B測試或者分組測試的 能力。比如,你可能想測試一下你應(yīng)用上的新feed功能,在其自身包含一個(gè)Like按鈕的情況下和只在feed內(nèi)容詳情頁面包含這個(gè)按鈕的情況,用戶使用 率是否有所提高。用HTML來實(shí)現(xiàn)feed功能,你可以很容易的對這個(gè)更改進(jìn)行區(qū)分測試,為了從統(tǒng)計(jì)數(shù)據(jù)上來看這個(gè)重要的用戶行為是否發(fā)生,可以只在一組 用戶中展示這個(gè)按鈕。
在原生代碼中執(zhí)行A/B測試不是不可能的,但是準(zhǔn)備測試用例會更加具有挑戰(zhàn)性,并且對比一個(gè)混合應(yīng)用來說,從測試用例中返回對應(yīng)的新數(shù)據(jù),這個(gè)過程將會顯著地耗費(fèi)更多的時(shí)間。如果你對如何在Objective C中實(shí)現(xiàn)A/B測試好奇,Little Big Thinkers上有一篇好文章展示了它們?nèi)绾卧趇Phone上進(jìn)行A/B測試(譯者:翻墻)。
App開發(fā)的民主方式
如果你曾經(jīng)嘗試過雇傭一個(gè)iOS開發(fā)者(或者如果你是一個(gè) iOS承包商),你知道對靠譜iOS開發(fā)者的需求與可雇傭的人來說早就已經(jīng)是供不應(yīng)求?;? 合應(yīng)用可以大大的增加你所需要的靠譜開發(fā)者數(shù)量,因?yàn)槟憧梢宰屇闳魏蔚那岸碎_發(fā)人員轉(zhuǎn)做你iOS應(yīng)用中的功能開發(fā),假設(shè)他們已經(jīng)熟悉 Javascript,HTML,CSS和你服務(wù)器上使用的任何后端語言。
混合應(yīng)用的劣勢
當(dāng)然,如果混合應(yīng)用沒有缺點(diǎn),沒人想開發(fā)原生應(yīng)用?,F(xiàn)實(shí)中顯然不是這種情況,并且這里有幾個(gè)原因解釋了為什么會這樣。概括來說,混合應(yīng)用較慢,較臃腫,外觀看起來沒有原生的感覺,并且,最重要的是,沒有原生的體驗(yàn)。
Nitro的缺失
Nitro是移動Safari的Javascript引擎,速度太TMD快了。不幸的是,由于安全的關(guān)系,UIWebViews無法享受到這個(gè)速度提升帶來的優(yōu)勢:
Nitro 比WebKit之前的JavaScript引擎性能有所提升的最大原因是其采用了JIT-”Just-In-Time” 編譯… JIT需要可以把RAM中的內(nèi)存頁標(biāo)記為可執(zhí)行狀態(tài)的能力,但是,iOS,出于安全的考量,不允許內(nèi)存頁被標(biāo)記為可執(zhí)行狀態(tài)。這是一個(gè)重要并且嚴(yán)格的安全 策略。大部分現(xiàn)代操作系統(tǒng)允許內(nèi)存頁被標(biāo)記為可執(zhí)行狀態(tài)-包括Mac OS X,Windows,和(我相信)Android。iOS 4.3對這個(gè)策略有個(gè)例外,但是這個(gè)例外只限于移動Safari。
實(shí)際上來說,這意味著如果你的混合應(yīng)用使用了Javascript,那么你將會感覺到同樣的UI要比在移動 Safari中要慢,也比以 Objective C實(shí)現(xiàn)的同樣的UI要慢。不幸的是,這兒沒有簡單的方法來解決這個(gè)問題,除了減少或者全部移除UIWebViews中你所使用的Javascript之 外,盡可能的以CSS3動畫(animations)和過渡(transitions)來做為替代(這可以利用GPU加速的優(yōu)勢)。
為了弄清楚,你可以只使用CSS3來完成一些真實(shí)的不可思議的效果,就像剛才Hakim El Hattab在他的stroll.js項(xiàng)目所演示的那樣。但是,總的來說,Nitro的缺失是任何混合iOS應(yīng)用成功的嚴(yán)重障礙,特別是如果想獲得與原生iOS應(yīng)用同樣的外觀和體驗(yàn)。
模仿原生UI的挑戰(zhàn)
除了上面描述的性能問題之外,模仿原生Cocoa Touch用戶界面的挑戰(zhàn)是實(shí)現(xiàn)一個(gè)混合應(yīng)用所面對的另一個(gè)困難。就像之前被指出過無數(shù)次,iPhone應(yīng)用擁有非常不同尋常的外觀和體驗(yàn)。比如,Table cell擁有標(biāo)準(zhǔn)的字體大小,內(nèi)邊距和空白要求,gradient selection高亮,disclosure箭頭,以及許多其他很難被復(fù)制的特性。
一個(gè)替代的方式是放棄對iOS系統(tǒng)控件的復(fù)制,相反為你的應(yīng)用構(gòu)建一個(gè)獨(dú)一無二的UI,就像Facebook的 News Feed和Timeline功能那樣。但是,即使你選擇了這個(gè)方法,為了使你的混合應(yīng)用在感覺上不像一個(gè)web頁面,這兒還有一些WebKit特性需要被 解決。
有時(shí)候事情很容易變糟
預(yù)計(jì)遲早有一天,你混合視圖中的CSS或者 Javascript文件將會加載失敗,這不是沒有理由的??赡苣愕挠脩羰羌~約或者舊金山的 AT&T的客戶,眾所周知他們使用的是不穩(wěn)定的GSM網(wǎng)絡(luò)??赡苌系劢裉靸H僅是沒對你笑而已。不管怎么樣,終有一天你將會給用戶展示一個(gè)像這樣的 UI:
…并且最糟糕的是你對于這種情況幾乎做不了任何事情,除了把你的JS和CSS都混入HTML頁面中。這種情況對于你和你的應(yīng)用都是極為糟糕的,并且它是完全無法控制的。至少對一個(gè)完整的原生應(yīng)用來說,服務(wù)端錯(cuò)誤可以通過一個(gè)彈出的警告來更加優(yōu)雅的進(jìn)行處理。 (截屏來自@timanrebel,社會化滑雪和滑雪板應(yīng)用,Snowciety的創(chuàng)始人。)
如何使你的混合應(yīng)用擁有原生的體驗(yàn)UIWebView的陰影和背景顏色
默認(rèn)情況下,UIWebViews會顯示一個(gè)陰影,以及背景顏色或者一個(gè)位于渲染頁面下面的圖案(取決于iOS版本和硬件)。這個(gè)外觀和感覺明顯是非原生 的,所以你將會需要移除它們兩個(gè)。幸運(yùn)的是,這么做很簡單:為了使視圖變得扁平(flat),所有你需要做的事情就是一個(gè)UIWebView子類里邊的幾 行代碼。check out我的那個(gè)MIT協(xié)議的項(xiàng)目,F(xiàn)latWebView,看看例子里邊是如何做的。
鏈接的高亮顯示
為了幫助用戶知道他們究竟觸碰過哪里,當(dāng)鏈接被觸碰的時(shí)候WebKit會在其周圍高亮的顯示一個(gè)半透明的矩形。
盡管這對于web來說很便捷,但是它在感覺上明顯不是原生的。為了清除這個(gè)行為,你可以在你的web應(yīng)用中包含這個(gè)CSS片段:
- /*http://stackoverflow.com/questions/9157080/wrong-webkit-tap-highlight-color-behavior-when-page-as-web-standalone-app */
- html {
- -webkit-tap-highlight-color: rgba(0,0,0,0);
- -webkit-user-select: none;
- }
觸摸延遲
默認(rèn)情況下,當(dāng) WebKit對web頁面上的被觸摸的鏈接進(jìn)行響應(yīng)的時(shí)候,它會增加一個(gè)大約300毫秒的延遲。這實(shí)際上是一個(gè)特性,不是一個(gè) bug,因?yàn)橐馔庥|碰一個(gè)web頁面上的錯(cuò)誤鏈接是特別常見的,所以擁有一個(gè)細(xì)微的延遲可以給用戶提供一個(gè)修正他們錯(cuò)誤的機(jī)會,對于什么都不做來說這可以 創(chuàng)造更好的體驗(yàn)。無論如何,當(dāng)你正在創(chuàng)建一個(gè)混合應(yīng)用的時(shí)候,你應(yīng)該只使用觸碰目標(biāo)(hit target)尺寸大得可以避免被意外觸碰的UI組件。(你正在使用最小尺寸為40×40px的大尺寸觸碰目標(biāo),對嗎?)
為了確保你應(yīng)用的UI在感覺上和Cocoa Touch的UI擁有一樣的響應(yīng)體驗(yàn),當(dāng)你在創(chuàng)建一個(gè)混合應(yīng)用的時(shí)候你必須禁用WebKit的觸摸延遲的特性。Matteo Spinelli提供了一些方便的Javascript代碼(不是jQuery?。┯脕斫鉀Q這個(gè)「問題」:Remove onclick delay on Webkit for iPhone.
滾動
過去創(chuàng)建一個(gè)混合應(yīng)用最大的挑戰(zhàn)之一就是在一個(gè)UIWebView中復(fù)制原 生的滾動行為。特別是想要在UIWebView中的UIScrollView子類中去復(fù)制滾動速度,慣性以及「橡皮筋」(rubberbanding)視 覺效果,這幾乎是不可能。幸運(yùn)的是,蘋果在iOS 5中為UIWebView增加了一個(gè)scrollView屬性,這使得對上面這些功能的支持是小菜一碟。
你所需要做的就是把你web視圖上的滾動視圖的decelerationRate屬性設(shè)置為UIScrollViewDecelerationRateNormal:
- UIWebView *hybridView = [self somethingThatGetsOurWebView];
- webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
Objective C到Javascript
為了在原生代碼發(fā)生改變的時(shí)候去更新一個(gè)web視圖,你擁有三個(gè)選擇:
- 重新加載UIWebView的內(nèi)容。
- 使用NSURLRequest加載一個(gè)新頁面。
- 在你的UIWebView中使用-stringByEvaluatingJavaScriptFromString:這個(gè)API來執(zhí)行Javascript代碼。
重新加載@UIWebView@中的內(nèi)容-或者加載一個(gè)全新的頁面-這糟透了,因?yàn)檫@將會在一個(gè)無法忽視的時(shí)間內(nèi)展示給你一個(gè)空白,無法操作的頁面。你可以一直顯示一個(gè)loading HUD視圖(比如SVProgressHUD),但是這仍然不是一個(gè)非常好的體驗(yàn)。
在UIWebView中執(zhí)行Javascript代碼來更新視圖是一個(gè)較好的選擇:比如,這給你提供了執(zhí)行部分更新的機(jī)會。不過,它也有它自己的挑戰(zhàn),比如極其糟糕的調(diào)試體驗(yàn),當(dāng)一些功能不能正常工作的時(shí)候你就會感受到了。
Javascript到Objective C
為了在當(dāng)你的web視圖發(fā)生改變 的時(shí)候去更新原生代碼,與iOS打賭(譯者:交互)的最佳方式是實(shí)現(xiàn)UIWebView的委托方法 -webView:shouldStartLoadWithRequest:navigationType:。為了可以從UIWebView中調(diào)用原生平 臺的特性,你可以定義自己的scheme(比如用myappname://代替http://)和自己的URIs(比如用 showImageCapture代替apple.com)。不幸的是,這個(gè)方案即使對于復(fù)雜程度不高的應(yīng)用來說也會退化為一個(gè)龐大的if區(qū)塊。
比如說Facebook可能像這樣來實(shí)現(xiàn)他們的委托方法:
- if ([request.url.scheme isEqual:@"showImagePicker"]) {
- // show a UIImagePickerController
- } else if ([request.url.scheme isEqual:@"sendMessage"]) {
- // show the send message view controller
- } else if ([request.url.scheme isEqual:@"checkIn"]) {
- // show the places checkin view controller
- } else if ([request.url.scheme isEqual:@"postStatus"]) {
- // show the post status view controller
- }
幸運(yùn)的是,這里有一些方法可以簡化它。比如,你可以在某種點(diǎn)對點(diǎn)的調(diào)度系統(tǒng)中使用一個(gè)NSDictionary來對scheme到action進(jìn)行路由。
- - (void)viewDidLoad
- {
- self.dispatch = [NSMutableDictionary dictionary];
- [dispatch setObject:[NSValue valueWithPointer:@selector(showImagePickerForURL:)] forKey:@"showImagePicker"];
- }
- - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
- {
- NSValue *selectorPointer = [self.dispatch objectForKey:request.url.scheme];
- if (selectorPointer)
- {
- [self performSelector:[selectorPointer pointerValue] withObject:request.url];
- return NO;
- }
- else
- {
- return YES;
- }
- }
當(dāng)Facebook的iPhone應(yīng)用首次亮相的時(shí)候,它幾乎是個(gè)完整的原生應(yīng)用,是由Joe Hewitt單槍匹馬寫出來的(據(jù)我所知),他把Three20 iOS框架從應(yīng)用的核心中提取了出來。如果你曾經(jīng)用Three20開發(fā)過應(yīng)用,你知道這個(gè)框架有一點(diǎn)笨拙??赡?,在Joe退出iOS開發(fā)團(tuán)隊(duì)之后的一段時(shí)間,F(xiàn)acebook的領(lǐng)導(dǎo)們判斷出當(dāng)前應(yīng)用的實(shí)現(xiàn)簡直是太難維護(hù)了,是時(shí)候重新考慮方案了。
Dave Fetterman,F(xiàn)acebook平臺的工程經(jīng)理,他在去年F8大會一個(gè)演講中的部分篇幅描述了這個(gè)突變:
由于一些根本原因你需要為四個(gè)不同的平臺開發(fā)應(yīng)用。什么?你想為所有的這些平臺各自開發(fā)?好吧,那你將會像SB一樣開發(fā)四次。然后每個(gè)平臺上都有了所有的特性-群組,交易,新的profile。但是這么做實(shí)在是太遭了。這么說來,我們不得不開發(fā)四次,這意味著開發(fā)的速度變慢了。代碼也變得陳 舊了。這里會存在不能一起工作的不同版本的等價(jià)功能,這對于像Facebook這樣快速發(fā)展的公司來說太糟糕了。
所以,當(dāng)Facebook4.0的iOS版本發(fā)布的時(shí)候, 它體現(xiàn)出了”Write Once, Run Anywhere”的思想。就個(gè)人來講,我必須說,我認(rèn)為在理論上這是一個(gè)偉大的想法。沒人想開發(fā)和維護(hù)相同的功能集合四次,特別是像Facebook這 樣的公司,它擁有驚人的高「用戶-工程師」比例。很不幸,真實(shí)的情況是UIWebView并沒有足夠的能力來處理像Facebook這樣的富應(yīng)用的需求, 用戶的眼睛是雪亮的:
總結(jié)
綜上所述,混合應(yīng)用在iOS上的體驗(yàn)不太好:它們較慢,也較笨拙,可能引起一些無法修復(fù)的錯(cuò) 誤,并且它們在感覺上并不能和原生應(yīng)用相比。但是,它們 所提供一些優(yōu)勢在特殊的境況之下,可能是一個(gè)有價(jià)值的折中方案。個(gè)人來講,我建議你應(yīng)該一直用原生代碼來開發(fā)你整個(gè)iOS應(yīng)用的用戶界面,然后根據(jù)需求所 要求的那樣有選擇的把應(yīng)用中的部分功能改造成混合方式,不管需求是純開發(fā)速度,即刻更新的能力,或者在很短的時(shí)間內(nèi)在用戶身上去測試功能的多個(gè)變化。
最后,無論怎樣,對你的用戶和你的業(yè)務(wù)來說,只有你知道什么才是真正正確的。確保你是因?yàn)檎_的原因而做的選擇,而不是純粹的圖方便。
對于混合應(yīng)用你是怎么看的?什么時(shí)候你覺得利大于弊?你還有其他可以提高混合應(yīng)用體驗(yàn)的竅門和技巧嗎?還需要更多如何從混合開發(fā)轉(zhuǎn)向完全原生開發(fā)的建議?請留言,我們期望聽到你對這個(gè)問題的聲音。