關於UIWebView和PhoneGap的總結
轉自: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 |
|
接着,我們需要讓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 |
|
具體讓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 |
|
在這裏,可能有些人說,通過改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大問題:
-
首先,PhoneGap的編程語言其實是javascript,這對於非前端工作者來說,其實學習起來和學習原生的objective-C或Java編程語言難度差不多,而且由於歷史原因,javascript語言本身的問題比其它語言都多。要想精通javascript,相當不易。
-
然後,PhoneGap的目標是方便地創建跨平臺的應用。但是其實蘋果和google都發布了自己的人機交互指南。有些情況下,蘋果的程序和android程序有着不同的交互原則的。象有道雲筆記的iPhone版和android版,就有着完全不同的界面和交互。使用PhoneGap就意味着你的程序在UI和交互上,既不象原生的iphone程序,又不象原生的android程序。
-
最後,性能問題。Javascript終究無法和原生的程序比運行效率,這一點在當你要做一些動畫效果的時候,就能顯現得很明顯。
當然,PhoneGap的優勢也很明顯,如果你是做圖書類,查詢類,小工具類應用的話,這些應用UI交互不復雜,也不佔用很高的cpu資源,PhoneGap將很好地發揮出它的優勢。對於這類應用:
-
你只需要編寫一次,則可以同時完成iOS, android, windows phone等版本的開發。
-
如果改動不大,只是內容升級,那它升級時只需要更新相應的js文件,而不需要提交審覈,而一般正常提交蘋果的app store審覈的話,常常需要一週時間。
所以PhoneGap不是萬能的,但也不是沒有用,它有它擅長的領域,一切都看你是否合理地使用它。
最後,推薦PhoneGap中國網站 ,在這裏,你可以找到爲數不多的中文資料。
對js的感想
現在前端工程師相當牛逼啊。前端工程師不但可以寫前端網頁,還可以用Flex寫桌面端程序,可以用nodejs寫server端程序,可以用PhoneGap寫移動端程序,這一切,都是基於javascript語言的,還有最新出的windows 8,原生支持用js來寫Metro程序,世界已經無法阻止前端工程師了。