2010年12月15日 星期三

WURFL開發

現在智慧手機的品牌、型號多到不行,每個月推陳出新,作業系統、瀏覽器、螢幕大小也都不盡相同!如果要寫個在可以在手機上瀏覽的網頁,可說是很難。一套衣服怎麼能讓不同身材的人穿呢~

通常能想到的就是偵測每個request的User-Agent,依據瀏覽器版本調整畫面大小或元素等,通常User-Agent大概像是這樣"Nokia6600/1.0 (4.03.24) SymbianOS/6.1 Series60/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.0"。

User-Agent會記載手機裝置型號、作業系統、瀏覽器版本等等資訊,可說非常方便,但現在手機出新已是家常便飯,我們怎麼去判斷呢?又要怎麼掌握全世界所有的行動裝置型號呢?

WURFL就是在做這個事的,WURFL的全名是"Wireless Universal Resource File",這是一個Open Source Project,主旨在於透過社群的力量將全世界所有的行動裝置都收集在一起,可以先試試http://wurflpro.com/(如下圖),在User-Agent輸入Apple按下Search即可看到Apple相關的設定檔!



接下來就要試寫一個最簡單的範例:

首先要有WURFL.xml,這個XML即記錄所有手機的資訊,大概說明一下內容的格式:

<?xml version="1.0" encoding="UTF-8"?>
  <wurfl>
   <version>...</version><!--說明WURFL的版本資訊-->
   <devices><!--包含所有的設備資訊,devices包含了上千個device,而且持續成長中-->
    <device user_agent="Nokia3100" fall_back="nokia_generic_series40" id="nokia_3100_ver1">
    <!--指出一個裝置的資訊,user_agent為裝置型號,id指出該裝置在WURFL的ID號碼,fall_back為此裝置的上一層裝置的ID號碼-->
    <!--在WURFL中的屬性是有繼承概念的,如果在當前設找不到某個屬性,就往所繼承的裝置尋找,直到找到或到最上一層的根-->
     <group id="product_info"><!--每個屬性都會按照類別分組,一個device下可以有很多的group-->
      <capability name="model_name" value="3100"/>
<!--capability代表一個真正的屬性,每個Group可以擁有很多的capability-->
      ...
     </group>
     ...
    </device>
    ...
   </devices>
  </wurfl>

開始前要先準備幾樣東西:
  1. 下載WURFL.zip,下載後不用解壓縮,因為在執行時期WURFL的lib會自行處理。
  2. 下載wurfl-1.2.jar,等下會用到的Lib。
  3. 準備backport-util-concurrent.jar、commons-collections-3.2.1.jar、commons-lang-2.5.jar、commons-logging-1.1.1.jar。
範例實作是參考NEW WURFL Java API

建立一個個Web Project後,加入一個Servlet,此Servlet程式碼如下:


package net.sourceforge.wurfl.core.example;

import java.io.IOException;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sourceforge.wurfl.core.CapabilityNotDefinedException;
import net.sourceforge.wurfl.core.Device;
import net.sourceforge.wurfl.core.MarkUp;
import net.sourceforge.wurfl.core.WURFLHolder;
import net.sourceforge.wurfl.core.WURFLManager;

/**
* Servlet implementation class HelloWorld
*/
public class HelloWorld extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* 此範例不會以下的檔案...
*/

private static final String XHTML_ADV = "xhtmladv.jsp";
private static final String XHTML_SIMPLE = "xhtmlmp.jsp";
private static final String CHTML = "chtml.jsp";
private static final String WML = "wml.jsp";

// private final Log log = LogFactory.getLog(getClass());

protected void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

//取得WURFLHolder..
WURFLHolder wurflHolder = (WURFLHolder) getServletContext()
.getAttribute("net.sourceforge.wurfl.core.WURFLHolder");

//從WURFUL取得Manager...
WURFLManager wurfl = wurflHolder.getWURFLManager();

//從Manager取得Device資訊....
Device device = wurfl.getDeviceForRequest(request);


System.out.println("Device: " + device.getId());

//把Device所有屬性都印出來...
HashMap map = (HashMap)device.getCapabilities();

Set set = map.keySet();
Iterator iter = set.iterator();

System.out.println("device.getCapabilities.size = " + map.size());
while(iter.hasNext())
{
String key = (String)iter.next();
System.out.println("Capability key = " + key + ",value = " + map.get(key));
}
//把Device所有屬性都印出來...

//根據MarkUp決定欲顯的網頁格式及頁面...
MarkUp markUp = device.getMarkUp();
String jspView = null;
if (MarkUp.XHTML_ADVANCED.equals(markUp)) {
jspView = XHTML_ADV;
} else if (MarkUp.XHTML_SIMPLE.equals(markUp)) {
jspView = XHTML_SIMPLE;
} else if (MarkUp.CHTML.equals(markUp)) {
jspView = CHTML;
} else if (MarkUp.WML.equals(markUp)) {
jspView = WML;
}

System.out.println("View: " + jspView);

// MIME type is decided by JSP. Only in case of XHTML
// will we need to multi-serve i.e text/html vs application/xml+xhtml vs
// application/vnd.wap.xml+xhtml
if (markUp == MarkUp.XHTML_ADVANCED || markUp == MarkUp.XHTML_SIMPLE) {
String contentType = "text/html";
try {
contentType = device
.getCapability("xhtmlmp_preferred_mime_type");
} catch (CapabilityNotDefinedException e) {
throw new RuntimeException(
"Somethingh is seriously wrong with your WURFL:"
+ e.getLocalizedMessage(), e);
}

request.setAttribute("contentType", contentType);
System.out.println("ContentType: " + contentType);
}
//根據MarkUp決定欲顯的網頁格式及頁面...

request.setAttribute("markUp", markUp);
request.setAttribute("device", device);

request.getRequestDispatcher("WEB-INF/jsp/" + jspView).forward(request,
response);
}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
processRequest(request, response);
}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
processRequest(request, response);
}

}



最後要設定web.xml檔:
<listener>
<listener-class>
net.sourceforge.wurfl.core.web.WURFLServletContextListener
</listener-class>
</listener>

要加入listener才能取得WURFLHolder物件。
WURFL在執行時期要取得手機資訊得依賴之前下載的WURFL.zip,所以將WURFL.zip放到WEB-INF/,然後在web.xml中加入context-param:
<context-param>
<param-name>wurfl</param-name>
<param-value>/WEB-INF/wurfl.zip</param-value>
</context-param>

如此;執行剛才建立的Servlet即可得知Device為何了,可利用Firefox的User Agent Switcher模擬iPhone 3.0可在Console中看到"Device: apple_iphone_ver3_sub7a341_enus"。

以上即為WURFL的簡單範例,WURFL還有PHP版本,台灣有一本翻譯的繁體書有寫WURFL的PHP版本:Smartphone 智慧型手機網路應用程式開發實戰

WURFL還有Patch_xx.xml的應用及整合Spring的範例,都可在WURFL官網查詢到。

WURFL Java細節可參考WURFL API Docs

1 則留言:

匿名 提到...

Touche. Great arguments. Keep up the amazing effort.

Feel free to surf to my web site: schools for sonogram technician