Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 開發入門 >> 開發移動 Web Ajax 應用(一)

開發移動 Web Ajax 應用(一)

編輯:開發入門

開始

本文闡述如何開發一個運行在 Apple iPhone 以及基於 Android 的智能手機上的移動 Web 應用。 開發這些設備上的移動 Web 應用,我們不能使用平常的桌面浏覽器,至少不能完全只使用桌面浏覽器。 我們還需要模擬器或實際的設備。對於 iPhone 來說,我們需要使用 iPhone 模擬器。它是 iPhone SDK 的一部分。本文所使用的是 iPhone SDK 3.1。類似地,我們也需要使用 Android SDK。它包含了一個 Android Virtual Device 管理器,這個管理器可以用來創建運行各個 Android 版本的 Android 模擬器。 其中本文使用的是 android SDK 2.1。本文所用到的大部分代碼是 Javascript 代碼,以及一些 Html 和 CSS。 此外,應用還有一個使用 Java™ 實現的服務器端。這並不是強制性要求的,和其它的 Web 應用一樣, 我們可以自己選擇使用任意的服務器端技術。我們需要使用 Java 1.6 來運行本文所開發的應用。 另外我們還需要使用 Jersey,它是 JAX-RS 的參考實現,以及所有相關的 Java Archive (JAR) 文件(見 參考資料)。


手機浏覽器與 WebKit

移動設備很多年就有 Web 浏覽器了。然而,由於 Web 開發人員必須處理跨浏覽器支持問題, 因此開發浏覽器應用一直是很困難的。開發人員需要花費很多的時間來實現 HTML、Javascript 和 CSS 在不同版本的浏覽器上運行完全一致,如 Internet Explorer、Mozilla Firefox、Safari 等。 而桌面浏覽器的問題幾乎與手機浏覽器中的問題如出一轍。不同版本的手機浏覽器也是多得驚人。 每個設備制造商都擁有各自的浏覽器,甚至來自相同廠商的設備在操作系統、屏幕大小等方面都有很大的差別。 有些浏覽器只支持 WAP,而有一些則支持部分的 HTML,還有一些完全支持 Html,但不支持 JavaScript。

幸好,現在情況已經大不相同了。到 2010 年 1 月,美國有超過 80% 的移動互聯網流量都是通過 iPhone 或 android 手機產生的。這兩種操作系統不僅都是使用 WebKit 進行 HTML/CSS 渲染, 而且它們都是一樣積極地使用 Html 5 標准所采用的 JavaScript 引擎。沒錯。 移動領域的主流浏覽器現在都使用了開放標准。這是 Web 開發人員所遇到的最好時機。

浏覽器之間還是存在差別的,即使是不同版本的 iPhone 和 Android 也不例外。 其中 Android 浏覽器差別最大。在 2.0 之前版本的 Android 上,Android 浏覽器使用的是私有的 Google Gears 技術。雖然 Gears 有很多優秀的創新技術,現在已經包含在 HTML 5 標准中了。 然而,這意味著在很長的一段時間裡,android 浏覽器是不支持其中一些 HTML 5 標准的, 但是我們還是能夠使用 Gears 來實現一部分功能。本文的所有代碼都是基於 Html 5 標准並且是可以正常運行在 android 2.0+ 或 iPhone 3.0+ 上的。 既然我們已經擁有了這些現代的基於 WebKit 的浏覽器,接下來讓我們來看看一些這些設備上的 AJax。

手機浏覽器上的 AJax

像桌面 Web 應用一樣,在移動 Web 應用上創建引人注目的用戶體驗的關鍵通常就是使用 Ajax。當然, 用戶體驗並不是使用 Ajax 的唯一原因;其中還可能涉及到速度和效率的原因。而後者恰恰是在移動 Web 應用上使用 Ajax 的更重要的原因所在,因為移動網絡的延遲更大,而浏覽器本身也受到處理器速度、 內存和緩存大小的限制。幸好,由於只需要關注於基於標准的浏覽器,因此 AJax 則恰好是許多因此變得更簡單的技術之一。在詳細討論這個問題之前, 讓我們先快速地了解一下本文所開發的應用所使用的後台服務器。

在開始之前,我們需要下載必要的 JAR 文件,其中包括 Jersey、Xerces、Rome 和 Google App Engine SDK (見 參考資料)。然後將它們安裝到下面的文件夾中:WebKitBlog>war>WEB-INF>lib。 我們可以從這裡 下載 應用的其它源代碼。

WebKit 博客

本文的移動 Web 應用是一個簡單的閱讀移動 Web 開發新聞的應用。雖然目前它只是簡單地從官方 WebKit 博客抓取 RSS 源,但是它可以經過簡單地修改來收集多個 RSS 源。這個應用是一個普通的 Java Web 應用,它可以部署到任何一個 Java 應用服務器上。所有代碼見清單 1。


清單 1. Feed 類

				
@Path("/feed")
public class Feed {
    String surfinSafari = "http://webkit.org/blog/feed/";
    
    @GET @Produces("application/JSon")
    public News getNews(@DefaultValue("0") @QueryParam("after") long after) 
    throws Exception{
      URL feedUrl = new URL(surfinSafari);
      SyndFeedInput input = new SyndFeedInput();
        SyndFeed feed = input.build(new XMLReader(feedUrl));
        List<Item> entries = new ArrayList<Item>(feed.getEntries().size());
        for (Object obj : feed.getEntries()){
            SyndEntry entry = (SyndEntry) obj;
            if (entry.getPublishedDate().getTime() > after){
                Item item = new Item(entry.getTitle(), entry.getLink(), 
                            entry.getDescription().getValue(), 
                            entry.getPublishedDate().getTime());
                entries.add(item);
            }
        }
        return new News(feed.getTitle(), entrIEs);
    }
}

 

這個類使用 Java 的 JAX-RS 創建一個 RESTful 服務。@Path 注釋表示了服務的終端,即服務的相對 URL 是 /feed。@GET 表示這個服務支持 HTTP GET@Produces 聲明這個服務將生成 JSON 格式的數據。這是一個簡單的以 JSON 格式序列化數據的方法。 方法getNews 接收一個名為 after 的參數, 即獲取一個特定日期之後的實體。這裡也使用 JAX-RS 注釋來將參數 after 綁定到查詢字符參數 after 上。如果沒有賦值,它會使用默認值 0。

到這裡,我只闡述了 清單 1 中創建服務尋址和數據序列化的代碼所用到的 JAX-RS 注釋。該方法的主體實際上大部分依賴於處理 RSS 的 Rome 包。它只是下載最新的 RSS 源, 然後將它轉換成我們應用所需要的數據,這裡的數據就是 Item 和 News 這兩類。其中唯一復雜的部分是文章的發表日期被轉化為一個 long 值,並用作一個 ID。這是一個非常有用的 ID, 因為我們可以用它來進行排序,我們將在後面使用到。News 類如清單 2 所示。


清單 2. News 類

				
@XMLRootElement
public class News {
    private String title;
    private List<Item> entrIEs;
    // constructors, getters/setters omitted for brevity
}

 

注意 News 類使用了 JAXB 注釋 @XMLRootElement。 這個應用中不使用 XML,但是 JAX-RS 使用 JAXB 完成自動的序列化/反序列化。這個類只有一個標題屬性和一組 Item。 Item 類如清單 3 所示。


清單 3. Item 類

				
@XMLType
public class Item {
    private String title;
    private String link;
    private String description;
    private Long id;
    // constructors, getters/setters omitted for brevity
}

 

這個類包含的就是我們在 Web 應用中顯示的內容。類似於 News類, 它也使用 JAXB 注釋,這樣 JAX-RS 可以將它序列化成 JSON。這部分代碼的最後一部分是配置 Web 應用的,以使請求能指向 JAX-RS。為了達到這個目的,我們需要編輯應用的 web.XML 文件, 如清單 4 所示。


清單 4. web.XML 配置文件

				
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/XML/ns/Javaee/web-app_2_5.xsd"
    version="2.5">
    <servlet>
        <servlet-name>WebKit Blog Servlet</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.
ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>org.developerworks.mobile</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>WebKit Blog Servlet</servlet-name>
        <url-pattern>/resources/*</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    <!-- Default page to serve -->
    <welcome-file-list>
        <welcome-file>index.Html</welcome-file>
    </welcome-file-list>
    <mime-mapping>
        <extension>mf</extension>
        <mime-type>text/cache-manifest</mime-type>
    </mime-mapping>
</web-app>

 

這是最典型的 web.XML 代碼。我們可以看到 servlet 聲明,應用使用了 JAX-RS 的參考實現 Jersey。 Servlet 的初始化參數指示 Jersey 掃描 org.developerworks.mobile 包中被標記為處理服務請求的類。同時,任何映射到 /resources/ 的請求都將映射到 Jersey servlet。 這裡最後需要注意的是文件最後的 mime-mapping 部分。這是 MANIFEST 文件的 MIME 類型, 也是我後面將討論到的脫機 Web 應用的一個關鍵。既然我們已經了解了 Web 應用將使用到的後台服務, 接下來我們將了解它使用到的前台。


AJax 構建的用戶接口

清單 4 中我們可以看到應用有一個標准的 index.Html 文件。這是應用的入口點,如清單 5 所示。


清單 5. index.Html 文件

				
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html manifest="application.mf">
  <head>
    <meta http-equiv="content-type" content="text/Html; charset=UTF-8">
    <title>WebKit News</title>
    <meta name = "vIEwport" content = "width = device-width"/>
    <script type="text/Javascript" src="index.JS"></script>
  </head>
  <body onload="loadEntrIEs()">
    <h1 id="title">Recent News About WebKit</h1><img 
id="loader" src="loading.gif"/>
  </body>
</Html>

 

這是一個非常簡單的 Web 頁面,但是其中有許多值得注意的方面。首先,在文件頭部我設置了視區。 它指示浏覽器放大內容,使內容在設備上良好顯示。在 UI 代碼方面,這裡只有一個標題和表示正在加載的圖片。 剩下的部分就是 JavaScript。文件 index.JS 中的loadEntrIEs 函數會發送一個 AJax 請求加載數據。這個函數如清單 6 所示。


清單 6. loadEntrIEs 函數

				
function loadEntrIEs(){
    if (!window.JSON){
        var head = document.getElementsByTagName("head")[0];
        var jsScript = document.createElement("script");
        jsScript.setAttribute("src", "json2.js");
        JSScript.setAttribute("type","text/Javascript");
        head.appendChild(JSScript);
    }    
      var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 &&this.status == 200){
                  var theFeed = JSON.parse(this.responseText);
                  var i = 0;
                  if (theFeed.entries){
                         var len = theFeed.entries.length;
                         for (i=0;i<len;i++){
                                addEntry(theFeed.entrIEs[len - 1 -i], true);
                         }
                  }
                  var body = document.getElementsByTagName("body")[0];
                  body.removeChild($("loader"));
              }
    };
    var urlStr = "/resources/feed";
    xhr.open("GET", urlStr);
    xhr.send();        
}

 

在這個函數的開始部分,我進行了一些功能檢查。大多數浏覽器支持內置的解析和序列化 JSON 的函數。 這與 json.org 上的 JSON 庫是相同的。然而,如果沒有這個函數,我們需要在頁面上包含這個文件, 然後我們就有相同的功能了。

另外,這是非常簡單的 AJax 代碼。我們不需要擔心 Internet Explorer 不支持,所以我們可以直接創建一個 XMLHttpRequest。我們可以設置它的 onreadystatechange 屬性為一個函數,在本例中將創建一個閉包。當響應從服務器返回(readyState = 4)且處理過程沒有發生問題(status = 200 OK)時,我們就使用 JSON.parse 函數來解析 JSON 響應。對於來自源的每一個記錄執行 addEntry 函數。 這個函數為每一個記錄創建 UI 元素。如清單 7 所示。


清單 7. addEntry 函數

				
function addEntry(e, prepend){
    var eDiv = document.createElement("div");
    eDiv.setAttribute("class", "item");
    var link = document.createElement("a");
    link.setAttribute("href", e["link"]);
    var title = document.createTextNode(e["title"]);
    link.appendChild(title);
    eDiv.appendChild(link);
    var button = document.createElement("input");
    button.setAttribute("type", "button");
    button.setAttribute("value", "Show");
    button.setAttribute("id", "button" + e["id"]);
    button.setAttribute("onclick", "showDescription('" + e["id"] + "')");
    eDiv.appendChild(button);
    var dDiv = document.createElement("div");
    dDiv.setAttribute("id", e["id"]);
    dDiv.setAttribute("class", "description");
    dDiv.setAttribute("style", "display:none");
    dDiv.innerHtml = e["description"];
    eDiv.appendChild(dDiv);
    var body = document.getElementsByTagName("body")[0];
    if (!prepend && body.childNodes.length > 1){
            body.appendChild(eDiv);
    } else {
            body.insertBefore(eDiv, body.childNodes.item(2));
    }
}

 

這個函數裡執行的都是標准的 DOM 操作。唯一復雜的方面是記錄可以附加在後面或插到前面, 或者添加到頂部或底部。在前面的 清單 6 中,函數的最後一個操作是刪除正在加載的圖片。 這其中包括基本的 AJax 函數,我們可以根據用例選擇使用。然而,應用需要從服務器下載許多數據, 然後再解析。對於移動 Web 應用,我們可以有更好的方式,我們可以使用本地緩存。


使用本地存儲進行緩存

本地存儲屬於 Html 5 規范,並且得到廣泛的支持。它有一個簡單的 name - value 存儲機制, 而其中 name 和 value 都是字符串。所以將記錄保存到本地存儲是很簡單的,如清單 8 所示。


清單 8. 保存到本地存儲

				
function saveLocal(entry){
    if (!window.localStorage){
        return;
    }
    localStorage.setItem(entry["id"], JSON.stringify(entry));
}

 

同樣,這裡做了一些浏覽器功能檢查,按照 Html 5 規范,首先檢查 window 對象是否有 localStorage 屬性。 如果有,那麼我們就可以使用記錄的 id 作為存儲的鍵。 對於 value,必須使用字符串,因此我們使用 JSON.stringify 函數來將對象序列化成一個字符串。 這樣,我們只需要一個從本地存儲讀取所有數據的函數。如清單 9 所示。


清單 9. 從本地存儲加載數據

				
function loadLocal(){
    if (!window.localStorage){
        return [];
    }
    var i = 0;
    var e = {};
    var id = 0;
    var entrIEs = [];
    for (i=0; i< localStorage.length; i++){
        id = localStorage.key(i);
        e = JSON.parse(localStorage.getItem(id));
        entries[entries.length] = e;
    }
    entries.sort(function(a,b) { 
        return b.id - a.id; 
    });
    return entrIEs;
}

 

同樣,這個函數從檢查本地存儲是否可用開始。接著,它遍歷本地存儲中的所有數據。 對於存儲中的每一個值,我們再次使用JSON.parse 函數來將字符串解析成為一個對象。 接著,我們需要對記錄進行排序,因為它們從本地存儲返回的順序是不一定的。這裡進行了一個降序排序,最新記錄在最前面。 最後,我們就有了本地存儲的保存和加載函數,我們需要將它們整合到 loadEntrIEs 函數中, 如清單 10 所示。


清單 10. 添加緩存到 loadEntrIEs 函數

				
 
function loadEntrIEs(){
    // check for JSON object
    var localEntries = loadLocal();
    var newest = 0;
    if (localEntries.length > 0){
        newest = localEntries[0].id;
    }
    var i = 0;
    for (i=0;i<localEntries.length;i++){
        addEntry(localEntrIEs[i]);
    }
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 && this.status == 200){
                  var theFeed = JSON.parse(this.responseText);
                  var i = 0;
                  if (theFeed.entries){
                         var len = theFeed.entries.length;
                         for (i=0;i<len;i++){
                                addEntry(theFeed.entries[len - 1 -i], true);
                                saveLocal(theFeed.entrIEs[len - 1 -i]);
                         }
                  }
                  var body = document.getElementsByTagName("body")[0];
                  body.removeChild($("loader"));
              }
    };
    var urlStr = "/resources/feed?after=" + newest;
    xhr.open("GET", urlStr);
    xhr.send();    
}

 

這裡的主要區別是我們首先加載本地數據。接著確定其中最新的記錄,然後在我們連接服務器時使用這個參數, 服務器將只會返回不在本地存儲的記錄。最後,當我獲得新的記錄後,在回調函數中我們需要調用 saveLocal 函數來將它們保存到本地緩存中。在新的記錄存儲到本地後,現在我們就可以啟用 Web 應用的完全脫機訪問。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved