編輯:中級開發
簡介: 有幾個網站從事一些非盈利服務,提供一些可輕松設置和使用的表單來進行民意測驗和數據收集。本教程介紹一個簡單的架構來為 android 設計類似的應用程序 — 允許非編程員從移動用戶收集數據的動態用戶界面。在本教程中,您將創建一個擁有服務器端和移動端的示例表單引擎。
開始之前
為更好地利用本文,您應該使用 Android SDK 來構造 Android 應用程序。完成本文之後,您將了解如何使用 HTTP(S) 執行從應用程序到 web 服務器的通信,以及如何使用 DOM 解析器解析 XML。在此過程中,您將創建自定義和動態的用戶界面布局、多線程通信、消息處理程序、以及進程對話框。至少,您將了解 androidManifest.XML 和服務器端腳本編寫。
關於本教程
本教程介紹一個用於在 android 設備上進行移動數據收集的動態表單的架構。我將首先介紹一個高級架構,討論這樣一個應用程序在數據收集的大環境中的應用。快速浏覽這個已完成的項目,包含每個源文件,對本教程將要介紹的內容有一個大概了解。就像 “廚藝展示” 一樣,您使用一些 Java 類從頭開始構建這個應用程序,每個 Java 類都被小心引入並關聯到應用程序的其他方面,其中最顯著是一個數據模型,這個表單引擎就是基於它構造的。最後,您將表單數據保存在服務器上並簡單地查看應用程序的服務器端。
數據收集
我們首先簡要討論數據收集,以及如何在使用 android 移動設備時輕松實現數據收集。
一個 android 數據收集框架
收集數據是計算機時代之前的任務。計算機已經變成一個日常 “訂書釘”,使我們思考、查找和使用信息的方式發生了革命性的變化。市值數百億美元的公司要歸功於他們在存儲、檢索和管理海量信息中的高效。今天使用的數據庫由各種各樣架構的系統供給,包括大型機、客戶服務器、web 應用程序、以及現在的移動應用程序。
物理存貨和周期計數應用程序是移動計算的早期實用應用程序。這些應用程序通常是批量數據收集,其中硬件需要一個擴展插口(docking station)來上傳收集的信息。
從出現到現在,移動應用程序市場已經走過了很長一段路程,無線連通性和設備幾乎遍布各種文化和市場,滲透到日常生活的每個方面。
盡管數據收集方式可能已經變得更具流動性,但數據收集的核心並沒有發生太大變化。用戶必須看到一些問題,並且要有一種簡單的響應方式。本教程演示如何為 android 驅動的移動電話構造一個簡單的數據收集框架,其中要利用一個 XML 支持的動態元數據結構。
應用程序架構
在深入代碼之前,我們先在一個非常高的層面上檢查應用程序設置。
Forms Engine 一瞥
我們首先浏覽一下這個 Forms Engine 應用程序的各個方面。圖 1描繪這個應用程序與提供各種內容的數據錄入表單的一個或多個服務器之間的關系。
圖 1. 應用程序架構
在 圖 1 中,Form 1 提供 Robotics Competition 的注冊,Form 2 要求用戶提供關於他們的汽車維修習慣的信息。這些表單和 android 應用程序通信使用 HTTP(S)來:
本教程的服務器端實現為一對文件:一個描述表單的 XML 文檔和一個負責記錄表單提交的 PHP 文檔。這個 android 應用程序是一個用 Java 代碼編寫的原生應用程序,使用 android SDK,在 Eclipse 中編碼。
表 2 顯示完整應用程序的源文件。您可以下載包含所有這些源文件的壓縮文件(參見 下載)。本教程將詳細介紹每個源文件。
表 2. 必要的應用程序源文件
圖 2 展示本教程末尾完成的應用程序在 Eclipse 中的項目結構。(查看 圖 2 的文本版本。)
圖 2. Eclipse 中的項目
如果您沒有一個正在運行的 Android 開發環境,那麼現在是安裝 Android 工具的絕好時機。要了解如何設置一個 android 開發環境,請參閱 參考資料 中必要工具的鏈接,以及一些關於如何為 Android 開發應用程序的入門文章和教程。熟悉 android 有助於理解本教程。
您已經對架構和應用程序有一個大致了解,現在就開始吧!
項目和數據模型
我們現在已准備好在 Eclipse 中啟動這個 android 項目,創建允許您為這個 Forms Engine 應用程序存儲和管理元數據的數據模型和類。
創建項目
創建 android 應用程序從我們熟悉的地方開始:打開 Eclipse 並選擇 File > New,如 圖 3 所示。
圖 3. 新建一個 android 應用程序
這個步驟將啟動 Eclipse New project 向導。選擇 android Project(針對 android 的專業 Java 環境)。確保向項目提供一個有效的標識符(我使用的是 XMLGUI)。為匹配本教程中介紹的解決方案,在 PropertIEs 下面,指定應用程序名為 XML GUI,包名為 com.msi.ibm。選擇 Create Activity 復選框並將 Activity name 指定為 XMLGui,如 圖 4 所示。
圖 4. 設置一個新項目
項目創建後,它應該非常類似 圖 5 中的圖像。(查看 圖 5 的文本版本。)
圖 5. New project 向導剛剛完成時的 android 項目
現在,項目已創建,確保應用程序干淨地構建並在 android Emulator 中運行是一個最佳實踐。注意,有時應用程序直到您編輯並保存 Java 源文件時才會構建。這導致 android SDK 工具自動在gen 文件夾中生成一些文件。如果 Eclipse 環境中的 Problems 選項卡沒有顯示任何條目,那麼您可以測試這個應用程序。
要測試應用程序,創建一個 Run 配置,如 圖 6 所示。在 android Application 列表中,選擇 XMLGui。確保以下值出現:XMLGui 位於 Name 字段中,XMLGUI 位於 Project 字段中,com.msi.ibm.XMLGui 位於 Launch 字段中。單擊 Run。
圖 6. Run 配置設置
此項目已在 Android 模擬器中創建、配置並正確啟動,現在可以為 android 創建 XML 驅動的數據收集工具了。
回頁首
數據模型
這個應用程序的具體細節要求它向一個用戶顯示輸入元素,驗證數據,然後將數據提交到一個指定服務器。如果這個應用程序只針對新記錄設置,那麼它什麼用處也沒有,因為它不支持查詢現有記錄以便編輯或刪除。
要向應用程序提供足夠的關於如何顯示數據錄入表單的指示,需要一個信息集(通常稱為元數據)。元數據是關於數據的數據。簡言之,這個應用程序必須理解幾個數據元素,包括:
幾乎所有種類的問題都映射到這三種類型的用戶界面元素中的一種。例如,可以實現一個復選框作為一個 Yes 或 No 選擇字段。可以實現多重選擇(multi-select)作為多選字段。當然,您可以隨意擴展本教程中展示的代碼。
對於您的應用程序,使用場景如下:您在參加一個活動,其中您可以注冊一個或多個項目。您可以填寫一張注冊表,也可以等到回家後登錄該組織的網站進行注冊。在本例中,您將假定一位用戶將通過在一個 android 設備上下載一個動態表單,以便在現場從他的手機填寫一張簡單表單,提供申請者的姓名、性別和年齡。
清單 1 顯示了 xmlgui1.XML 的內容,該文件表示一個 Robotics 俱樂部活動的注冊表單。
清單 1. xmlgui1.XML
<?xml version="1.0" encoding="utf-8"?> <xmlgui> <form id="1" name="Robotics Club Registration" submitTo="http://serverurl/XMLgui1-post.PHP" > <field name="fname" label="First Name" type="text" required="Y" options=""/> <field name="lname" label="Last Name" type="text" required="Y" options=""/> <field name="gender" label="Gender" type="choice" required="Y" options="Male|Female"/> <fIEld name="age" label="Age on 15 Oct. 2010" type="numeric" required="N" options=""/> </form> </XMLgui>
注意關於這個 XML 文檔的以下幾點:
submitTo 屬性告知應用程序,數據收集完成後將要發送到的位置。fIEld 元素同時提供一個字段名和一個標簽的屬性。盡管這些值是相關的,但您希望每個 name 屬性的值都保持惟一,以便接收應用程序能夠正確解析並處理它們。您還應該向用戶提供一個提示性label 值,提示用戶什麼種類的數據將進入一個特定字段。regex 表達式,或者一個獲取關於特殊字段的更多信息的鏈接。options 字段用作 choice 字段的一個受限列表。基本了解數據模型後,現在看看負責將這個 XML 數據轉化為一個有用應用程序的代碼。
回頁首
表示數據
解析數據是一個非常機械的練習,將在本教程後面展示。在檢查解析流程之前,應用程序需要某個位置來在內存中存儲和管理這個元數據。出於這個目的,您擁有兩個 Java 類,一個用於表單,一個用於表示表單字段。我們首先查看 清單 2 中的 XMLGuiForm.Java。
清單 2. XMLGuiForm.Java
package com.msi.ibm;
import java.util.Vector;
import java.util.ListIterator;
import Java.Net.URLEncoder;
public class XmlGuiForm {
private String formNumber;
private String formName;
private String submitTo;
public Vector<XmlGuiFormField> fields;
public XmlGuiForm()
{
this.fields = new Vector<XmlGuiFormField>();
formNumber = "";
formName = "";
submitTo = "loopback"; // do nothing but display the results
}
// getters & setters
public String getFormNumber() {
return formNumber;
}
public void setFormNumber(String formNumber) {
this.formNumber = formNumber;
}
public String getFormName() {
return formName;
}
public void setFormName(String formName) {
this.formName = formName;
}
public String getSubmitTo() {
return submitTo;
}
public void setSubmitTo(String submitTo) {
this.submitTo = submitTo;
}
public Vector<XmlGuiFormField> getFields() {
return fields;
}
public void setFields(Vector<XmlGuiFormField> fields) {
this.fields = fields;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("XmlGuiForm:\n");
sb.append("Form Number: " + this.formNumber + "\n");
sb.append("Form Name: " + this.formName + "\n");
sb.append("Submit To: " + this.submitTo + "\n");
if (this.fields == null) return sb.toString();
ListIterator<XmlGuiFormField> li = this.fields.listIterator();
while (li.hasNext()) {
sb.append(li.next().toString());
}
return sb.toString();
}
public String getFormattedResults()
{
StringBuilder sb = new StringBuilder();
sb.append("Results:\n");
if (this.fields == null) return sb.toString();
ListIterator<XmlGuiFormField> li = this.fields.listIterator();
while (li.hasNext()) {
sb.append(li.next().getFormattedResult() + "\n");
}
return sb.toString();
}
public String getFormEncodedData()
{
try {
int i = 0;
StringBuilder sb = new StringBuilder();
sb.append("Results:\n");
if (this.fields == null) return sb.toString();
ListIterator<XmlGuiFormField> li = this.fIElds.listIterator();
while (li.hasNext()) {
if (i != 0) sb.append("&");
XMLGuiFormField thisField = li.next();
sb.append(thisField.name + "=");
String encstring = new String();
URLEncoder.encode((String) thisFIEld.getData(),encstring);
sb.append(encstring);
}
return sb.toString();
}
catch (Exception e) {
return "ErrorEncoding";
}
}
}
關於 XMLGuiForm 類,有幾個重要項目需要注意:
formNumber:這是服務器端表單分發機制的惟一標識符。formName:這是表單標題,向用戶提供上下文和確認。submitTo:這是應用程序提交輸入的數據(經過驗證)的 URL。這個值也可以是一個 loopback。在 loopback 場景中,數據顯示給用戶,而不是提交到服務器。這對測試比較有用。fIElds:這是被模板化以持有表單的字段數據的 Vector 類。清單 3展示了 XMLGuiFormFIEld.Java 的細節。getters 和 setters。toString() 和 getFormattedResults() 方法負責生成 XMLGuiForm類的可讀摘要。getFormEncodedData() 方法在准備將數據提交到 submitTo 屬性中指定的 URL 時使用。Java.lang.String 類,而是采用一個StringBuilder 作為構建理想數據字符串的一個更內存高效的方法。URLEncoder 類用於准備將數據提交到服務器。這使得數據實際上看起來就像一個傳統 Html 表單創建的一樣。現在看看 清單 3 中 XMLGuiFormFIEld 類的構造情況。
清單 3. XMLGuiFormFIEld.Java
package com.msi.ibm;
// class to handle each individual form
public class XmlGuiFormField {
String name;
String label;
String type;
boolean required;
String options;
Object obj; // holds the ui implementation
// or the EditText for example
// getters & setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
public String getOptions() {
return options;
}
public void setOptions(String options) {
this.options = options;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Field Name: " + this.name + "\n");
sb.append("Field Label: " + this.label + "\n");
sb.append("Field Type: " + this.type + "\n");
sb.append("Required? : " + this.required + "\n");
sb.append("Options : " + this.options + "\n");
sb.append("Value : " + (String) this.getData() + "\n");
return sb.toString();
}
public String getFormattedResult()
{
return this.name + "= [" + (String) this.getData() + "]";
}
public Object getData()
{
if (type.equals("text") || type.equals("numeric"))
{
if (obj != null) {
XmlGuiEditBox b = (XmlGuIEditBox) obj;
return b.getValue();
}
}
if (type.equals("choice")) {
if (obj != null) {
XmlGuiPickOne po = (XMLGuiPickOne) obj;
return po.getValue();
}
}
// You could add logic for other UI elements here
return null;
}
}
現在更詳細地查看 XMLGuiFormFIEld 類。
name 持有字段名稱 — 這是數據值的字段名稱,類似於 Html 值的表單域名稱或數據庫列名。label 持有字段說明或顯示給用戶的值。type 表明要創建的用戶界面字段的風格。
text 表明這個字段是用一個 EditText 字段實現的,用於輸入字母數字字符。這是最常用的值。numeric 也是一個 EditText,但它局限於數字值。choice 使這個字段成為一個下拉列表。required 是一個 Boolean 值,標志字段是否為必要字段。如果字段必要且沒有填充,則當用戶試圖提交表單時,將向用戶顯示一條錯誤消息。options 是一個字符串值,用於為一個選擇字段傳送一個可用選項列表。這個字段對其他字段可用,可能用作一個 regex 表達式以進行驗證,也可以被覆蓋以指定一個默認值。obj 表示用戶界面小部件。例如,這個變量持有一個 EditText,用於文本或數字字段。對於一個選擇字段,obj 成員包含一個 spinner小部件。這個方法將在本教程後面進一步解釋。getters 和 setters。toString() 和 getFormattedResult() 方法都利用 getData() 方法,我們將稍後解釋。XMLGuiFormFIEld 類中,您需要管理多種類型數據,因此代碼需要明確表示數據如何存儲和訪問。getData() 方法檢查這個類型字段並在 obj 字段上執行一個類型設置,以便與存儲的對象正確交互。如果您想向這個框架添加新字段類型,可以擴展 getData() 方法來支持這種新字段類型(參見 清單 3 末尾附近的注釋)。您現在有一種方法來存儲和管理元數據。現在可以檢查應用程序的運行情況,然後將各部分組合起來。
Android之自定義背景按鈕、自定義形狀按鈕
在android開發應用中,默認的Button是由系統渲染和管理大小的。而我們看到的成功的移動應用,都是有著酷炫的外觀和使用體驗的。因此,我們在開發產品的時候,需要對默
為 iPhone 和 Android 構建基於浏覽器的應用程序
簡介: 本文是共兩部分的系列文章 “android 和 iPhone 浏覽器之戰” 的第 2 部分,主要關注為 iPhone 和 Android 開發基於浏覽
聯合使用 HTML 5、地理定位 API 和 Web 服務來創建移動混搭程序
簡介: 在這個由五個部分所組成的系列的第一部分中,您將接觸到移動 Web 應用程序中最流行的新技術:地理定位。高端智能手機都內置 GPS,現在您將了解 Web
Android之Linux 調度器 BFS 簡介
簡介: BFS 是一款專門為 Linux 桌面環境所設計的內核調度器,它基於 Staircase Deadline 和 EEVDF 算法,支持 Linux 2