編輯:開發入門
構建未來的應用程序
這個例子的重點是如何在移動設備上使用地理定位,但請記住 Firefox 3.5+ 也支持地理定位。這個應用程序首先查找用戶當前位置附近的稱為場所 的 Foursquare。場所可以是任何東西,但通常是指飯館、酒吧、商店等。作為一個 Web 應用程序,我們的示例也受限於目前所有浏覽器均執行的同源策略。它不能直接調用 Foursquare 的 API。而是使用一個 Java servlet 來實際代理這些調用。之所以采用 Java 並沒有任何特別之處;您也可以用 PHP、Python、Ruby 等輕松編寫一個類似的代理。清單 5 顯示了一個代理 servlet。
清單 5. Foursquare 代理 servlet
public class FutureWebServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String operation = request.getParameter("operation");
if (operation != null && Operation.equalsIgnoreCase("getDetails")){
getDetails(request,response);
}
String geoLat = request.getParameter("geoLat");
String geoLong = request.getParameter("geoLong");
String baseUrl = "http://api.foursquare.com/v1/venues.json?";
String urlStr = baseUrl + "geolat=" + geoLat + "&geolong=" + geoLong;
PrintWriter out = response.getWriter();
proxyRequest(urlStr, out);
}
private void proxyRequest(String urlStr, PrintWriter out) throws IOException{
try {
URL url = new URL(urlStr);
InputStream stream = url.openStream();
BufferedReader reader = new BufferedReader( new InputStreamReader(stream));
String line = "";
while (line != null){
line = reader.readLine();
if (line != null){
out.append(line);
}
}
out.flush();
stream.close();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
private void getDetails(HttpServletRequest request, HttpServletResponse response)
throws IOException{
String venueId = request.getParameter("venueId");
String urlStr = "http://api.foursquare.com/v1/venue.JSon?vid="+venueId;
proxyRequest(urlStr, response.getWriter());
}
}
這裡需要注意的重要一點是代理了兩個 Foursquare API。一個用來搜索,另一個用來獲得這個場所的細節。要辨別這二者,細節 API 添加了一個操作參數。此外,將返回類型指定為 JSON,這會使解析來自 JavaScript 的數據變得十分簡單。知道了應用程序代碼所能進行哪種類型的調用之後,接下來讓我們看看它如何進行這些調用以及如何使用來自 Foursquare 的數據。
使用地理定位
第一個調用是一個搜索。清單 5 顯示了對於緯度和經度需要兩個參數:geoLat 和 geoLong 。如下的清單 6 顯示了如何獲得應用程序內的這兩個參數以及如何調用這個 servlet。
清單 6. 用位置進行搜索
if (!!navigator.geolocation){
navigator.geolocation.getCurrentPosition(function(location) {
venueSearch(location.coords.latitude, location.coords.longitude);
});
}
var allVenues = [];
function venueSearch(geoLat, geoLong){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
var responseObj = eval('(' + this.responseText + ')');
var venues = responSEObj.groups[0].venues;
allVenues = venues;
buildVenuesTable(venues);
}
}
xhr.open("GET", "api?geoLat=" + geoLat + "&geoLong="+geoLong);
xhr.send(null);
}
上述代碼會查找浏覽器的地理定位功能。如果浏覽器有此功能,就會獲得這個位置並用緯度和經度調用 venueSearch 函數。此函數使用了 AJax(一個 XMLHttpRequest 對象調用 清單 5 內的 servlet)。它為 callback 函數使用一個閉包、解析來自 Foursquare 的 JSON 數據並將一個由場所對象組成的數組傳遞給一個名為 buildVenuesTable 的函數,如下所示。
清單 7. 使用場所構建 UI
function buildVenuesTable(venues){
var rows = venues.map(function (venue) {
var row = document.createElement("tr");
var nameTd = document.createElement("td");
nameTd.appendChild(document.createTextNode(venue.name));
row.appendChild(nameTd);
var addrTd = document.createElement("td");
var addrStr = venue.address + " " + venue.city + "," + venue.state;
addrTd.appendChild(document.createTextNode(addrStr));
row.appendChild(addrTd);
var distTd = document.createElement("td");
distTd.appendChild(document.createTextNode("" + venue.distance));
row.appendChild(distTd);
return row;
});
var vTable = document.createElement("table");
vTable.border = 1;
var header = document.createElement("thead");
var nameLabel = document.createElement("td");
nameLabel.appendChild(document.createTextNode("Venue Name"));
header.appendChild(nameLabel);
var addrLabel = document.createElement("td");
addrLabel.appendChild(document.createTextNode("Address"));
header.appendChild(addrLabel);
var distLabel = document.createElement("td");
distLabel.appendChild(document.createTextNode("Distance (m)"));
header.appendChild(distLabel);
vTable.appendChild(header);
var body = document.createElement("tbody");
rows.forEach(function(row) {
body.appendChild(row);
});
vTable.appendChild(body);
$("searchResults").appendChild(vTable);
if (!!window.openDatabase){
$("saveBtn").style.display = "block";
}
}
清單 7 內的代碼主要是用來創建內含場所信息的數據表的 DOM 代碼。但其中也不乏一些亮點。請注意高級 JavaScript 特性(比如數組對象的映射以及 forEach 函數)的使用。這裡還有在支持地理定位的所有浏覽器上均可用的一些特性。最後的兩行代碼也很有趣。對數據庫支持的檢測在此執行。如果支持,那麼就啟用一個 Save 按鈕,用戶單擊此按鈕就可將所有該場所的數據保存到一個本地數據庫。下一節將討論這一點是如何實現的。
結構化存儲
清單 7 展示了典型的漸進增強策略。這個示例測試了是否有數據庫支持。如果有此支持,就會添加一個 UI 元素以便向這個應用程序添加一個新特性供其使用。在本例中,它啟用了一個按鈕。單擊此按鈕會調用函數 saveAll,如清單 8 所示。
清單 8. 保存到數據庫
var db = {};
function saveAll(){
db = window.openDatabase("venueDb", "1.0", "Venue Database",1000000);
db.transaction(function(txn){
txn.executeSql("CREATE TABLE venue (id INTEGER NOT NULL PRIMARY KEY, "+
"name NVARCHAR(200) NOT NULL, address NVARCHAR(100),
cross_street NVARCHAR(100), "+
"city NVARCHAR(100), state NVARCHAR(20), geolat TEXT NOT NULL, "+
"geolong TEXT NOT NULL);");
});
allVenues.forEach(saveVenue);
countVenues();
}
function saveVenue(venue){
// check if we already have the venue
db.transaction(function(txn){
txn.executeSql("SELECT name FROM venue WHERE id = ?", [venue.id],
function(t, results){
if (results.rows.length == 1 && results.rows.item(0)['name']){
console.log("Already have venue id=" + venue.id);
} else {
insertVenue(venue);
}
})
});
}
function insertVenue(venue){
db.transaction(function(txn){
txn.executeSql("INSERT INTO venue (id, name, address, cross_street, "+
"city, state, geolat, geolong) VALUES (?, ?, ?, ?, "+
"?, ?, ?, ?);", [venue.id, venue.name,
venue.address, venue.crossstreet, venue.city, venue.state,
venue.geolat, venue.geolong], null, errHandler);
});
}
function countVenues(){
db.transaction(function(txn){
txn.executeSql("SELECT COUNT(*) FROM venue;",[], function(transaction, results){
var numRows = results.rows.length;
var row = results.rows.item(0);
var cnt = row["COUNT(*)"];
alert(cnt + " venues saved locally");
}, errHandler);
});
}
要將場所數據保存到數據庫,先要創建一個用來存儲數據的表。創建表的語法是非常標准的 SQL 語法。(所有支持數據庫的浏覽器均使用 SQLite。查閱 SQLite 文檔獲得受支持的數據類型、限制等。)SQL 執行異步完成。此外,還會調用事務函數並向其傳遞一個回調函數。callback 函數獲得一個事務對象,用來執行 SQL。executeSQL函數接受一個 SQL 字符串,然後是一個可選的參數列表,外加成功和錯誤處理器函數。如果沒有錯誤處理器,錯誤就被 “吃掉”。對於 create table 語句而言,這是一種理想狀況。腳本首次執行時,此表將會被成功創建。當再次執行時,腳本將會失敗,因為表已經存在 — 但這也問題不大。因為我們只需要確保在向表內插入行之前,此表已經存在。
此表創建後,通過 forEach 函數用從 Foursquare 返回的每個場所調用 saveVenue 函數。此函數先是通過查詢這個場所來查證這個場所是否已經被存儲在本地。這裡,使用了一個成功處理器。查詢的結果集將被傳遞給這個處理器。如果沒有結果或場所尚未被存儲於本地,就會調用 insertVenue 函數,由它執行一個插入語句。
借助 saveAll,在所有的保存/插入完成後,調用 countVenues。目的是查詢插入到這個場所表內的行的總數。這裡的語法(row["COUNT(*)"])從查詢的結果集中拉出該計數。
了解了如何使用數據庫支持(如果有的話)後,接下來的一節將會探討如何使用 Web worker 支持。
Web worker 的後台處理
回到 清單 6,讓我們對它進行稍許修改。如下面的清單 9 所示,檢測是否有 Web worker 支持。如果有,就用它來獲得從 Foursquare 檢索來的每個場所的更多信息。
清單 9. 修改後的場所搜索
function venueSearch(geoLat, geoLong){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
var responseObj = eval('(' + this.responseText + ')');
var venues = responSEObj.groups[0].venues;
allVenues = venues;
buildVenuesTable(venues);
if (!!window.Worker){
var worker = new Worker("details.JS");
worker.onmessage = function(message){
var tips = message.data;
displayTips(tips);
};
worker.postMessage(allVenues);
}
}
}
xhr.open("GET", "api?geoLat=" + geoLat + "&geoLong="+geoLong);
xhr.send(null);
}
上述代碼使用了與之前相同的檢測方法。如果存在對 Web worker 支持,就會創建一個新的 worker。為了創建一個新的 worker,需要指向 worker 將要運行的腳本的 URL — 在本例中,即 details.JS 文件。當此 worker 完成其作業後,它會向主線程發送回一個消息。onmessage 處理器將接收這個消息;我們為它使用了一個簡單的閉包。最後,為了初始化這個 worker,用一些數據調用postMessage 以便它能工作起來。將所有檢索自 Foursquare 的場所信息傳遞進來。清單 10 顯示了 details.JS 的內容,該腳本將由此 worker 執行。
清單 10. worker 的腳本 details.JS
var tips = [];
onmessage = function(message){
var venues = message.data;
venues.foreach(function(venue){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
var venueDetails = eval('(' + this.responseText + ')');
venueDetails.tips.forEach(function(tip){
tip.venueId = venue.id;
tips.push(tip);
});
}
};
xhr.open("GET", "api?Operation=getDetails&venueId=" + venueId, true);
xhr.send(null);
});
postMessage(tips);
}
這個細節腳本迭代所有場所。對於每個場所,此腳本會使用XMLHttpRequest 調用 Foursquare 代理以便獲得場景的細節。不過,請注意在使用它的 open 函數打開連接時,傳遞進第三個參數(true)。這會使調用成為同步的,而不再是通常的異步的。在 worker 中這麼做是可以的,因為沒有處於主 UI 線程,並且不會凍結整個應用程序。把它變成同步的,意味著一個調用必須結束後,下一個才能開始。處理程序只簡單從場所細節中抽取 tips 並收集所有這些 tips 後一並傳遞回主 UI 線程。為了將此數據傳遞回去,調用了 postMessage 函數,由該函數在這個 worker 上調用 onmessage回調函數,如 清單 9 中所示。
默認地,這個場所搜索返回 10 個場所。不難想象為了獲得細節而進行 10 個額外的調用將要花費多長時間。因而使用 Web worker 在後台線程中完成這類任務是很有意義的。
結束語
本文介紹了現代浏覽器內的一些 Html 5 的新功能。您了解了如何檢測新的特性以及如何將這些新特性添加到您的應用程序內。這些特性的絕大多數現在都已經廣受流行浏覽器的支持 — 特別是移動浏覽器。現在您就可以充分利用地理定位和 Web worker 等特性來創建具有創新性的 Web 應用程序了。
使用 Android、Scala 和 Eclipse 創建移動應用程序
先決條件在本文中,我們將創建一個在 Android 設備上運行的移動應用程序。您將需要安裝 android SDK;本文使用 V1.5 SDK。應用程序代碼將用 Sca
Android PreferenceActivity 詳解教程
為了引入這個概率 首先從需求說起 即:現有某Activity專門用於手機屬性設置 那麼應該如何做呢?根據已學知識 很快一個念頭閃過 即:Activity + Prefe
Android之服務器診所: Expect 超出預期
簡介: Cameron Laird 用一篇對受歡迎的 Expect 工具的概述開啟了他新的月度專欄,Expect 是一種功能大大超出大多數程序員和管理員認識的
Android平台Qt開發入門教程
很多人會問,android平台可以不使用Java開發應用程序??我做Android平台native開發之前,也有這麼想過,但是我又想,底層系統全是c/c++代碼,用c/