Hybrid App經驗解讀 一


鄭昀編纂 關鍵詞:Hybrid,Zepto,Fastclick,Backbone,sui,SPA,pushState,跨域,CORS
  1. click 事件還是 tap 事件?
  2. Zepto 的 show/hide 有時不靠譜
  3. Android 下的跨域問題
  4. pushState 調用失敗也屬於跨域問題

   內部做 Hybrid App 開發歷程也不短了,楊海波、潘軍和劉勤紅與前端組、App 組一起總結了不少寶貴的經驗教訓,逐步形成我們自己的標准打法。下面選了幾個經驗點(坑)做進一步解讀。
 
0x00,click 事件還是 tap 事件?
300 毫秒點擊延遲的來龍去脈一文所言『盡管蘋果公司創造的雙擊縮放行為,是一種在移動設備上訪問桌面端站點的不錯的解決方案,但隨之引入的 300 毫秒點擊延遲也成為了移動端網站讓用戶感覺卡頓的罪魁禍首之一』,此文把原因和對策講得清楚透徹,此處鄭昀不再贅述,感興趣的同學自習一下。
 
那么,我們看到,時至今日,有三種應對措施:
  1. 對於無需縮放的頁面,禁用雙擊縮放功能,
  2. 引入 Zepto 框架的 tap 事件
    • Zepto 趁着 JQuery 在移動互聯網市場歷史包袱重的機會,搞了一套輕量級類 JQuery 框架的代碼,核心代碼千行,從而迅速成為移動端 DOM 操作庫的首選。
    • Zepto 提供了一個 touch 庫,在載入 Zepto 后為 document 綁定 touchstart、touchmove、touchend 事件,根據手指 x、y 值的位置,判斷方向從而觸發 tap、doubleTap、swipeLeft 等事件。
    • 原本沒有 tap 事件,Zepto tap 是用 touchstart 和 touchend 模擬出來的。
  3. 引入 Fastclick 的 click 事件
    • FastClick 是 FT Labs 專門為解決移動端瀏覽器 300 毫秒點擊延遲問題所開發的一個輕量級的庫。簡而言之,FastClick 在檢測到 touchend 事件的時候,會通過 DOM 自定義事件立即觸發一個模擬click 事件,並把瀏覽器在 300 毫秒之后真正觸發的 click 事件阻止掉。
 
但我們在實踐過程中,也領教了著名的  Zepto tap 『點透』坑
對此,yexiaochai  分析道
1,一旦引入 touch 庫便會在全局綁定事件,每次點擊皆會觸發無意義的 tap 事件 ;
2,zepto 為了實現 doubleTap 等功能,2B 地在 touchend 上設置了一個 setTimeout,然后整個世界都充滿了翔了。
由於 setTimeout 的拋出主干流程,導致其 event 參數失效,這個時候就算在 tap 事件函數中執行 e.preventDefault() 或者什么都是無效的,這是導致 zepto tap“點透”的罪魁禍首。
點透的效果可以看  http://www.cnblogs.com/lilyimage/p/3740668.html 所述。總之,有人 建議如果你還打算繼續用 Zepto,那么它的 tap 事件已經沒用了,那你可以自己 build 一個無 touch 模塊的 Zepto,以便減小文件大小並提高運行效率。
Fastclick 是 怎么做的呢?
Fastclick 將事件綁定到你傳入的元素 上,在 touchstart 和 touchend 后(會手動獲取當前點擊 el),如果是類 click 事件便手動觸發 dom 元素的 click 事件。所以 click 事件在 touchend 便被觸發,於是整個響應速度就起來了。這里雖然使用了 touch 事件,但 touch 事件是綁定到了具體 dom 而不是 document 上 ,所以 e.preventDefault 是有效的,我們可以阻止冒泡,也可以阻止瀏覽器默認事件。
 
最終我們采用 Fastclick 的 click 事件來規避點擊延遲響應。
 
0x01,Zepto 的 show/hide 有時不靠譜
在 Hybrid App 開發初期,我們引入了 Zepto 的 fx_methods 模塊,來控制元素的顯示與隱藏:
fx_methods   Animated showhidetoggle, and fade*() methods.
但總是出現一些莫名奇妙的問題,如元素不能正常顯示,元素的定位發生變化等,非常不好定位,最終前端同學發現是 Zepto 的 show/hide 造成的。
對此,我們可以找到 2012 年的一個 issue 佐證:
When you add fx_methods to your Zepto, the show() and hide() methods are overriden with methods that also call animate. 
Even in the case of undefined speed (normal show), animate still adds many CSS properties to the shown/hidden nodes.
These unexpected CSS rules can interfere with the page CSS (in my case, it has changed the positioning of my child fixed DIVs).
即,她認為,即使是簡單的顯示,fx_methods.js 的 show/hide 也會加入一些額外的 CSS,以至於影響整個頁面樣式,而且還會亂彈琴地改變透明度,重置為 1.0。samwu 在 2013 年也 講過類似問題
所以,我們有兩種選擇:
  1. 繼續引入 Zepto 的 fx_methods,但按 madrobby 所說,沒有定義 speed 時,僅調用原生的 show;
    • 從 Zepto 官方的  改為 
  2. 如 0x00 和本小節所描述的問題,不要引入 Zepto 的 touch 和 fx_methods 模塊
    • 只引入 zepto+event+ajax+form+ie 基本模塊
 
0x02,Android 下的跨域問題
在 Hyrbid App 里,鄭昀要求采用 Template+Data=HTML 模式在手機客戶端本地渲染出所需的頁面。
Template(HTML5 模板文件+JS+CSS+Images)既可以提前打包到 App 安裝包里,也可以從 CDN(如無,則溯源到靜態文件服務器)拉到客戶端里並存儲在本地。
Data 則是通過模板文件里的 JavaScript 腳本,從遠端拉取 JSON 格式的數據包。
那么,這里存在一個跨域問題:
  • Android 的 WebView 通過 file:// 協議加載本地的 HTML5 模板文件。本地文件的 JS 向遠端發送 AJAX 請求,受到同源策略的限制,譬如你可能會看到“XMLHttpRequest cannot load file://…… Origin null is not allowed by Access-Control-Allow-Origin.”的錯誤提示,即當你的 URI 是 file:// 時,源域名(origin)為空。
    • 借用 difcareer 的評論:Web 應用程序能且只能使用 XMLHttpRequest 對象向其加載的源域名發起請求,而不能向任何其他域名發起請求,但 HTML5 允許 AJAX 跨域向其他域名發起請求,但是不能獲取服務器端響應
  • iOS 下無此跨域問題。
 
對此,大致有四種應對措施:
  • 如果是 GET 請求:
    1. 可以通過 JSONP 來解決跨域問題;
  • 如果是 POST 請求:
  1. 由 App 原生代碼發起網絡操作,成功后回調 JS。
  2. 利用 HTML5 的“Cross-Origin Resource Sharing(CORS,跨域資源共享)”新特性,在服務器端響應頭里設置:Access-Control-Allow-Origin 頭域;
    • 不限制來源,Access-Control-Allow-Origin: *
    • 限制來源域,Access-Control-Allow-Origin: http://xxx.com
    • 具體設置參考 http://enable-cors.org/
    • 這需要瀏覽器支持;
    • SmdCn 提及,『在某些版本的 Android 瀏覽器中,因為緩存的原因,第一次 CORS 正常,但第二次會失效。對此,可以通過在響應頭中增加 Cache-Control: no-cache 來解決』。
  3. 設置 Android 的 WebView 如下 settings 參數為 True:

    if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.JELLY_BEAN){

        webView.getSettings().setAllowUniversalAccessFromFileURLs(true);

    }

    由此允許通過  file url 加載的 Javascript 可以訪問其他的源,包括其他的文件和 http/https 等源。這個設置在 JELLY_BEAN 以前的版本默認是允許,在 JELLY_BEAN 及以后的版本中默認是禁止的。
    According to the documentation for setAllowFileAccessFromFileURLs and setAllowUniversalAccessFromFileURLs, they used to be set to true for ICS and older. Starting in JellyBean they turned it off. If you are trying to load a local JS file from a local HTML page, then you'll need to enable these.  – Steven
 
我們是既會在 Native API 里封裝了一個接口 sendpostmsg,讓 JS 調用(但也因此導致『 除了 POST 請求,其他都可以在 PC 瀏覽器調試』),也會使用 setAllowUniversalAccessFromFileURLs 方法。
 
0x03,pushState 調用失敗也屬於跨域問題
我們需要使用 HTML5 的特性 history.pushState,手動插入歷史記錄和修改地址欄,這樣雖然地址欄被修改了,但並不觸發網頁跳轉。
同上節,本地文件的 JS 調用 history.pushState 也會遇到跨域問題,報錯如下圖所示:
原因仍是當你的 URI 是 file:// 時,源域名(origin)為空。
由於我們使用了  SUI,繞不開這個問題。所以,最終還是得調用 webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
徹底解決各種跨域問題。
 
參考資源:
2,2015,freebuf,鳶尾, 同源策略詳解及繞過(Part1)
4,2012,csdn, CORS(跨域資源共享)簡介
6,2011,sof, WebView Javascript cross domain from a local HTML file
7,2014,龔廣, WebView跨源攻擊分析
歡迎訂閱我的微信訂閱號『老兵筆記』,請掃描二維碼關注:
老兵筆記訂閱號二維碼
轉載時請注明“轉載自旁觀者-博客園”或者給出本文的原始鏈接。
-EOF-


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM