DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> HTML基礎知識 >> HTML5教程 >> HTML5移動應用開發第4章:Web Workers來加速您的移動Web應用
HTML5移動應用開發第4章:Web Workers來加速您的移動Web應用
編輯:HTML5教程     

在本文中,您將使用最新的 Web 技術開發 Web 應用程序。這裡的 大部分代碼只是 HTML、JavaScript 和 CSS — 所有 Web 開發人員的核心技術。所需的最重要的工具是用於進行測試的浏覽器。本文大部分代碼將在最新桌面浏覽器上運行,但也有一些例外,我們將在文章中進行說明。當然,您也必須在移動浏覽器上測試,為此,您需要最新的 iPhone 和 Android SDKs。本文將使用 iPhone SDK 3.1.3 和 Android SDK 2.1。本文的樣例還將使用一個代理服務器來從浏覽器訪問遠程服務。這個代理服務器是一個簡單的 Java™ servlet,但也可以使用以 PHP、Ruby 以及其他語言編寫的代理輕松替換。

移動設備上的多線程 JavaScript

對於大多數開發人員來說,多線程或並發編程並不新鮮。但是,JavaScript 並不是一種支持並發編程的語言。JavaScript 的創建者認為,對於 JavaScript 這樣旨在 Web 頁面上執行簡單任務的語言來說,並發編程容易出現問題,而且沒有必要。然而,由於 Web 頁面已經發展成為 Web 應用程序,使用 JavaScript 完成的任務的復雜程度已經大大增加,向 JavaScript 提出了與其他語言同等的要求。與此同時,使用其他支持並發編程的語言工作的開發人員經常面臨伴隨線程和 mutexes 這樣的並發原語而來的超高復雜性的困擾。實際上,最近像 Scala、Clojure 和 F# 這樣的幾種新語言已經發展,它們都有可能簡化並發性。

常用縮略詞
  • Ajax:異步 JavaScript + XML
  • API:應用程序編程接口
  • CSS:層疊樣式表
  • DOM:文檔對象模型
  • HTML:超文本標記語言
  • REST:具象狀態傳輸
  • SDK:軟件開發工具包
  • UI:用戶界面
  • URL:統一資源定位符
  • W3C:萬維網聯盟
  • XML:可擴展標記語言

Web Worker 規范不只是向 JavaScript 和 Web 浏覽器添加並發性,而且是以一種智慧的方式添加,這種方式將增加開發人員的能力,但不會向他們提供一種會導致問題的工具。 例如,多年來,桌面應用程序開發人員一直在使用多線程來支持他們的應用程序訪問多個 I/O 資源,以避免在等待這些資源時凍結 UI。然而,當這些多線程更改共享的資源(包括 UI)時,這樣的應用程序通常會出現問題,因為這種行為可能會導致應用程序凍結或崩潰。有了 Web Workers,這種情況就不會發生。衍生線程不能訪問主 UI 線程訪問的資源。事實上,衍生線程中的代碼甚至不能與主 UI 線程執行的代碼位於同一個文件中。

您甚至必須提供相應的外部文件作為構造函數的一部分,如 清單 1 所示。

這個進程使用三個資源:

  1. 在主線程上執行的 Web 頁面 JavaScript(我稱其為頁面腳本)。
  2. Worker 對象,這是用於執行 Web Worker 函數的 JavaScript 對象。
  3. 將在新衍生的線程上執行的腳本。我稱其為 Worker 腳本。

讓我們首先看看 清單 1 中的頁面腳本。


清單 1.在頁面腳本中使用一個 Web Worker

JavaScript Code復制內容到剪貼板
  1. var worker = new Worker("worker.js");  
  2. worker.onmessage = function(message){  
  3.     // do stuff  
  4. };  
  5. worker.postMessage(someDataToDoStuffWith);  

 

在 清單 1 中,您可以看到使用 Web Workers 的三個基本步驟。首先,您創建一個 Worker 對象並向它傳遞將在新線程中執行的腳本的 URL。Worker 將執行的所有代碼都必須包含在一個 Worker 腳本中,該腳本的 URL 將被傳遞到這個 Worker 的構造函數中。這個 Worker 腳本的 URL 受到浏覽器的同源策略的限制 — 它必須來自加載這個頁面的同一個域,該頁面已加載正在創建這個 Web Worker 的頁面腳本。

下一步是使用 onmessage 函數指定一個回調處理器函數。這個回調函數將在該 Worker 腳本執行後調用。message 是從該 Worker 腳本返回的數據,您可以隨意處理該消息。回調函數在主線程上執行,因此它能訪問 DOM。Worker 腳本在一個不同的線程內運行且不能訪問 DOM,因此,您需要將來自這個 Worker 腳本的數據返回主線程,在那裡,您可以安全地修改 DOM 來更新您的應用程序的 UI。這是 Web Workers 的無共享設計的關鍵特性。

清單 1 中的最後一行展示如何通過調用 Worker 的 postMessage 函數來啟動它。這裡,您傳遞一條消息(重申一下,它只是數據)給 Worker。當然,postMessage 是一個異步函數;您調用它,它就立即返回。

現在,檢查這個 Worker 腳本。清單 2 中的代碼是來自 清單 1 的 worker.js 文件的內容。


清單 2. 一個 Worker 腳本

JavaScript Code復制內容到剪貼板
  1. importScripts("utils.js");  
  2. var workerState = {};  
  3. onmessage = function(message){  
  4.      workerState = message.data;  
  5.       // do stuff with the message  
  6.     postMessage({responseXml: this.responseText});  
  7. }  

 

可以看到,這個 Worker 腳本擁有自己的 onmessage 函數。該函數在您從主線程調用 postMessage 時調用。從頁面腳本傳來的數據被傳遞到 message 對象中的 postMessage 函數。您通過檢索 message 對象的 data 屬性來訪問該數據。當您處理完 Worker 腳本中的數據時,調用 postMessage 函數將數據返回主線程。主線程也可以通過訪問它接收到的消息的 data 屬性來訪問該數據。

至此,您已經見識了 Web Workers 的這個簡單、但強大的語義。接下來,您將了解如何應用這個語義來加速移動 Web 應用程序。在此之前,有必要先討論一下設備支持。畢竟,這些是移動 Web 應用程序,且處理不同浏覽器之間的功能的區別對於移動 Web 應用程序開發很重要。

設備支持

從 Android 2.0 開始,Android 浏覽器就擁有了對 HTML 5 Web Worker 規范的全面支持。在撰寫本文之時,最新的 Android 設備(包括非常流行的 Motorola Droid)已配置了 Android 2.1。另外,此特性在運行 Maemo 操作系統的 Nokia 設備上的 Mozilla Fennec 浏覽器以及 Windows Mobile 設備上受到完全支持。這裡需要引起注意的遺漏是 iPhone。iPhone OS 3.1.3 和 3.2 版(在 iPad 上運行的 OS 的版本)並不支持 Web Workers。但是,此特性已在 Safari 上受到支持,因此,此特性在運行在 iPhone 上的 Mobile Safari 浏覽器上出現應該只是一個時間問題。鑒於 iPhone 的主導地位(尤其是在美國),最好不要依賴 Web Workers 的存在,且不要只在您檢測到它們的存在時才使用它們來增強您的移動 Web 應用程序。意識到這一點後,我們來看看如何使用 Web Workers 來加速您的移動 Web 應用程序。

使用 Workers 改善性能

智能手機浏覽器上的 Web Worker 支持很不錯,而且一直在不斷改進。這就提出了一個問題:什麼時候需要在移動 Web 應用程序中使用 Workers?答案很簡單:需要完成耗時的任務的任何時候。有些示例展示了如何將 Workers 用於執行密集的數學計算,比如計算 1 萬位數的圓周率。很可能您永遠也不需要在 Web 應用程序上執行這樣一個計算,在移動 Web 應用程序上執行這種計算的幾率則更小。但是,從遠程資源檢索數據則相當常見,這也是本文示例的關注點。

在這個示例中,您將從 eBay 檢索一個 Daily Deals(每天都在變化的交易)列表。這個交易列表包含關於每筆交易的簡短信息。更詳細的信息可以通過使用 eBay 的 Shopping API 獲取。當用戶浏覽這個交易列表選擇感興趣的商品時,您將使用 Web Workers 來預取這個附加信息。要從您的 Web 應用程序訪問所有這些 eBay 數據,您需要通過使用一個泛型代理(generic proxy)來處理浏覽器的同源策略。一個簡單的 Java servlet 將用於這個代理,它包含在本文的代碼中,但不在這裡單獨展示。相反,我們將把注意力集中在處理 Web Workers 的代碼上。清單 3 展示了這個交易應用程序的基本 HTML 頁面。

清單 3. 交易應用程序 HTML

XML/HTML Code復制內容到剪貼板
  1. <!DOCTYPE HTML>  
  2. <html>  
  3.   <head>  
  4.     <meta http-equiv="content-type" content="text/html; charset=UTF-8">  
  5.     <meta name = "viewport" content = "width = device-width">  
  6.     <title>Worker Deals</title>  
  7.     <script type="text/javascript" src="common.js"></script>  
  8.   </head>  
  9.   <body onload="loadDeals()">  
  10.     <h1>Deals</h1>  
  11.     <ol id="deals">  
  12.     </ol>  
  13.     <h2>More Deals</h2>  
  14.     <ul id="moreDeals">  
  15.     </ul>  
  16.   </body>  
  17. </html>  

 

可以看出,這是一段非常簡單的 HTML;它只是一個 shell。您使用 JavaScript 檢索數據並生成 UI。這是移動 Web 應用程序的優化設計,因為它允許將所有代碼和靜態標記緩存到設備上,用戶只需等待來自服務器的數據。注意,在 清單 3 中,一旦那個 body 加載,您就調用 loadDeals 函數,在那裡,您將加載 清單 4 中的應用程序的初始數據。


清單 4. loadDeals 函數

JavaScript Code復制內容到剪貼板
  1. var deals = [];  
  2. var sections = [];  
  3. var dealDetails = {};  
  4. var dealsUrl = "http://deals.ebay.com/feeds/xml";  
  5. function loadDeals(){  
  6.     var xhr = new XMLHttpRequest();  
  7.     xhr.onreadystatechange = function(){  
  8.         if (this.readyState == 4 && this.status == 200){  
  9.                var i = 0;  
  10.                var j = 0;  
  11.                var dealsXml = this.responseXML.firstChild;  
  12.                var childNode = {};  
  13.                for (i=0; i< dealsXml.childNodes.length;i++){  
  14.                    childNode = dealsXml.childNodes.item(i);  
  15.                    switch(childNode.localName){  
  16.                    case 'Item':   
  17.                        deals.push(parseDeal(childNode));  
  18.                        break;  
  19.                    case "MoreDeals":  
  20.                        for (j=0;j<childNode.childNodes.length;j++){  
  21.                            var sectionXml= childNode.childNodes.item(j);  
  22.                            if (sectionXml && sectionXml.hasChildNodes()){  
  23.                                sections.push(parseSection(sectionXml));  
  24.                            }  
  25.                        }  
  26.                        break;      
  27.                    default:  
  28.                        break;  
  29.                    }  
  30.                }  
  31.                deals.forEach(function(deal){  
  32.                    var entry = createDealUi(deal);  
  33.                    $("deals").appendChild(entry);  
  34.                });  
  35.                loadDetails(deals);  
  36.                sections.forEach(function(section){  
  37.                    var ui = createSectionUi(section);  
  38.                    $("moreDeals").appendChild(ui);  
  39.                    loadDetails(section.deals);  
  40.                });  
  41.         }  
  42.     };  
  43.     xhr.open("GET", "proxy?url=" + escape(dealsUrl));  
  44.     xhr.send(null);  
  45. }  

 

清單 4 展示了 loadDeals 函數,以及應用程序中使用的全局變量。您使用了一個 deals 數組和一個 sections 數組。它們是相關交易的附加組(例如,Deals under $10)。還有一個名為 dealDetails 的映射,其鍵是 Item IDs(來自於交易數據),其值是從 eBay Shopping API 獲取的詳細信息。

您首先調用一個代理,該代理又將調用 eBay Daily Deals REST API。這將把交易列表作為一個 XML 文檔提供給您。您解析用於進行 Ajax 調用的 XMLHttpRequest 對象的 onreadystatechange 函數中的文檔。您還使用其他兩個函數,parseDeal 和 parseSection,來將 XML 節點解析為更易於使用的 JavaScript 對象。這些函數可以在可下載的代碼樣例(參見 下載 部分)中找到,但由於它們只是令人厭煩的 XML 解析函數,因此我在這裡沒有包括它們。最後,在解析了 XML 後,您還使用了另外兩個函數,createDealUi 和createSectionUi,來修改 DOM。此時,這個 UI 如 圖 1 所示。


圖 1. Mobile Deals UI
帶有樣例交易的 Mobile Deals UI 的屏幕截圖,每個交易都有一個 Show Details 按鈕 

如果您返回 清單 4,就會注意到在加載主交易之後,您對這些交易的每個部分都調用了 loadDetails 函數。在這個函數中,您通過使用 eBay Shopping API 加載每個交易的附加細節 — 但前提是浏覽器支持 Web Workers。清單 5 展示了 loadDetails 函數。


清單 5. 預取交易細節

JavaScript Code復制內容到剪貼板
  1. function loadDetails(items){  
  2.     if (!!window.Worker){  
  3.         items.forEach(function(item){  
  4.             var xmlStr = null;  
  5.             if (window.localStorage){  
  6.                 xmlStr = localStorage.getItem(item.itemId);  
  7.             }  
  8.             if (xmlStr){  
  9.                 var itemDetails = parseFromXml(xmlStr);  
  10.                 dealDetails[itemDetails.id] = itemDetails;  
  11.             } else {  
  12.                 var worker = new Worker("details.js");  
  13.                 worker.onmessage = function(message){  
  14.                     var responseXmlStr =message.data.responseXml;  
  15.                     var itemDetails=parseFromXml(responseXmlStr);  
  16.                     if (window.localStorage){  
  17.                         localStorage.setItem(  
  18.                                         itemDetails.id, responseXmlStr);  
  19.                     }  
  20.                     dealDetails[itemDetails.id] = itemDetails;  
  21.                 };  
  22.                     worker.postMessage(item.itemId);  
  23.             }  
  24.         });  
  25.     }  
  26. }  

 

在 loadDetails 中,您首先檢查全局作用域(window 對象)中的 Worker 函數。如果該函數不在那裡,那麼無需做任何事。反之,您首先檢查 XML 的 localStorage 以獲取這個交易的細節。這是移動 Web 應用程序常用的本地緩存策略,本系列第 2 部分(參見 參考資料 部分的鏈接)詳細介紹過這種策略。

如果 XML 位於本地,那麼您在 parseFromXml 函數中解析它並將交易細節添加到 dealDetails 對象。反之,則衍生一個 Web Worker 並使用 postMessage 向其發送 Item ID。當這個 Worker 檢索到數據並將數據發布回主線程後,您解析 XML,將結果添加到dealDetails,然後將 XML 存儲到 localStorage 中。清單 6 展示了這個 Worker 腳本:details.js。


清單 6. 交易細節 Worker 腳本

JavaScript Code復制內容到剪貼板
  1. importScripts("common.js");  
  2. onmessage = function(message){  
  3.     var itemId = message.data;  
  4.     var xhr = new XMLHttpRequest();  
  5.     xhr.onreadystatechange = function(){  
  6.         if (this.readyState == 4 && this.status == 200){  
  7.             postMessage({responseXml: this.responseText});  
  8.         }  
  9.     };  
  10.     var urlStr = generateUrl(itemId);  
  11.     xhr.open("GET", "proxy?url=" + escape(urlStr));  
  12.     xhr.send(null);  
  13. }  

這個 Worker 腳本非常簡單。您使用 Ajax 調用代理,該代理又調用 eBay Shopping API。當您收到來自代理的 XML 後,使用一個 JavaScript 對象文字(object literal)將其發送回主線程。注意,即使您能夠使用來自一個 Worker 的 XMLHttpRequest,但所有信息都將返回它的 responseText 屬性,而不是它的 responseXml 屬性。這是因為這個 Worker 腳本范圍內沒有 JavaScript DOM 解析器。注意,generateUrl 函數來自 common.js 文件(見 清單 7)。您使用 importScripts 函數導入 common.js 文件。


清單 7. Worker 導入的腳本

JavaScript Code復制內容到剪貼板
  1. function generateUrl(itemId){  
  2.     var appId = "YOUR APP ID GOES HERE";  
  3.     return "http://open.api.ebay.com/shopping?callname=GetSingleItem&"+  
  4.         "responseencoding=XML&appid=" + appId + "&siteid=0&version=665"  
  5.             +"&ItemID=" + itemId;  
  6. }  

 

現在,您已經知道如何(為支持 Web Workers 的浏覽器)填充交易細節,我們返回 圖 1 研究一下如何在應用程序中使用這種方法。注意,每筆交易旁邊都有一個 Show Details 按鈕,單擊該按鈕修改這個 UI,如 圖 2 所示。


圖 2. 顯示的交易細節
顯示交易細節的屏幕截圖,包含兩個 Golla 小包(MP3、移動電話和相機)的產品說明、圖片和價格 

這個 UI 將在您調用 showDetails 函數時顯示。清單 8 展示了這個函數。


清單 8. showDetails 函數

JavaScript Code復制內容到剪貼板
  1. function showDetails(id){  
  2.     var el = $(id);  
  3.     if (el.style.display == "block"){  
  4.         el.style.display = "none";  
  5.     } else {  
  6.         el.style.display = "block";  
  7.         if (!el.innerHTML){  
  8.             var details = dealDetails[id];  
  9.             if (details){  
  10.                 var ui = createDetailUi(details);  
  11.                 el.appendChild(ui);  
  12.             } else {  
  13.                 var itemId = id;  
  14.                 var xhr = new XMLHttpRequest();  
  15.                 xhr.onreadystatechange = function(){  
  16.                     if (this.readyState == 4 &&   
  17.                                       this.status == 200){  
  18.                         var itemDetails =   
  19.                                         parseFromXml(this.responseText);  
  20.                         if (window.localStorage){  
  21.                             localStorage.setItem(  
  22.                                               itemDetails.id,   
  23.                                               this.responseText);  
  24.                         }  
  25.                         dealDetails[id] = itemDetails;  
  26.                         var ui = createDetailUi(itemDetails);  
  27.                         el.appendChild(ui);  
  28.                     }  
  29.                 };  
  30.                 var urlStr = generateUrl(id);  
  31.                 xhr.open("GET", "proxy?url=" + escape(urlStr));  
  32.                 xhr.send(null);                          
  33.             }  
  34.         }  
  35.     }  
  36. }  

 

您收到了即將顯示的交易的 ID 並切換是否顯示它。當該函數第一次調用時,它將檢查細節是否已經存儲到 dealDetails 映射中。如果浏覽器支持 Web Workers,那麼這些細節已經存儲且它的 UI 已經創建並添加到 DOM 中。如果這些細節還沒有加載,或者,如果浏覽器不支持 Workers,那麼您需要執行一個 Ajax 調用來加載此數據。這就是這個應用程序無論在有無 Workers 時都同樣能正常工作的原因。這意味著,如果 Workers 受到支持,那麼數據就已被加載且 UI 將立即響應。如果沒有 Workers,UI 仍將加載,只是需要花費幾秒鐘時間。

結束語

對於 Web 開發人員來說,Web Workers 聽起來就像一種外來的新技術。但是,如本文所述,它們是非常實用的應用程序。這對於移動 Web 應用程序來說尤其正確。這些 Workers 可用於預取數據或執行其他預先操作,從而提供一個更加實時的 UI。這對於需要通過網速可能較慢的網絡加載數據的移動 Web 應用程序來說尤其正確。結合使用這種技術和緩存策略,您的應用程序的快捷反應將使您的用戶感到驚喜!

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved