你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS hybrid App 的實現原理及性能監測

iOS hybrid App 的實現原理及性能監測

編輯:IOS開發基礎

QQ圖片20151118122500.jpg

作者董一凡自述:作為一名寫了十年代碼的程序員,目前我最擅長的領域是移動平台的客戶端開發,在移動領域的開發時間超過七年,前前後後涉獵過很多個平台。隨著大部分移動平台自己走向死亡,現在我也主要專注在了iOS和Android兩大移動平台,偶爾也會客串下Windows這個不知道是移動還是桌面的平台。 十年前,我剛入行的時候,曾經認為自己將會永遠做一個C++程序員,於是花了大量時間在C++上。現在C++也是我工作所用的主力語言之一,工作之外也會偶爾寫點什麼娛樂一下。 寫了一些年程序後,終於意識到了之前定位的狹隘,於是開始廣泛的學習各種技術,各種各樣的語言也學了很多,值得慶幸的是,幾年折騰下來,我一直也沒有對寫代碼這件事感到厭倦,於是我又認為自己將會永遠把開發做下去。 現在,我也覺得開發是一個可以終身做下去的事業,不過除了事業我還想追求更多的東西,從這些年的經歷來看,其中貫穿始終的就是在不停的學習,想明白這一點後,我開始除技術之外更廣領域的學習,比如日語,畫畫,設計,鋼琴等等,給自己的定位也變成了在今後作為一名終身學習者。

一 iOS hybrid App 簡單介紹

大家應該多少都知道,iOS 設備上有兩種入口,一是通過 App Strore 下載一個個的 App,另一個是用系統浏覽器去訪問網頁。前者我們一般稱為原生應用,後者就是傳統意義上的網頁。兩者各有特點,開發一個原生應用,一般是使用 Apple 給我們提供的開發工具和 Cocoa 框架。優勢就是可以利用到系統的所有特性,做出很酷的特性而不損失任何的性能,而缺點就是每次 App 提供新功能都必須重新打包 App,提交給 Apple 進行審核,通過以後再上架 App Store,最後用戶再升級,平均需要兩周的時間。相反,寫一個網頁則完全沒有這個限制,服務器做一次升級,用戶通過浏覽器再訪問,就是最新的了,而寫網頁的缺點則是受到很大的限制,很多系統特性是無法訪問的,而且性能往往不高,以至於很難實現一些很酷的效果。

鑒於原生應用和網頁各有優勢,所以就衍生出了一種介於兩者之間的開發方式--混合應用(hybrid App)。其特點是在原生應用中嵌入一個浏覽器組件,然後通過某種方式,讓原生代碼和網頁能夠雙向通訊,結果就是可以在需要原生功能的時候使用原生功能,而適合放在網頁端的部分就放在服務器上。某種程度上利用到了兩者的優勢。另一個優勢就是,由於網頁技術在 iOS 和 Android 上是一樣的,所以網頁的這部分也就天然可以跨平台了。

二 如何實現 hybrid App

實現一個 hybrid App 最簡單的方法就是使用 Apache Cordova 開源框架。Cordova 已經幫你做好了所有的網頁和原生應用之間的橋接工作,你需要做的就是根據他的文檔去寫對應的網頁代碼和原生代碼就行了。具體請參考官方網站

可惜的是,我們總有些場景無法使用 Cordava,比如我曾經的一個項目,項目主要是要提供一個 SDK ,SDK 本身要使用 hybrid 的技術。但是 SDK 的用戶可能也會用到 Cordova,有些情況下,兩者用的 Cordova 為不同版本,正好無法兼容。於是就需要自己去實現 hybrid App 的底層了。

三 iOS hybrid App 的底層實現

1. 原生代碼調用網頁中的 JavaScript 函數

假設我們的網頁中有如下代碼

[script type="text/javascript"]
    function myFunc() {
        return "Text from web"
    }
[/script]

原生代碼可以用如下方式調用 myFunc()

NSString * result = [self.webView stringByEvaluatingJavaScriptFromString:@"myFunc()"];

在這裡 result 就等於 Text from web

2. 網頁中的 JavaScript 調用系統的原生代碼

這一步比上邊的要復雜一些,iOS 不像 Android 可以直接給網頁中的 JavaScript 函數注入一個原生代碼的接口。這裡我們會用一個比較曲折的方式來實現。

假設 Objective-C 的類裡有一個方法

-(void)nativeFunction:(NSString*)args {
}

JavaScript 裡我們用下邊的方法來最終調用到上邊這個方法

window.JSBridge.callFunction("callNativeFunction", "some data");

在我們的頁面裡,是沒有 JSBridge.callFunction 存在的,這一步我們要在原生代碼端注入。

在 webView 的 delegate 的 - (void)webViewDidFinishLoad:(UIWebView *)webView 裡我們用下邊的方式注入 JavaScript

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,在這個方法裡我們做如下處理

NSURL *url = [request URL];
NSString *urlStr = url.absoluteString;
return [self processURL:urlStr];

processURL 函數如下

-(BOOL) processURL:(NSString *) url
{
    NSString *urlStr = [NSString stringWithString:url];
    NSString *protocolPrefix = @"bridge-js://invoke?";
    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 的地方填寫相關信息

blob.png

之後就會得到一個唯一的 App Key

blob.png

然後下載聽雲的 iOS SDK 的 Framework,拷貝到項目中,注意添加以下 4 個額外的系統庫

  • CoreTelephony.framework

  • Security.framework

  • SystemConfiguration.framework

  • libz.dylib

然後在 App 的 pch 文件中包含聽雲 App 探針的頭文件

#import

最後將 main.m 中加入

[NBSAppAgent startWithAppID:App_Key];

代碼一般為下邊的樣子

int main(int argc, char * argv[]) {
@autoreleasepool {
    [NBSAppAgent startWithAppID:App_Key];
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

這樣整個集成工作就完成了。啟動 App,如果在 Log 日志中有如下內容顯示就表示代碼集成成功

NBSAppAgent 2.2.2.1
---->start!
Success to connect to NBSSERVER

六 聽雲監測數據觀察

1. 匯總數據

登錄到聽雲的後台管理頁面,首先我們可以看到匯總的監測數據,圖表的效果還是不錯的,鼠標放到每個數據點上會顯示詳細的數據。

blob.png

總體看來,分為兩大類,一類是應用交互性能,這類主要是監測 UI 響應情況,會給出 view 加載,以及 layout 的時間匯總。如果發現某一項參數出現異常,那也許就是需要重構 UI 的信號了。另一類是網絡性能,包含網絡請求的響應時間等。

2. Web View

重要的東西最先講,這個部分是聽雲目前最有特色的部分(好像是首家這麼做的,目前還沒在其他的類似服務裡看到這個功能)。通常我們進行網絡性能監測的時候,給出的是整個網絡請求的情況,這在浏覽器裡邊來說,整個網絡請求其實也就是頁面的請求,兩者沒有區別。而到了 App 裡,同樣是 http 請求,有可能是來自 web service 的調用,也可能是來自 web view 加載頁面。而後者正好是我們講的 Hybrid App 的主要實現方式。聽雲的這個條目就是完全只給出 web view 所進行的請求情況,換句話說,這是我們用來監測 Hybrid App 網絡性能的最好數據。

(1)HTTP請求

blob.png

這裡有所有 web view 所加載的頁面的匯總數據。

(2)頁面加載

blob.png

聽雲除了給出網絡性能的數據,這裡還很貼心的給出了頁面加載的匯總數據,要知道現在的網頁是有可能非常復雜,包含很多頁面元素的,在桌面端問題也許不明顯,但在移動端,太復雜的效果也許會大大的拖慢加載速度,影響用戶體驗。根據這裡給出的頁面加載數據,就可以有針對性的去優化網頁了。

3. 網絡

這個條目主要是 App 的所有網絡請求的數據。

(1)拓補圖

blob.png

這主要是一個分類匯總的數據,可以分別查看是自己的 App 所以及第三方服務所發送的網絡請求的匯總數據。

(2)HTTP請求

blob.png

顧名思義,這裡是所有 http 請求的詳細數據,分別顯示了響應最慢的主機,以及吞吐量最高的主機。

(3)地域

blob.png

這個條目比較有意思,用顏色的方式標示出了世界各國的平均響應時間,點擊對應的國家還可以繼續進入到下一級,最後可以進入到詳細的網絡數據分析頁面。

blob.png

(4)組合分析

blob.png

這個頁面在中國的網絡環境下是非常有用的,它可以根據運營商,地域,接入方式來給出匯總數據。要知道中國的網絡環境非常復雜,不同運營商之間,甚至同一運營商在不同的地區網絡互通情況會相差非常大。有了這裡的數據,就可以有針對性的去部署服務器,優化網絡體驗。

4. 交互

進入交互分析具體項

blob.png

在這裡我們可以詳細的看到 ViewController 以及 View 的每一個系統函數的調用時間,通過這個數據就可以非常好的分析是哪個一個 ViewController 出了問題,對應的去重構就可以了。

交互分析下邊的幾項是通過一定的條件來看 UI 交互的具體數據,可以通過版本進行過濾,這樣就可以方便的過濾掉已經把問題修改掉了的版本。還可以通過操作系統(iOS/Android)和設備來看各自的交互響應的數據。

5. 其他

除了性能分析,聽雲的數據裡也有常見的崩潰數據,活躍數以及事件監測等。這裡就不詳細展開了

七 總結

Hybrid App 在某些特定場景是非常有用的,然而也確實有它的局限性,特別是對交互要求很高的地方,使用它是不太合適,畢竟它還是基於網頁技術。不過html5在移動端的發展也非常迅速,也許會有更好的未來也說不定。總之掌握這個技術是不會錯的。另外由於網頁端很可能會成為我們性能瓶頸,所以要時時注意測試相關部分的性能表現,也建議使用一些應用性能監測的第三方服務。這樣能夠更好的定位產品環境的問題。


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved