關於UIWebView的總結

關於UIWebView的總結

前言javascript

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

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

機制

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

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 代碼的相互調用。node

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

// 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 方法中截獲此次請求。git

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

// 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 方法中,只能截獲後面那次請求,前一次請求因爲很快被替換掉,因此被忽略掉了。web

我也專門去 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 程序,世界已經沒法阻止前端工程師了。

相關文章
相關標籤/搜索