關於UIWebView和PhoneGap的總結

前言

今天參加了Adobe和CSDN組織的一個關於PhoneGap的開發講座 ,而PhoneGap在iOS設備上的實現就是經過UIWebView控件來展現html內容,而且與native代碼進行交互的。javascript

正好咱們在作有道雲筆記的iPad版,由於咱們也是使用UIWebView來展現筆記內容,因此也須要作js與native代碼相互調用的事情。因此在這兒順便總結一下UIWebView在使用上的細節,以及談談我對PhoneGap的見解。html

機制

首先咱們須要讓UIWebView加載本地HTML。使用以下代碼完成:前端

NSString * path = [[NSBundle mainBundle] bundlePath];
NSURL * baseURL = [NSURL fileURLWithPath:path];
NSString * htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString * htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:(NSUTF8StringEncoding) error:nil];
[self.webView loadHTMLString:htmlString baseURL:baseURL];

如下是PhoneGap相關調用的示例代碼:java

// Objective-C語言
- (BOOL)webView:(UIWebView *)webView
    shouldStartLoadWithRequest:(NSURLRequest *)request
    navigationType:(UIWebViewNavigationType)navigationType {
       NSURL * url = [request URL];
       if ([[url scheme] isEqualToString:@"gap"]) {
            // 在這裏作js調native的事情
            // ....
            // 作完以後用以下方法調回js
            [webView stringByEvaluatingJavaScriptFromString:@"alert('done')"];
            return NO;
        }
        return YES;
}

具體讓js通知native的方法是讓js發起一次特殊的網絡請求。這裏,咱們和PhoneGap都是使用加載一個隱藏的iframe來實現的,經過將iframe的src指定爲一個特殊的URL,實如今delegate方法中截獲此次請求。node

如下是PhoneGap相關調用的示例代碼:android

 

// Javascript語言
// 通知iPhone UIWebView 加載url對應的資源
// url的格式爲: gap:something
function loadURL(url) {
    var iFrame;
    iFrame = document.createElement("iframe");
    iFrame.setAttribute("src", url);
    iFrame.setAttribute("style", "display:none;");
    iFrame.setAttribute("height", "0px");
    iFrame.setAttribute("width", "0px");
    iFrame.setAttribute("frameborder", "0");
    document.body.appendChild(iFrame);
    // 發起請求後這個iFrame就沒用了,因此把它從dom上移除掉
    iFrame.parentNode.removeChild(iFrame);
    iFrame = null;
}

在這裏,可能有些人說,經過改document.location也能夠達到相同的效果。關於這個,我和zyc專門試過,通常狀況下,改document.location是能夠,可是改document.location有一個很嚴重的問題,就是若是咱們連續2個js調native,連續2次改document.location的話,在native的delegate方法中,只能截獲後面那次請求,前一次請求因爲很快被替換掉,因此被忽略掉了。git

我也專門去Github上查找相關的開源代碼,它們都是用過iframe來實現調用的,例如這個:https://github.com/marcuswestin/WebViewJavascriptBridgegithub

關於這個,我也作了一個Demo來簡單示例,地址以下:https://github.com/tangqiaoboy/UIWebViewSampleweb

 

參數的傳遞

以上的示例代碼爲了講清楚機制,因此只是示例了最簡單的相互調用。但實際上js和native相互調用時,經常須要傳遞參數。編程

例如,有道雲筆記iPad版用UIWebView顯示筆記的內容,當用戶點擊了筆記中的附件,這個時候,js須要通知native到後臺下載這個筆記附件,同時通知js當前的下載進度。對於這個需求,js層得到用戶點擊事件後,就須要把當前點擊的附件的ID傳遞給native,這樣native才能知道下載哪一個附件。

參數傳遞最簡單的方式是將參數做爲url的一部分,放到iFrame的src裏面。這樣UIWebView經過截取分析url後面的內容便可得到參數。可是這樣的問題是,該方法只能傳遞簡單的參數信息,若是參數是一個很複雜的對象,那麼這個url的編解碼將會很複雜。對此,咱們的有道雲筆記和PhoneGap採用了不一樣的技術方案。

  • 咱們的技術方案是將參數以JSON的形式傳遞,可是由於要附加在url以後,因此咱們將JSON進行了Base64編碼,以保證url中不會出現一些非法的字符。
  • PhoneGap的技術方案是,也是用JSON傳遞參數,可是將JSON放在UIWebView中的一個全局數組中,UIWebView當須要讀取參數時,經過讀取這個全局數組來得到相應的參數。

相比之下,應該說PhoneGap的方案更加全面,適用於多種場景。而咱們的方案簡潔高效,知足了咱們本身產品的需求。

 

同步和異步

由於iOS SDK沒有天生支持js和native相互調用,你們的技術方案都是本身實現的一套調用機制,因此這裏面有同步異步的問題。細心的同窗就能發現,js調用native是經過插入一個iframe,這個iframe插入完了就完了,執行的結果須要native另外用stringByEvaluatingJavaScriptFromString方法通知js,因此這是一個異步的調用。

而stringByEvaluatingJavaScriptFromString方法自己會直接返回一個NSString類型的執行結果,因此這顯然是一個同步調用。

因此js call native是異步,native call js是同步。在處理一些邏輯的時候,不可避免須要考慮這個特色。

這裏順便說一個android,其實在android開發中,js調native是同步的,可是PhoneGap爲了將本身作成一個跨平臺的框架,因此在android的js call native的native端,用 new Thread新建了一個執行線程,這樣把android的js call native也變成了異步調用。

UIWebView的問題

線程阻塞問題

咱們在開發中發現,當在native層調用stringByEvaluatingJavaScriptFromString方法時,可能因爲javascript是單線程的緣由,會阻塞原有js代碼的執行。這裏咱們的解決辦法是在js端用defer將iframe的插入延後執行。

主線程的問題

UIWebView的stringByEvaluatingJavaScriptFromString方法必須是主線程中執行,而主線程的執行時間過長就會block UI的更新。因此咱們應該儘可能讓stringByEvaluatingJavaScriptFromString方法執行的時間短。

有道雲筆記在保存的時候,須要調用js得到筆記的完整html內容,這個時候若是筆記內容很複雜,就會執行很長一段時間,而由於這個操做必須是主線程執行,因此咱們顯示「正在保存」的UIAlertView徹底沒法正常顯示,整個UI界面徹底卡住了。在新的編輯器裏,咱們更新了得到html內容的代碼,纔將這個問題解決。

鍵盤控制

作iOS開發的都知道,當咱們須要鍵盤顯示在某個控件上時,能夠調用[obj becomeFirstResponder]方法來讓鍵盤出來,而且光標輸入焦點出如今該控件上。

可是這個方法對於UIWebView並不可用。也就是說,咱們沒法經過程序控制讓光標輸入焦點出如今UIWebView上。 關於這個問題,我在stackoverflow上專門問了一下,仍是沒有獲得很好的解決辦法。

CommonJS規範

commonJS是一個模塊塊加載的規範。而AMD是該規範的一個草案,CommonJS AMD規範描述了模塊化的定義,依賴關係,引用關係以及加載機制,其規範原文在這裏 。它被requireJS,NodeJs,Dojo,jQuery等開源框架普遍使用。這裏還有一篇不錯的中文介紹文章。

AMD規範須要用目錄層級看成包層次,這一點就象java同樣。以前我覺得iOS打包後的ipa資源文件中不能有資源目錄層級關係,今天在會上問了一下,原來是我本身弄錯了。若是須要將目錄層級帶入ipa資源文件中,只須要將該目錄拖入工程中,而後選擇「Create groups for any added folders」。以下圖所示,這樣目錄層級可以打包到ipa文件中。

調試

在iOS設備中調試javascript是一件至關苦逼的事情,拿pw的話來講:「一會兒回到了ie6時代」。固然,業界也有一些調試工具能夠用的。

咱們在開發時主要採用的是weinre這個框架。用這個框架,能夠作一些基本的調試工做,可是它如今功能尚未象pc上的js調試器那麼強大,例如它不能下斷點,另外若是有js執行錯誤,它也沒法正確的將錯誤信息報出。它還有一些bug,例如在mac機下,若是你同時鏈接了有線網和無線網,那麼weinre將沒法正確地鏈接到調試頁面。

但終究,它是如今業界現存的惟一相對可用的調試工具了。本次的PhoneGap講座的第一位演講者董龍飛有一篇博客很好地介紹了weinre的使用,地址是這裏,推薦感興趣的同窗看看。即便不用PhoneGap,weinre也能給你在移動設備上設計網頁帶來方便。

(2013年10月22日更新):關於調試這一起,從WWDC2012開始,蘋果已經支持用safari來鏈接iPhone模擬器裏面的UIWebView進行調試了,因此調試上已經方便了不少。詳細的教程能夠查看: WWDC2012 Session 600《Debuging UIWebViews and Websites on iOS》

我對PhoneGap的見解

今天的大會上,2位演講者把PhoneGap吹得至關牛。可是其實真正用過的人才能知道,PhoneGap仍是有至關多的問題的。至少我知道在網易就有一個使用PhoneGap而失敗的項目,因此我認爲PhoneGap仍是有它至關大的侷限性的。

我認爲PhoneGap有如下3大問題:

  1. 首先,PhoneGap的編程語言實際上是javascript,這對於非前端工做者來講,其實學習起來和學習原生的objective-C或Java編程語言難度差很少,並且因爲歷史緣由,javascript語言自己的問題比其它語言都多。要想精通javascript,至關不易。

  2. 而後,PhoneGap的目標是方便地建立跨平臺的應用。可是其實蘋果和google都發布了本身的人機交互指南。有些狀況下,蘋果的程序和android程序有着不一樣的交互原則的。象有道雲筆記的iPhone版android版,就有着徹底不一樣的界面和交互。使用PhoneGap就意味着你的程序在UI和交互上,既不象原生的iphone程序,又不象原生的android程序。

  3. 最後,性能問題。Javascript終究沒法和原生的程序比運行效率,這一點在當你要作一些動畫效果的時候,就能顯現得很明顯。

固然,PhoneGap的優點也很明顯,若是你是作圖書類,查詢類,小工具類應用的話,這些應用UI交互不復雜,也不佔用很高的cpu資源,PhoneGap將很好地發揮出它的優點。對於這類應用:

  1. 你只須要編寫一次,則能夠同時完成iOS, android, windows phone等版本的開發。

  2. 若是改動不大,只是內容升級,那它升級時只須要更新相應的js文件,而不須要提交審覈,而通常正常提交蘋果的app store審覈的話,經常須要一週時間。

因此PhoneGap不是萬能的,但也不是沒有用,它有它擅長的領域,一切都看你是否合理地使用它。

最後,推薦PhoneGap中國網站 ,在這裏,你能夠找到爲數很少的中文資料。

對js的感想

如今前端工程師至關牛逼啊。前端工程師不但能夠寫前端網頁,還能夠用Flex寫桌面端程序,能夠用nodejs寫server端程序,能夠用PhoneGap寫移動端程序,這一切,都是基於javascript語言的,還有最新出的windows 8,原生支持用js來寫Metro程序,世界已經沒法阻止前端工程師了。

相關文章
相關標籤/搜索