做爲一名寫了⑦年代碼的程序員,目前我最擅長的領域是IOS的客戶端開發,在移動領域的開發時間2年。 ⑦年前,我剛入行的時候,曾經認爲本身將會永遠作一個LINUX 服務端C++程序員,因而花了大量時間在C++上。如今C++也是我工做所用的主力語言之一,工做以外也會偶爾寫點什麼娛樂一下。 寫了一些年程序後,終於意識到了以前定位的狹隘,因而開始普遍的學習各類技術,各類各樣的語言也學了不少,值得慶幸的是,幾年折騰下來,我一直也沒有對寫代碼這件事感到厭倦,因而我又認爲本身將會永遠把開發作下去。 如今,我也以爲開發是一個能夠終身作下去的事業,不過除了事業我還想追求更多的東西,從這些年的經從來看,其中貫穿始終的就是在不停的學習,想明白這一點後,給本身的定位也變成了在從此做爲一名終身學習者。javascript
一 iOS hybrid App 簡單介紹html
你們應該多少都知道,iOS 設備上有兩種入口,一是經過 App Strore 下載一個個的 App,另外一個是用系統瀏覽器去訪問網頁。前者咱們通常稱爲原生應用,後者就是傳統意義上的網頁。二者各有特色,開發一個原生應用,通常是使用 Apple 給咱們提供的開發工具和 Cocoa 框架。優點就是能夠利用到系統的全部特性,作出很酷的特性而不損失任何的性能,而缺點就是每次 App 提供新功能都必須從新打包 App,提交給 Apple 進行審覈,經過之後再上架 App Store,最後用戶再升級,平均須要兩週的時間。相反,寫一個網頁則徹底沒有這個限制,服務器作一次升級,用戶經過瀏覽器再訪問,就是最新的了,而寫網頁的缺點則是受到很大的限制,不少系統特性是沒法訪問的,並且性能每每不高,以致於很難實現一些很酷的效果。前端
鑑於原生應用和網頁各有優點,因此就衍生出了一種介於二者之間的開發方式--混合應用(hybrid App)。其特色是在原生應用中嵌入一個瀏覽器組件,而後經過某種方式,讓原生代碼和網頁可以雙向通信,結果就是能夠在須要原生功能的時候使用原生功能,而適合放在網頁端的部分就放在服務器上。某種程度上利用到了二者的優點。另外一個優點就是,因爲網頁技術在 iOS 和 Android 上是同樣的,因此網頁的這部分也就自然能夠跨平臺了。html5
二 如何實現 hybrid Appjava
實現一個 hybrid App 最簡單的方法就是使用 Apache Cordova 開源框架。Cordova 已經幫你作好了全部的網頁和原生應用之間的橋接工做,你須要作的就是根據他的文檔去寫對應的網頁代碼和原生代碼就好了。具體請參考官方網站程序員
惋惜的是,咱們總有些場景沒法使用 Cordava,好比我曾經的一個項目,項目主要是要提供一個 SDK ,SDK 自己要使用 hybrid 的技術。可是 SDK 的用戶可能也會用到 Cordova,有些狀況下,二者用的 Cordova 爲不一樣版本,正好沒法兼容。因而就須要本身去實現 hybrid App 的底層了。web
三 iOS hybrid App 的底層實現apache
1. 原生代碼調用網頁中的 JavaScript 函數json
假設咱們的網頁中有以下代碼瀏覽器
1
2
3
4
5
|
[script type=
"text/javascript"
]
function
myFunc() {
return
"Text from web"
}
[/script]
|
原生代碼能夠用以下方式調用 myFunc()
1
|
NSString * result = [self.webView stringByEvaluatingJavaScriptFromString:@
"myFunc()"
];
|
在這裏 result 就等於 Text from web
2. 網頁中的 JavaScript 調用系統的原生代碼
這一步比上邊的要複雜一些,iOS 不像 Android 能夠直接給網頁中的 JavaScript 函數注入一個原生代碼的接口。這裏咱們會用一個比較曲折的方式來實現。
假設 Objective-C 的類裏有一個方法
1
2
|
-(void)nativeFunction:(NSString*)args {
}
|
JavaScript 裏咱們用下邊的方法來最終調用到上邊這個方法
1
|
window.JSBridge.callFunction(
"callNativeFunction"
,
"some data"
);
|
在咱們的頁面裏,是沒有 JSBridge.callFunction 存在的,這一步咱們要在原生代碼端注入。
在 webView 的 delegate 的 - (void)webViewDidFinishLoad:(UIWebView *)webView 裏咱們用下邊的方式注入 JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
NSString *js = @"(
function
() {\
window.JSBridge = {};\
window.JSBridge.callFunction =
function
(functionName, args){\
var
url = \"bridge-js:
//invoke?\";\
var
callInfo = {};\
callInfo.functionname = functionName;\
if
(args)\
{\
callInfo.args = args;\
}\
url += JSON.stringify(callInfo);\
var
rootElm = document.documentElement;\
var
iFrame = document.createElement(\"IFRAME\");\
iFrame.setAttribute(\"src\",url);\
rootElm.appendChild(iFrame);\
iFrame.parentNode.removeChild(iFrame);\
};\
return
true
;\
})();";
[webView stringByEvaluatingJavaScriptFromString:js];
|
簡單解釋一下,首先咱們在 window 裏建立一個叫 JSBridge 的對象,而後在裏邊定義一個方法 callFunction,這個方法的做用是把兩個參數打包爲 JSON 字符串,而後附帶到咱們自定義的 URL bridge-js://invoke? 後邊,最後用 IFRAME 的方式來加載這個 URL
這麼作的緣由是,當加載 IFRAME 的時候,就會調用 webView 的 delegate 的 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 方法,其中 request 就是咱們剛纔自定義的那個 URL,在這個方法裏咱們作以下處理
1
2
3
|
NSURL *url = [request URL];
NSString *urlStr = url.absoluteString;
return
[self processURL:urlStr];
|
processURL 函數以下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
-(BOOL) processURL:(NSString *) url
{
NSString *urlStr = [NSString stringWithString:url];
if
([[urlStr lowercaseString] hasPrefix:protocolPrefix])
{
urlStr = [urlStr substringFromIndex:protocolPrefix.length];
urlStr = [urlStr stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSError *jsonError;
NSDictionary *callInfo = [NSJSONSerialization
JSONObjectWithData:[urlStr dataUsingEncoding:NSUTF8StringEncoding]
options:kNilOptions
error:&jsonError];
NSString *functionName = [callInfo objectForKey:@
"functionname"
];
NSString * args = [callInfo objectForKey:@
"args"
];
if
([functionName isEqualToString:@
"callNativeFunction"
]) {
[self nativeFunction:args];
}
return
NO;
}
return
YES;
}
|
從 bridge-js://invoke? 這個自定義的 URL 裏邊把附帶在後邊 JSON 字符串解析出來,而後判斷 functionname key 的值若是是 callNativeFunction 那麼就去調用原生方法 nativeFunction, 若是須要實現更多的方法調用,只要添加這個映射關係就好了。
至此,JavaScript 和 Objective-C 代碼的雙向調用就都實現了。
四 性能監測
Hybrid 和原生應用之間的爭論一直以來都很多,其核心問題其實就是如何平衡開發成本和用戶體驗之間的關係。Hybrid的開發成本通常來講要低於原生應用,而後其體驗老是要差一些。爲了讓 Hybrid 的用戶體驗能更可能的接近原生應用,性能監測就顯的更爲重要了。
影響 App 使用體驗通常來說有兩個主要方面
第一方面是 UI 的響應速度,UI 的流暢與否給用戶的體驗是很是不同的。對這方面的性能監測,通常的作法就是在主要的交互函數裏打上時間戳,而對於系統的 View,也能夠採用 Method Swizzle 的方法對全部的系統函數的調用時間進行統計。
二是網絡,而因爲如今的大部分 App 都多少有了網絡請求,因此網絡的請求速度也會很大程度上影響用戶體驗。網絡問題在 Hybrid App 就體現的更明顯。Hybrid App 老是會去加載服務器端的頁面,在頁面加載出來以前,極可能整個手機屏幕是空白的,若是空白時間太長,將是一個很糟糕的事情,因此實時的監測請求網頁的時間,以及頁面的加載速度就很是有必要了。針對 webView,建議在它的 delegate 的幾個方法裏打上時間戳,以此來統計頁面請求和加載的時間。
總之實現起來,並非一個很是複雜的工做。然而性能監測的工做,實現只是其中的一個方面,因爲用戶的使用習慣,實際的網絡環境各類問題,性能監測並非在開發階段監測一下就算完了的,通常來講,老是得把監測工做部署到最終用戶的手機上去的,若是是一個用戶量不小的 App,那麼如何把收集到的大量數據很好的統計顯示出來,這徹底是另外一回事了,把這事作好,要牽扯到不少的數據組織,前端展現的工做,實際的實施絕對不是個簡單的工做。
所幸,如今已經有不少公司幫咱們完成了這個工做,好比 New Relic,App dynamics,Compuware,聽雲等。此次咱們就以聽云爲例,看看他們是怎麼來作性能監測這件事的。
五 聽雲探針(iOS App版)的使用
聽雲對 App 的性能監測使用起來仍是比較簡單的,簡單步驟以下
申請完聽雲的帳戶後,在添加 App 的地方填寫相關信息
以後就會獲得一個惟一的 App Key
而後下載聽雲的 iOS SDK 的 Framework,拷貝到項目中,注意添加如下 4 個額外的系統庫
CoreTelephony.framework
Security.framework
SystemConfiguration.framework
libz.dylib
而後在 App 的 pch 文件中包含聽雲 App 探針的頭文件
1
|
#import
|
最後將 main.m 中加入
1
|
[NBSAppAgent startWithAppID:App_Key];
|
代碼通常爲下邊的樣子
1
2
3
4
5
6
|
int main(int argc, char * argv[]) {
@autoreleasepool {
[NBSAppAgent startWithAppID:App_Key];
return
UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
|
這樣整個集成工做就完成了。啓動 App,若是在 Log 日誌中有以下內容顯示就表示代碼集成成功
1
2
3
|
NBSAppAgent 2.2.2.1
---->start!
Success to connect to NBSSERVER
|
六 聽雲監測數據觀察
1. 彙總數據
登陸到聽雲的後臺管理頁面,首先咱們能夠看到彙總的監測數據,圖表的效果仍是不錯的,鼠標放到每一個數據點上會顯示詳細的數據。
整體看來,分爲兩大類,一類是應用交互性能,這類主要是監測 UI 響應狀況,會給出 view 加載,以及 layout 的時間彙總。若是發現某一項參數出現異常,那也許就是須要重構 UI 的信號了。另外一類是網絡性能,包含網絡請求的響應時間等。
2. Web View
重要的東西最早講,這個部分是聽雲目前最有特點的部分(好像是首家這麼作的,目前還沒在其餘的相似服務裏看到這個功能)。一般咱們進行網絡性能監測的時候,給出的是整個網絡請求的狀況,這在瀏覽器裏邊來講,整個網絡請求其實也就是頁面的請求,二者沒有區別。而到了 App 裏,一樣是 http 請求,有多是來自 web service 的調用,也多是來自 web view 加載頁面。然後者正好是咱們講的 Hybrid App 的主要實現方式。聽雲的這個條目就是徹底只給出 web view 所進行的請求狀況,換句話說,這是咱們用來監測 Hybrid App 網絡性能的最好數據。
(1)HTTP請求
這裏有全部 web view 所加載的頁面的彙總數據。
(2)頁面加載
聽雲除了給出網絡性能的數據,這裏還很貼心的給出了頁面加載的彙總數據,要知道如今的網頁是有可能很是複雜,包含不少頁面元素的,在桌面端問題也許不明顯,但在移動端,太複雜的效果也許會大大的拖慢加載速度,影響用戶體驗。根據這裏給出的頁面加載數據,就能夠有針對性的去優化網頁了。
3. 網絡
這個條目主要是 App 的全部網絡請求的數據。
(1)拓補圖
這主要是一個分類彙總的數據,能夠分別查看是本身的 App 因此及第三方服務所發送的網絡請求的彙總數據。
(2)HTTP請求
顧名思義,這裏是全部 http 請求的詳細數據,分別顯示了響應最慢的主機,以及吞吐量最高的主機。
(3)地域
這個條目比較有意思,用顏色的方式標示出了世界各國的平均響應時間,點擊對應的國家還能夠繼續進入到下一級,最後能夠進入到詳細的網絡數據分析頁面。
(4)組合分析
這個頁面在中國的網絡環境下是很是有用的,它能夠根據運營商,地域,接入方式來給出彙總數據。要知道中國的網絡環境很是複雜,不一樣運營商之間,甚至同一運營商在不一樣的地區網絡互通狀況會相差很是大。有了這裏的數據,就能夠有針對性的去部署服務器,優化網絡體驗。
4. 交互
進入交互分析具體項
在這裏咱們能夠詳細的看到 ViewController 以及 View 的每個系統函數的調用時間,經過這個數據就能夠很是好的分析是哪一個一個 ViewController 出了問題,對應的去重構就能夠了。
交互分析下邊的幾項是經過必定的條件來看 UI 交互的具體數據,能夠經過版本進行過濾,這樣就能夠方便的過濾掉已經把問題修改掉了的版本。還能夠經過操做系統(iOS/Android)和設備來看各自的交互響應的數據。
5. 其餘
除了性能分析,聽雲的數據裏也有常見的崩潰數據,活躍數以及事件監測等。這裏就不詳細展開了
七 總結
Hybrid App 在某些特定場景是很是有用的,然而也確實有它的侷限性,特別是對交互要求很高的地方,使用它是不太合適,畢竟它仍是基於網頁技術。不過html5在移動端的發展也很是迅速,也許會有更好的將來也說不定。總之掌握這個技術是不會錯的。另外因爲網頁端極可能會成爲咱們性能瓶頸,因此要時時注意測試相關部分的性能表現,也建議使用一些應用性能監測的第三方服務。這樣可以更好的定位產品環境的問題。