關於UIWebView和PhoneGap的總結

關於UIWebView和PhoneGap的總結

MAR 24TH, 2012

轉自:http://blog.devtang.com/blog/2012/03/24/talk-about-uiwebview-and-phonegap/

前言

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

正好我們在做有道雲筆記的iPad版,因爲我們也是使用UIWebView來展示筆記內容,所以也需要做js與native代碼相互調用的事情。所以在這兒順便總結一下UIWebView在使用上的細節,以及談談我對PhoneGap的看法。

機制

首先我們需要讓UIWebView加載本地HTML。使用如下代碼完成:

1
2
3
4
5
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];

接着,我們需要讓js能夠調用native端。iOS SDK並沒有原生提供js調用native代碼的API。但是UIWebView的一個delegate方法使我們可以做到讓js需要調用時,通知native。在native執行完相應調用後,可以用stringByEvaluatingJavaScriptFromString方法,將執行結果返回給js。這樣,就實現了js與native代碼的相互調用。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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方法中截獲這次請求。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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方法中,只能截獲後面那次請求,前一次請求由於很快被替換掉,所以被忽略掉了。

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

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

參數的傳遞

以上的示例代碼爲了講清楚機制,所以只是示例了最簡單的相互調用。但實際上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程序,世界已經無法阻止前端工程師了。

 Mar 24th, 2012  iOSjavascript

原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章