iOS hybrid App 的實(shí)現(xiàn)原理及性能監(jiān)測(cè)
作者董一凡自述:作為一名寫了十年代碼的程序員,目前我最擅長(zhǎng)的領(lǐng)域是移動(dòng)平臺(tái)的客戶端開發(fā),在移動(dòng)領(lǐng)域的開發(fā)時(shí)間超過七年,前前后后涉獵過很多個(gè)平臺(tái)。隨著大部分移動(dòng)平臺(tái)自己走向死亡,現(xiàn)在我也主要專注在了iOS和Android兩大移動(dòng)平臺(tái),偶爾也會(huì)客串下Windows這個(gè)不知道是移動(dòng)還是桌面的平臺(tái)。 十年前,我剛?cè)胄械臅r(shí)候,曾經(jīng)認(rèn)為自己將會(huì)永遠(yuǎn)做一個(gè)C++程序員,于是花了大量時(shí)間在C++上?,F(xiàn)在C++也是我工作所用的主力語言之一,工作之外也會(huì)偶爾寫點(diǎn)什么娛樂一下。 寫了一些年程序后,終于意識(shí)到了之前定位的狹隘,于是開始廣泛的學(xué)習(xí)各種技術(shù),各種各樣的語言也學(xué)了很多,值得慶幸的是,幾年折騰下來,我一直也沒有對(duì)寫代碼這件事感到厭倦,于是我又認(rèn)為自己將會(huì)永遠(yuǎn)把開發(fā)做下去。 現(xiàn)在,我也覺得開發(fā)是一個(gè)可以終身做下去的事業(yè),不過除了事業(yè)我還想追求更多的東西,從這些年的經(jīng)歷來看,其中貫穿始終的就是在不停的學(xué)習(xí),想明白這一點(diǎn)后,我開始除技術(shù)之外更廣領(lǐng)域的學(xué)習(xí),比如日語,畫畫,設(shè)計(jì),鋼琴等等,給自己的定位也變成了在今后作為一名終身學(xué)習(xí)者。
一 iOS hybrid App 簡(jiǎn)單介紹
大家應(yīng)該多少都知道,iOS 設(shè)備上有兩種入口,一是通過 App Strore 下載一個(gè)個(gè)的 App,另一個(gè)是用系統(tǒng)瀏覽器去訪問網(wǎng)頁。前者我們一般稱為原生應(yīng)用,后者就是傳統(tǒng)意義上的網(wǎng)頁。兩者各有特點(diǎn),開發(fā)一個(gè)原生應(yīng)用,一般是使用 Apple 給我們提供的開發(fā)工具和 Cocoa 框架。優(yōu)勢(shì)就是可以利用到系統(tǒng)的所有特性,做出很酷的特性而不損失任何的性能,而缺點(diǎn)就是每次 App 提供新功能都必須重新打包 App,提交給 Apple 進(jìn)行審核,通過以后再上架 App Store,最后用戶再升級(jí),平均需要兩周的時(shí)間。相反,寫一個(gè)網(wǎng)頁則完全沒有這個(gè)限制,服務(wù)器做一次升級(jí),用戶通過瀏覽器再訪問,就是最新的了,而寫網(wǎng)頁的缺點(diǎn)則是受到很大的限制,很多系統(tǒng)特性是無法訪問的,而且性能往往不高,以至于很難實(shí)現(xiàn)一些很酷的效果。
鑒于原生應(yīng)用和網(wǎng)頁各有優(yōu)勢(shì),所以就衍生出了一種介于兩者之間的開發(fā)方式--混合應(yīng)用(hybrid App)。其特點(diǎn)是在原生應(yīng)用中嵌入一個(gè)瀏覽器組件,然后通過某種方式,讓原生代碼和網(wǎng)頁能夠雙向通訊,結(jié)果就是可以在需要原生功能的時(shí)候使用原生功能,而適合放在網(wǎng)頁端的部分就放在服務(wù)器上。某種程度上利用到了兩者的優(yōu)勢(shì)。另一個(gè)優(yōu)勢(shì)就是,由于網(wǎng)頁技術(shù)在 iOS 和 Android 上是一樣的,所以網(wǎng)頁的這部分也就天然可以跨平臺(tái)了。
二 如何實(shí)現(xiàn) hybrid App
實(shí)現(xiàn)一個(gè) hybrid App 最簡(jiǎn)單的方法就是使用 Apache Cordova 開源框架。Cordova 已經(jīng)幫你做好了所有的網(wǎng)頁和原生應(yīng)用之間的橋接工作,你需要做的就是根據(jù)他的文檔去寫對(duì)應(yīng)的網(wǎng)頁代碼和原生代碼就行了。具體請(qǐng)參考官方網(wǎng)站
可惜的是,我們總有些場(chǎng)景無法使用 Cordava,比如我曾經(jīng)的一個(gè)項(xiàng)目,項(xiàng)目主要是要提供一個(gè) SDK ,SDK 本身要使用 hybrid 的技術(shù)。但是 SDK 的用戶可能也會(huì)用到 Cordova,有些情況下,兩者用的 Cordova 為不同版本,正好無法兼容。于是就需要自己去實(shí)現(xiàn) hybrid App 的底層了。
三 iOS hybrid App 的底層實(shí)現(xiàn)
1. 原生代碼調(diào)用網(wǎng)頁中的 JavaScript 函數(shù)
假設(shè)我們的網(wǎng)頁中有如下代碼
[scripttype=text/javascript]functionmyFunc(){returnTextfromweb}[/script]
原生代碼可以用如下方式調(diào)用 myFunc()
NSString*result=[self.webViewstringByEvaluatingJavaScriptFromString:@myFunc()];
在這里 result 就等于 Text from web
2. 網(wǎng)頁中的 JavaScript 調(diào)用系統(tǒng)的原生代碼
這一步比上邊的要復(fù)雜一些,iOS 不像 Android 可以直接給網(wǎng)頁中的 JavaScript 函數(shù)注入一個(gè)原生代碼的接口。這里我們會(huì)用一個(gè)比較曲折的方式來實(shí)現(xiàn)。
假設(shè) Objective-C 的類里有一個(gè)方法
-(void)nativeFunction:(NSString*)args{}
JavaScript 里我們用下邊的方法來最終調(diào)用到上邊這個(gè)方法
window.JSBridge.callFunction(callNativeFunction,somedata);
在我們的頁面里,是沒有 JSBridge.callFunction 存在的,這一步我們要在原生代碼端注入。
在 webView 的 delegate 的 - (void)webViewDidFinishLoad:(UIWebView *)webView 里我們用下邊的方式注入 JavaScript
NSString*js=@(function(){window.JSBridge={};window.JSBridge.callFunction=function(functionName,args){varurl=bridge-js://invoke?;varcallInfo={};callInfo.functionname=functionName;if(args){callInfo.args=args;}url+=JSON.stringify(callInfo);varrootElm=document.documentElement;variFrame=document.createElement(IFRAME);iFrame.setAttribute(src,url);rootElm.appendChild(iFrame);iFrame.parentNode.removeChild(iFrame);};returntrue;})();;[webViewstringByEvaluatingJavaScriptFromString:js];
簡(jiǎn)單解釋一下,首先我們?cè)? window 里創(chuàng)建一個(gè)叫 JSBridge 的對(duì)象,然后在里邊定義一個(gè)方法 callFunction,這個(gè)方法的作用是把兩個(gè)參數(shù)打包為 JSON 字符串,然后附帶到我們自定義的 URL bridge-js://invoke? 后邊,最后用 IFRAME 的方式來加載這個(gè) URL
這么做的原因是,當(dāng)加載 IFRAME 的時(shí)候,就會(huì)調(diào)用 webView 的 delegate 的 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 方法,其中 request 就是我們剛才自定義的那個(gè) URL,在這個(gè)方法里我們做如下處理
NSURL*url=[requestURL];NSString*urlStr=url.absoluteString;return[selfprocessURL:urlStr];
processURL 函數(shù)如下
-(BOOL)processURL:(NSString*)url{NSString*urlStr=[NSStringstringWithString:url];NSString*protocolPrefix=@bridge-js://invoke?;if([[urlStrlowercaseString]hasPrefix:protocolPrefix]){urlStr=[urlStrsubstringFromIndex:protocolPrefix.length];urlStr=[urlStrstringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];NSError*jsonError;NSDictionary*callInfo=[NSJSONSerializationJSONObjectWithData:[urlStrdataUsingEncoding:NSUTF8StringEncoding]options:kNilOptionserror:jsonError];NSString*functionName=[callInfoobjectForKey:@functionname];NSString*args=[callInfoobjectForKey:@args];if([functionNameisEqualToString:@callNativeFunction]){[selfnativeFunction:args];}returnNO;}returnYES;}
從 bridge-js://invoke? 這個(gè)自定義的 URL 里邊把附帶在后邊 JSON 字符串解析出來,然后判斷 functionname key 的值如果是 callNativeFunction 那么就去調(diào)用原生方法 nativeFunction, 如果需要實(shí)現(xiàn)更多的方法調(diào)用,只要添加這個(gè)映射關(guān)系就行了。
至此,JavaScript 和 Objective-C 代碼的雙向調(diào)用就都實(shí)現(xiàn)了。
四 性能監(jiān)測(cè)
Hybrid 和原生應(yīng)用之間的爭(zhēng)論一直以來都不少,其核心問題其實(shí)就是如何平衡開發(fā)成本和用戶體驗(yàn)之間的關(guān)系。Hybrid的開發(fā)成本一般來說要低于原生應(yīng)用,然后其體驗(yàn)總是要差一些。為了讓 Hybrid 的用戶體驗(yàn)?zāi)芨赡艿慕咏鷳?yīng)用,性能監(jiān)測(cè)就顯的更為重要了。
影響 App 使用體驗(yàn)一般來講有兩個(gè)主要方面
第一方面是 UI 的響應(yīng)速度,UI 的流暢與否給用戶的體驗(yàn)是非常不一樣的。對(duì)這方面的性能監(jiān)測(cè),一般的做法就是在主要的交互函數(shù)里打上時(shí)間戳,而對(duì)于系統(tǒng)的 View,也可以采用 Method Swizzle 的方法對(duì)所有的系統(tǒng)函數(shù)的調(diào)用時(shí)間進(jìn)行統(tǒng)計(jì)。
二是網(wǎng)絡(luò),而由于現(xiàn)在的大部分 App 都多少有了網(wǎng)絡(luò)請(qǐng)求,所以網(wǎng)絡(luò)的請(qǐng)求速度也會(huì)很大程度上影響用戶體驗(yàn)。網(wǎng)絡(luò)問題在 Hybrid App 就體現(xiàn)的更明顯。Hybrid App 總是會(huì)去加載服務(wù)器端的頁面,在頁面加載出來之前,很可能整個(gè)手機(jī)屏幕是空白的,如果空白時(shí)間太長(zhǎng),將是一個(gè)很糟糕的事情,所以實(shí)時(shí)的監(jiān)測(cè)請(qǐng)求網(wǎng)頁的時(shí)間,以及頁面的加載速度就非常有必要了。針對(duì) webView,建議在它的 delegate 的幾個(gè)方法里打上時(shí)間戳,以此來統(tǒng)計(jì)頁面請(qǐng)求和加載的時(shí)間。
總之實(shí)現(xiàn)起來,并不是一個(gè)非常復(fù)雜的工作。然而性能監(jiān)測(cè)的工作,實(shí)現(xiàn)只是其中的一個(gè)方面,由于用戶的使用習(xí)慣,實(shí)際的網(wǎng)絡(luò)環(huán)境各種問題,性能監(jiān)測(cè)并不是在開發(fā)階段監(jiān)測(cè)一下就算完了的,一般來說,總是得把監(jiān)測(cè)工作部署到最終用戶的手機(jī)上去的,如果是一個(gè)用戶量不小的 App,那么如何把收集到的大量數(shù)據(jù)很好的統(tǒng)計(jì)顯示出來,這完全是另一回事了,把這事做好,要牽扯到很多的數(shù)據(jù)組織,前端展示的工作,實(shí)際的實(shí)施絕對(duì)不是個(gè)簡(jiǎn)單的工作。
所幸,現(xiàn)在已經(jīng)有很多公司幫我們完成了這個(gè)工作,比如 New Relic,App dynamics,Compuware,聽云等。這次我們就以聽云為例,看看他們是怎么來做性能監(jiān)測(cè)這件事的。
五 聽云探針(iOS App版)的使用
聽云對(duì) App 的性能監(jiān)測(cè)使用起來還是比較簡(jiǎn)單的,簡(jiǎn)單步驟如下
申請(qǐng)完聽云的賬戶后,在添加 App 的地方填寫相關(guān)信息
之后就會(huì)得到一個(gè)唯一的 App Key
然后下載聽云的 iOS SDK 的 Framework,拷貝到項(xiàng)目中,注意添加以下 4 個(gè)額外的系統(tǒng)庫(kù)
CoreTelephony.framework
Security.framework
SystemConfiguration.framework
libz.dylib
然后在 App 的 pch 文件中包含聽云 App 探針的頭文件
#import
最后將 main.m 中加入
[NBSAppAgentstartWithAppID:App_Key];
代碼一般為下邊的樣子
intmain(intargc,char*argv[]){@autoreleasepool{[NBSAppAgentstartWithAppID:App_Key];returnUIApplicationMain(argc,argv,nil,NSStringFromClass([AppDelegateclass]));}}
這樣整個(gè)集成工作就完成了。啟動(dòng) App,如果在 Log 日志中有如下內(nèi)容顯示就表示代碼集成成功
NBSAppAgent2.2.2.1---->start!SuccesstoconnecttoNBSSERVER
六 聽云監(jiān)測(cè)數(shù)據(jù)觀察
1. 匯總數(shù)據(jù)
登錄到聽云的后臺(tái)管理頁面,首先我們可以看到匯總的監(jiān)測(cè)數(shù)據(jù),圖表的效果還是不錯(cuò)的,鼠標(biāo)放到每個(gè)數(shù)據(jù)點(diǎn)上會(huì)顯示詳細(xì)的數(shù)據(jù)。
總體看來,分為兩大類,一類是應(yīng)用交互性能,這類主要是監(jiān)測(cè) UI 響應(yīng)情況,會(huì)給出 view 加載,以及 layout 的時(shí)間匯總。如果發(fā)現(xiàn)某一項(xiàng)參數(shù)出現(xiàn)異常,那也許就是需要重構(gòu) UI 的信號(hào)了。另一類是網(wǎng)絡(luò)性能,包含網(wǎng)絡(luò)請(qǐng)求的響應(yīng)時(shí)間等。
2. Web View
重要的東西最先講,這個(gè)部分是聽云目前最有特色的部分(好像是首家這么做的,目前還沒在其他的類似服務(wù)里看到這個(gè)功能)。通常我們進(jìn)行網(wǎng)絡(luò)性能監(jiān)測(cè)的時(shí)候,給出的是整個(gè)網(wǎng)絡(luò)請(qǐng)求的情況,這在瀏覽器里邊來說,整個(gè)網(wǎng)絡(luò)請(qǐng)求其實(shí)也就是頁面的請(qǐng)求,兩者沒有區(qū)別。而到了 App 里,同樣是 http 請(qǐng)求,有可能是來自 web service 的調(diào)用,也可能是來自 web view 加載頁面。而后者正好是我們講的 Hybrid App 的主要實(shí)現(xiàn)方式。聽云的這個(gè)條目就是完全只給出 web view 所進(jìn)行的請(qǐng)求情況,換句話說,這是我們用來監(jiān)測(cè) Hybrid App 網(wǎng)絡(luò)性能的最好數(shù)據(jù)。
(1)HTTP請(qǐng)求
這里有所有 web view 所加載的頁面的匯總數(shù)據(jù)。
(2)頁面加載
聽云除了給出網(wǎng)絡(luò)性能的數(shù)據(jù),這里還很貼心的給出了頁面加載的匯總數(shù)據(jù),要知道現(xiàn)在的網(wǎng)頁是有可能非常復(fù)雜,包含很多頁面元素的,在桌面端問題也許不明顯,但在移動(dòng)端,太復(fù)雜的效果也許會(huì)大大的拖慢加載速度,影響用戶體驗(yàn)。根據(jù)這里給出的頁面加載數(shù)據(jù),就可以有針對(duì)性的去優(yōu)化網(wǎng)頁了。
3. 網(wǎng)絡(luò)
這個(gè)條目主要是 App 的所有網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)。
(1)拓補(bǔ)圖
這主要是一個(gè)分類匯總的數(shù)據(jù),可以分別查看是自己的 App 所以及第三方服務(wù)所發(fā)送的網(wǎng)絡(luò)請(qǐng)求的匯總數(shù)據(jù)。
(2)HTTP請(qǐng)求
顧名思義,這里是所有 http 請(qǐng)求的詳細(xì)數(shù)據(jù),分別顯示了響應(yīng)最慢的主機(jī),以及吞吐量最高的主機(jī)。
(3)地域
這個(gè)條目比較有意思,用顏色的方式標(biāo)示出了世界各國(guó)的平均響應(yīng)時(shí)間,點(diǎn)擊對(duì)應(yīng)的國(guó)家還可以繼續(xù)進(jìn)入到下一級(jí),最后可以進(jìn)入到詳細(xì)的網(wǎng)絡(luò)數(shù)據(jù)分析頁面。
(4)組合分析
這個(gè)頁面在中國(guó)的網(wǎng)絡(luò)環(huán)境下是非常有用的,它可以根據(jù)運(yùn)營(yíng)商,地域,接入方式來給出匯總數(shù)據(jù)。要知道中國(guó)的網(wǎng)絡(luò)環(huán)境非常復(fù)雜,不同運(yùn)營(yíng)商之間,甚至同一運(yùn)營(yíng)商在不同的地區(qū)網(wǎng)絡(luò)互通情況會(huì)相差非常大。有了這里的數(shù)據(jù),就可以有針對(duì)性的去部署服務(wù)器,優(yōu)化網(wǎng)絡(luò)體驗(yàn)。
4. 交互
進(jìn)入交互分析具體項(xiàng)
在這里我們可以詳細(xì)的看到 ViewController 以及 View 的每一個(gè)系統(tǒng)函數(shù)的調(diào)用時(shí)間,通過這個(gè)數(shù)據(jù)就可以非常好的分析是哪個(gè)一個(gè) ViewController 出了問題,對(duì)應(yīng)的去重構(gòu)就可以了。
交互分析下邊的幾項(xiàng)是通過一定的條件來看 UI 交互的具體數(shù)據(jù),可以通過版本進(jìn)行過濾,這樣就可以方便的過濾掉已經(jīng)把問題修改掉了的版本。還可以通過操作系統(tǒng)(iOS/Android)和設(shè)備來看各自的交互響應(yīng)的數(shù)據(jù)。
5. 其他
除了性能分析,聽云的數(shù)據(jù)里也有常見的崩潰數(shù)據(jù),活躍數(shù)以及事件監(jiān)測(cè)等。這里就不詳細(xì)展開了
七 總結(jié)
Hybrid App 在某些特定場(chǎng)景是非常有用的,然而也確實(shí)有它的局限性,特別是對(duì)交互要求很高的地方,使用它是不太合適,畢竟它還是基于網(wǎng)頁技術(shù)。不過html5在移動(dòng)端的發(fā)展也非常迅速,也許會(huì)有更好的未來也說不定。總之掌握這個(gè)技術(shù)是不會(huì)錯(cuò)的。另外由于網(wǎng)頁端很可能會(huì)成為我們性能瓶頸,所以要時(shí)時(shí)注意測(cè)試相關(guān)部分的性能表現(xiàn),也建議使用一些應(yīng)用性能監(jiān)測(cè)的第三方服務(wù)。這樣能夠更好的定位產(chǎn)品環(huán)境的問題。
評(píng)論