編輯:Android開發實例
常用縮略詞
Ajax: Asynchronous JavaScript + XML
API:應用程序編程接口
CSV:逗號分隔值
CSS:層疊樣式表
DOM:文檔對象模型
HTML:超文本標記語言
HTTP:超文本傳輸協議
IDL:接口描述語言
JSON:Javascript 對象標識
SAX:XML 簡單 API
SDK:軟件開發包
UI:用戶界面
URL:統一資源定位符
XML:可擴展標記語言
3G:第三代手機技術標准
Android 應用程序必須訪問位於 Internet 上的數據,而 Internet 數據可以有幾種不同的格式。本文將介紹在 Android 應用程序中如何使用三種數據格式:
XML
JSON
Google 的 protocol buffers
首先開發一個 Web 服務,將 CSV 數據轉換成 XML、JSON 和 protocol-buffers 格式。然後構建一個樣例 Android 應用程序,可以從 Web 服務中以任何一種格式提取數據並將其解析並顯示給用戶。
要進行本文中的練習,您需要最新的 Android SDK和 Android 2.2 平台。SDK 還要求您安裝一個 Java™ 開發包(JDK);本文中使用了 JDK 1.6.0_17。您不需要有 Android 物理設備;所有代碼都將在 SDK 的 Android 仿真器中運行。本文並沒有教您如何進行 Android 開發,因此建議您熟悉 Android 編程。當然,只憑借 Java 編程語言的知識也可以完成本文的學習。
您還需要一個 Java web 應用程序服務器來運行 Android 應用程序使用的 Web 服務。此外,也可以將服務器端代碼部署到 Google App Engine。參見 下載 部分獲得完整的源代碼。
Day Trader 應用程序
您將開發一個簡單的 Android 應用程序,叫做 Day Trader。Day Trader 允許用戶輸入一個或更多的股票代碼並獲取其所代表股票的最新價格信息。用戶可以指定數據使用的格式:XML、JSON 或 protocol buffers。實際的 Android 應用程序通常不會提供此選擇,但是通過實現此功能,您可以了解如何讓您的應用程序處理每一種格式。圖 1 展示了 Day Trader 用戶界面:
圖 1. 運行中的 Day Trader 應用程序
文本框及其旁邊的 Add Stock 按鈕允許用戶輸入感興趣的每支股票的代碼。用戶按下 Download Stock Data 按鈕後,會從服務器請求所有這些股票的數據,在應用程序中解析並顯示在屏幕上。默認情況下,獲取的是 XML 數據。通過菜單,您可以在 XML、JSON 或 protocol buffers 數據格式間切換。
清單 1 顯示用於創建 圖 1 中所示 UI 的布局 XML:
清單 1. Day Trader 布局 XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText android:id="@+id/symbol" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:width="120dip"/>
<Button android:id="@+id/addBtn" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/addBtnLbl"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/symList" />
<Button android:id="@+id/dlBtn" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dlBtnLbl"
/>
</LinearLayout>
<ListView android:id="@android:id/list"
android:layout_height="fill_parent" android:layout_width="fill_parent"
android:layout_weight="1"
/>
</LinearLayout>
清單 1 中的大部分代碼都簡單明了。可以看到幾個小部件創建了 圖 1 所示的輸入和按鈕。還會看到一個 ListView,Android 小部件中真正的瑞士軍刀。此 ListView 將用從服務器下載的股票數據填充。清單 2 顯示了控制該視圖的 Activity:
清單 2. Day Trader 主活動
public class Main extends ListActivity {
private int mode = XML; // default
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final EditText input = (EditText) findViewById(R.id.symbol);
final TextView symbolsList = (TextView) findViewById(R.id.symList);
final Button addButton = (Button) findViewById(R.id.addBtn);
final Button dlButton = (Button) findViewById(R.id.dlBtn);
addButton.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
String newSymbol = input.getText().toString();
if (symbolsList.getText() == null ||
symbolsList.getText().length() == 0){
symbolsList.setText(newSymbol);
} else {
StringBuilder sb =
new StringBuilder(symbolsList.getText());
sb.append(",");
sb.append(newSymbol);
symbolsList.setText(sb.toString());
}
input.setText("");
}
});
dlButton.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
String symList = symbolsList.getText().toString();
String[] symbols = symList.split(",");
symbolsList.setText("");
switch (mode){
case JSON :
new StockJsonParser().execute(symbols);
break;
case PROTOBUF :
new StockProtoBufParser().execute(symbols);
break;
default :
new StockXmlParser().execute(symbols);
break;
}
}
});
}
}
此 Activity 設置了 清單 1 中 XML 文件的布局,它將幾個事件處理程序連接起來。首先,對於 Add Stock 按鈕而言,代碼讀取文本框中的代碼並將其添加到 symList TextView 中,用逗號分隔每個代碼。接下來,對於 Download 按鈕而言,處理程序從 symList TextView 中讀取數據,然後 —基於 mode 變量— 使用三個不同的類之一從服務器下載數據。菜單設置 mode 變量的值;這個代碼不是很重要,因此我在 清單 2 中省略了它。在了解各種數據下載/解析類之前,我先為您展示一下服務器如何提供此數據。
提供股票數據
應用程序服務器需要能夠做兩件事。第一,它必須獲取股票代碼列表並檢索它們的數據。然後,它需要接受一個格式參數並基於該格式編碼數據。對於 XML 和 JSON 格式而言,該服務器將返回作為文本的串行化的股票數據。對於 protocol buffers 而言,它必須發送二進制數據。 清單 3 顯示了處理這些步驟的 servlet:
清單 3. Stock Broker servlet
public class StockBrokerServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException {
String[] symbols = request.getParameterValues("stock");
List<Stock> stocks = getStocks(symbols);
String format = request.getParameter("format");
String data = "";
if (format == null || format.equalsIgnoreCase("xml")){
data = Stock.toXml(stocks);
response.setContentType("text/xml");
} else if (format.equalsIgnoreCase("json")){
data = Stock.toJson(stocks);
response.setContentType("application/json");
} else if (format.equalsIgnoreCase("protobuf")){
Portfolio p = Stock.toProtoBuf(stocks);
response.setContentType("application/octet-stream");
response.setContentLength(p.getSerializedSize());
p.writeTo(response.getOutputStream());
response.flushBuffer();
return;
}
response.setContentLength(data.length());
response.getWriter().print(data);
response.flushBuffer();
response.getWriter().close();
}
public List<Stock> getStocks(String... symbols) throws IOException{
StringBuilder sb = new StringBuilder();
for (String symbol : symbols){
sb.append(symbol);
sb.append('+');
}
sb.deleteCharAt(sb.length() - 1);
String urlStr =
"http://finance.yahoo.com/d/quotes.csv?f=sb2n&s=" +
sb.toString();
URL url = new URL(urlStr);
HttpURLConnection conn =
(HttpURLConnection) url.openConnection();
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String quote = reader.readLine();
List<Stock> stocks = new ArrayList<Stock>(symbols.length);
while (quote != null){
String[] values = quote.split(",");
Stock s =
new Stock(values[0], values[2],
Double.parseDouble(values[1]));
stocks.add(s);
quote = reader.readLine();
}
return stocks;
}
}
這是一個簡單的 Java servlet,只支持 HTTP GET 請求。它讀入股票的值和格式請求參數。然後調用 getStocks() 方法。該方法調用 Yahoo! Finance 獲取股票數據。Yahoo! 只支持 CSV 格式的數據,因此 getStocks() 方法將其解析到一個 Stock 對象列表。清單 4 展示了這個簡單的數據結構:
清單 4. 股票數據結構
public class Stock {
private final String symbol;
private final String name;
private final double price;
//getters and setters omitted
public String toXml(){
return "<stock><symbol>" + symbol +
"</symbol><name><![CDATA[" +
name + "]]></name><price>" + price +
"</price></stock>";
}
public String toJson(){
return "{ 'stock' : { 'symbol' : " +symbol +", 'name':" + name +
", 'price': '" + price + "'}}";
}
public static String toXml(List<Stock> stocks){
StringBuilder xml = new StringBuilder("<stocks>");
for (Stock s : stocks){
xml.append(s.toXml());
}
xml.append("</stocks>");
return xml.toString();
}
public static String toJson(List<Stock> stocks){
StringBuilder json = new StringBuilder("{'stocks' : [");
for (Stock s : stocks){
json.append(s.toJson());
json.append(',');
}
json.deleteCharAt(json.length() - 1);
json.append("]}");
return json.toString();
}
}
每個 Stock 都有三個屬性— symbol、name 和 price — 和幾個便捷的方法,以便將其自己轉換成 XML 字符串或 JSON 字符串。它提供了一個工具方法,用於將 Stock 對象列表轉換成 XML 或 JSON。回到 清單 3,根據格式請求參數,Stock 對象列表被轉換成 XML 或 JSON 字符串並被發送回客戶端。
XML 和 JSON 用例非常類似和直接。對於 protocol buffers,您必須生成 protocol buffers 格式的代碼讀寫對象。為此,您需要使用 protocol buffers 規范格式定義數據結構。清單 5 展示了一個示例:
清單 5. 股票的 Protocol buffers 消息
package stocks;
option java_package = "org.developerworks.stocks";
message Quote{
required string symbol = 1;
required string name = 2;
required double price = 3;
}
message Portfolio{
repeated Quote quote = 1;
}
protocol buffers 消息格式類似於接口描述語言 (IDL),它與語言無關,因此可以將其與各種語言一起使用。在本例中,運行 protocol buffers 編譯器(protoc)將 清單 5 中的代碼編譯成要用於客戶端和服務器的 Java 類。有關將 protocol buffers 消息編譯成 Java 類的詳細信息,請參閱 Protocol Buffers Developer Guide。
在 清單 3 中,一個名為 toProtoBuf() 的方法將 Stock 對象列表轉換成一個 Portfolio 消息。清單 6 展示了該方法的實現:
清單 6. 創建組合消息
public static Stocks.Portfolio toProtoBuf(List<Stock> stocks){
List<Stocks.Quote> quotes = new ArrayList<Stocks.Quote>(stocks.size());
for (Stock s : stocks){
Quote q =
Quote.newBuilder()
.setName(s.name)
.setSymbol(s.symbol)
.setPrice(s.price)
.build();
quotes.add(q);
}
return Portfolio.newBuilder().addAllQuote(quotes).build();
}
清單 6 中的代碼使用了從 清單 5 中的消息生成的代碼 — Quote 和 Portfolio 類。只需構建來自每個 Stock 對象的 Quote,然後將其添加到 清單 3 中返回到 servlet 的 Portfolio 對象即可。在 清單 3 中,servlet 直接打開到客戶端的流並使用生成的代碼編寫到流的二進制協議 buffers 數據。
現在,您了解了服務器如何創建要發送到 Android 應用程序的數據。接下來將學習應用程序如何解析此數據。
使用數據格式
清單 2 中的主 Activity 需要使用服務器可以發送的各種格式的數據。它還需要請求適當格式的數據並且數據一旦解析,就用它來填充其 ListView。因此,無論數據格式是什麼,大部分功能都是通用的。
首先,創建一個抽象的基類,封裝此通用功能,如 清單 7 所示:
清單 7. 數據解析器基類
abstract class BaseStockParser extends AsyncTask<String, Integer, Stock[]>{
String urlStr = "http://protostocks.appspot.com/stockbroker?format=";
protected BaseStockParser(String format){
urlStr += format;
}
private String makeUrlString(String... symbols) {
StringBuilder sb = new StringBuilder(urlStr);
for (int i=0;i<symbols.length;i++){
sb.append("&stock=");
sb.append(symbols[i]);
}
return sb.toString();
}
protected InputStream getData(String[] symbols) throws Exception{
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(new URI(makeUrlString(symbols)));
HttpResponse response = client.execute(request);
return response.getEntity().getContent();
}
@Override
protected void onPostExecute(Stock[] stocks){
ArrayAdapter<Stock> adapter =
new ArrayAdapter<Stock>(Main.this, R.layout.stock,
stocks );
setListAdapter(adapter);
}
}
清單 7 中的基類擴展了 android.os.AsyncTask。這是一個常用的用於異步操作的類。它抽象出線程和處理程序的創建,用於請求主 UI 線程。它是基於其輸入和輸出數據類型參數化的。對於所有解析器而言,輸入總是一樣的:股票代碼字符串。 輸出也是一樣的:Stock 對象數組。基類獲取 format,這是一個指定了要使用的數據格式的字符串。然後提供一個方法,發出適當的 HTTP 請求並返回一個流響應。最後,它覆蓋 AsyncTask 的 onPostExecute() 方法並使用從解析器返回的數據為 Activity 的 ListView 創建一個 Adapter。
現在看到三個解析器的功能是通用的。我將為您展示更具體的解析代碼,從 XML 解析器開始。
用 SAX 解析 XML
Android SDK 提供了幾種使用 XML 的方式,包括標准 DOM 和 SAX。 對於一些對內存密集型情況,可以使用 SDK 的 pull-parser。大部分時候,SAX 是最快的方式。Android 包括一些便捷的 API 使得使用 SAX 更輕松。清單 8 顯示了 Day Trader 應用程序的 XML 解析器:
清單 8. XML 解析器實現
private class StockXmlParser extends BaseStockParser{
public StockXmlParser(){
super("xml");
}
@Override
protected Stock[] doInBackground(String... symbols) {
ArrayList<Stock> stocks = new ArrayList<Stock>(symbols.length);
try{
ContentHandler handler = newHandler(stocks);
Xml.parse(getData(symbols), Xml.Encoding.UTF_8, handler);
} catch (Exception e){
Log.e("DayTrader", "Exception getting XML data", e);
}
Stock[] array = new Stock[symbols.length];
return stocks.toArray(array);
}
private ContentHandler newHandler(final ArrayList<Stock> stocks){
RootElement root = new RootElement("stocks");
Element stock = root.getChild("stock");
final Stock currentStock = new Stock();
stock.setEndElementListener(
new EndElementListener(){
public void end() {
stocks.add((Stock) currentStock.clone());
}
}
);
stock.getChild("name").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
currentStock.setName(body);
}
}
);
stock.getChild("symbol").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
currentStock.setSymbol(body);
}
}
);
stock.getChild("price").setEndTextElementListener(
new EndTextElementListener(){
public void end(String body) {
currentStock.setPrice(Double.parseDouble(body));
}
}
);
return root.getContentHandler();
}
}
清單 8 中的大部分代碼都在 newHandler() 方法中,該方法創建一個 ContentHandler。如果熟悉 SAX 解析, 會知道 ContentHandler 通過響應 SAX 解析器觸發的各種事件創建解析數據。newHandler() 方法使用 Android 便捷 API 指定使用事件處理程序的 ContentHandler。代碼只是偵聽在解析器遇到各種標記時觸發的事件,然後選取數據,放到 Stock 對象列表中。 創建 ContentHandler 後,調用 Xml.parse() 方法來解析基類提供的 InputStream 並返回 Stock 對象數組。這是快速解析 XML 的方法,但是 —即使使用 Android 提供的便捷 API— 它也是非常冗長的。
使用 JSON
XML 是 Android 上的一等公民,鑒於依賴於 XML 的 Web 服務的數量,這是個好事。很多服務還支持另一個流行格式 JSON。它通常比 XML 簡潔一些,但也是人們可讀的,使得它更易於使用,並且可以更輕松地將其用於調試使用它的應用程序。Android 包括一個 JSON 解析器。(您可以從 JSON.org 網站獲得該解析器,只是要去除幾個手機不需要的類)。 清單 9 顯示了使用中的解析器:
清單 9. JSON 解析器實現
private class StockJsonParser extends BaseStockParser{
public StockJsonParser(){
super("json");
}
@Override
protected Stock[] doInBackground(String... symbols) {
Stock[] stocks = new Stock[symbols.length];
try{
StringBuilder json = new StringBuilder();
BufferedReader reader =
new BufferedReader(
new InputStreamReader(getData(symbols)));
String line = reader.readLine();
while (line != null){
json.append(line);
line = reader.readLine();
}
JSONObject jsonObj = new JSONObject(json.toString());
JSONArray stockArray = jsonObj.getJSONArray("stocks");
for (int i=0;i<stocks.length;i++){
JSONObject object =
stockArray.getJSONObject(i).getJSONObject("stock");
stocks[i] = new Stock(object.getString("symbol"),
object.getString("name"),
object.getDouble("price"));
}
} catch (Exception e){
Log.e("DayTrader", "Exception getting JSON data", e);
}
return stocks;
}
}
可以看到在 Android 中使用 JSON 解析器是多麼簡單。您將來自服務器的流轉換成傳遞給 JSON 解析器的字符串。您遍歷對象圖並創建 Stock 對象數組。如果使用過 XML DOM 解析,這看起來很類似,因為編程模型幾乎一樣。
像 DOM 一樣,JSON 解析器可以用於內存密集型應用。在 清單 9 中,所有來自服務器的數據都表示為字符串,然後作為 JSONObject,最後作為 Stock 對象數組。換句話說,同一數據通過三種不同的方式表示。可以看到,對於大量數據而言,這可能是個問題。當然,一旦到達方法末尾,這三種數據表示方式中的兩種都會落在范圍之外,被垃圾回收器回收。但是,只是觸發更頻繁的垃圾回收可能會對用戶體驗帶來負面影響,造成處理速度下降。如果內存效率和性能很重要,使用 protocol buffers 的解析器可能是個較好的選擇。
使用 protocol buffers 處理二進制
Protocol buffers 是一個由 Google 開發的與語言無關的數據串行化格式,旨在比 XML 更快地通過網絡傳送數據。它是 Google 用於服務器對服務器調用的事實 標准。Google 將該格式及其用於 C++、Java 和 Python 編程語言的綁定工具以開源方式提供。
在 清單 3 和 清單 6 中看到 protocol buffers 是二進制格式。如您所料,這使得數據很簡潔。如果在客戶端和服務器端啟用 gzip 壓縮,在使用 XML 和 JSON 時通常也可以得到類似的消息大小,但是 protocol buffers 仍然有一些大小上的優勢。它還是一種可以迅速解析的格式。最後,它提供了一個相當簡單的 API。 清單 10 顯示了一個示例解析器實現:
清單 10. Protocol buffers 解析器實現
private class StockProtoBufParser extends BaseStockParser{
public StockProtoBufParser(){
super("protobuf");
}
@Override
protected Stock[] doInBackground(String... symbols) {
Stock[] stocks = new Stock[symbols.length];
try{
Stocks.Portfolio portfolio =
Stocks.Portfolio.parseFrom(getData(symbols));
for (int i=0;i<symbols.length;i++){
stocks[i] = Stock.fromQuote(portfolio.getQuote(i));
}
} catch (Exception e){
Log.e("DayTrader", "Exception getting ProtocolBuffer data", e);
}
return stocks;
}
}
如 清單 3 所示,您可以使用 protocol buffers 編譯器生成的 helper 類。這與服務器使用的 helper 類相同。可以編譯它一次,然後在服務器和客戶端共享它。 這樣,您可以更輕松地直接從服務器的流讀取數據並將其轉換成 Stock 對象數組。這種簡單編程也具有非常出色的性能。現在看一下此性能與 XML 和 JSON 的比較。
性能比較
比較性能通常涉及某種微基准測試,此類基准測試很容易產生偏見或無意間得到不正確的結果。即使以公平方式設計微基准測試,很多隨機因素也會對結果產生影響。盡管有這些問題,我還是要使用這樣的微基准測試來比較 XML(大約 1300 ms)、JSON(大約 1150 ms)和 protocol buffers(大約 750 ms)。基准測試向服務器發送了一個關於 200 個股票的請求並測量了從發出請求到用於創建 ListView 的 Adapter 的數據准備就緒所需的時間量。對每個數據格式在兩個設備上進行 50 次這樣的操作:一個 Motorola Droid 和一個 HTC Evo,兩個都通過 3G 網絡。 圖 2 顯示了結果:
圖 2. 比較數據格式速度
圖 2 顯示出,在此基准測試中 protocol buffers(大約 750 ms)比 XML (大約 1300 ms)幾乎快兩倍。很多因素影響著數據通過網絡和被手持設備處理的性能。一個明顯的因素是通過網絡的數據量。二進制格式的 protocol buffers 比文本格式的 XML 和 JSON 在通過網絡時小得多。然而,文本格式可以使用 gzip 進行有效地壓縮,這是 Web 服務器和 Android 設備都支持的標准技術。 圖 3 顯示了在打開和關閉 gzip 時通過網絡的數據大小:
圖 3. 不同格式的數據大小
圖 3 應該增加了您對 XML 和 JSON 之類的文本內容的壓縮效果的喜愛(更不用說 Web 格式、HTML、JavaScript 和 CSS 了)。protocol buffers 數據(大約 6KB)比原始 XML(大約 17.5KB)或 JSON(大約 13.5KB)數據小得多。但是一旦進行了壓縮, JSON 和 XML(都是大約 3KB)實際上比 protocol buffers 小很多了。在本例中,它們都接近於 protocol-buffers 編碼消息大小的一半了。
回到 圖 2,速度的不同顯然不能由通過網絡的消息大小解釋。protocol-buffers 消息比 XML 或 JSON 編碼的消息大,但是通過使用 protocol buffers,您仍然能夠削減半秒鐘的用戶等待時間。這是否意味著應該在 Android 應用程序中使用 protocol buffers 呢?這樣的決定很少是固定的。如果要發送的數據量很小,則三種格式間的差異也不大。對於大量數據而言,protocol buffers 可能會有所不同。但是,像這樣精心設計的基准測試無法替代對您自己的應用程序的測試。
結束語
本文介紹了如何使用 Internet 上流行的兩種數據格式 XML 和 JSON 的方方面面。還講到了第三種可能性,protocol buffers。像軟件工程中的其他內容一樣,選擇技術主要就是權衡利弊。當您為一個局限的環境(比如 Android)開發時,這些決定的結果往往被放大了。我希望您現在擁有的關於這些後果的額外知識能夠幫助您創建出色的 Android 應用程序。
下載
描述 名字 大小 下載方法 Article source code daytrader.stockbroker.zip 4.81MB HTTP
本文實例講述了Android學習筆記之應用單元測試。分享給大家供大家參考,具體如下: 第一步:在AndroidManifest.xml中加入如下兩段代碼: &
就目前的互聯網發展來看,已經有越來越多互聯網企業都在Android平台上部署其客戶端,並且為了提升用戶體驗,這些客戶端都做得布局合理而且美觀。本文所要介紹的And
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我
本文首先解釋一下handler是用來干嘛的,然後通過例子介紹其在多線程中的應用。 什麼是Handler handler通俗一點講就是用來在各個進程之間發送數據的處