編輯:開發入門
Android 應用程序一個最常見的任務就是檢索數據或通過網絡將數據發送到遠程服務器。這一操作的結果通常是一些您想要展示給用戶的新數據。這意味著您需要修改用戶界面。大多數開發人員知道您將不會執行一個潛在的長期運行任務,例如,在主 UI 線程上通過網絡訪問數據(特別使用一個網絡連接非常慢的手機)。凍結您的應用程序直至長期運行任務完成。事實上,如果這個任務超過 5 秒,android 操作系統將出現臭名昭著的 Application Not Responding
對話框,如 圖 1 所示。
圖 1. android 臭名昭著的 Application Not Responding 對話框
您不可能知道用戶網絡連接能有多慢。為了避免冒險,您必須在不同的線程上執行任務,或者至少不在主 UI 線程上執行。許多 Android 應用程序,但不是全部,需要處理多線程,由此引起並發。應用程序經常需要本地保存數據,android 數據庫是一個很好的選擇。這三個場景(不同線程,並發和本地保存數據)在 Java 環境中有許多標准方法可以用來處理。然而,正如您將要看到的,android 提供不同的選擇。讓我們逐個看看,看看其優點和缺點。
回頁首
android 網絡
通過網絡使用 Java 編程進行調用是簡單的,我們熟悉的 Java.Net
包含幾個執行此操作的類。這些類大多數在 android 中都可用,事實上,您可以使用像 Java.Net.URL
和 Java.Net.URLConnection
這樣的類,就像您在其他 Java 應用程序中那樣。然而,Android 包括 pache HttpClIEnt 庫,這是在 android 上連接網絡的首選方法。即使您使用常用 Java 類,android 實現仍然使用 HttpClIEnt。清單 1 顯示了一個使用這個必不可少的庫的示例。(所有源代碼見 下載。)
清單 1. 在 android 上使用 Http ClIEnt 庫
private ArrayList<Stock> fetchStockData(Stock[] oldStocks) throws ClientProtocolException, IOException{ StringBuilder sb = new StringBuilder(); for (Stock stock : oldStocks){ sb.append(stock.getSymbol()); sb.append('+'); } sb.deleteCharAt(sb.length() - 1); String urlStr = "http://finance.yahoo.com/d/quotes.csv?f=sb2n&s=" + sb.toString(); HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet(urlStr.toString()); HttpResponse response = clIEnt.execute(request); BufferedReader reader = new BufferedReader( new InputStreamReader(response.getEntity().getContent())); String line = reader.readLine(); int i = 0; ArrayList<Stock> newStocks = new ArrayList<Stock>(oldStocks.length); while (line != null){ String[] values = line.split(","); Stock stock = new Stock(oldStocks[i], oldStocks[i].getId()); stock.setCurrentPrice(Double.parseDouble(values[1])); stock.setName(values[2]); newStocks.add(stock); line = reader.readLine(); i++; } return newStocks; }
在這段代碼中有一組 Stock
對象。這是基本的數據結構對象,保存用戶擁有股票信息(比如,代號、價格等)以及更多的個人信息(比如,用戶付了多少錢)。您可以使用 HttpClIEnt
類從 Yahoo Finance 檢索動態數據(例如,這支股票目前的價格)。HttpClIEnt
包含一個 HttpUriRequest
,在本例中,您可以使用 HttpGet
,這是HttpUriRequest
的一個子類。類似地,當您需要向遠程服務器發送數據時,可以使用 HttpPost
類,當您從客戶端得到 HttpResponse
時,您能接觸到響應的潛在 InputStream
、對其進行緩沖、解析來獲取股票信息。
現在,您看到了如何通過網絡檢索數據、如何用這個數據來通過使用多線程智能地更新 android UI。
回頁首
android 並發性實踐
如果您在應用程序的主 UI 線程上運行 清單 1 中的代碼,可能會出現 Application Not Responding
對話框,具體視用戶網絡速度而定。因此必須確定生成一個線程來獲取數據。清單 2 顯示了一種解決方法。
清單 2. Na&iUML;ve 多線程(別這樣,這行不通!)
private void refreshStockData(){ Runnable task = new Runnable(){ public void run() { try { ArrayList<Stock> newStocks = fetchStockData(stocks.toArray( new Stock[stocks.size()])); for (int i=0;i<stocks.size();i++){ Stock s = stocks.get(i); s.setCurrentPrice( newStocks.get(i).getCurrentPrice()); s.setName(newStocks.get(i).getName()); refresh(); } } catch (Exception e) { Log.e("StockPortfolioVIEwStocks", "Exception getting stock data", e); } } }; Thread t = new Thread(task); t.start(); }
清單 2 的標題聲明這是 na&iUML;ve 代碼,確實是。在這個例子中,您將調用 清單 1 中的 fetchStockData
方法,將其封裝在 Runnable
對象中,並在一個新線程中執行。在這個新線程中,您可以訪問stocks
,一個封裝 Activity
(此類創建了 UI)的成員變量。顧名思義,這是 Stock
對象的一個數據結構(本例中是Java.util.ArrayList
)。換句話說,您在兩個線程之間共享數據,主 UI 線程和衍生(spawned)線程(在 清單 2 中調用)。當您修改了衍生線程中的共享數據時,通過在 Activity
對象上調用refresh
方法來更新 UI。
如果您編寫了 Java Swing 應用程序,您可能需要遵循一個像這樣的模式。然而,這在 android 中將不能正常工作。衍生線程根本不能修改 UI。因此在不凍結 UI ,但另一方面,在數據收到之後又允許您修改 UI 的情況下,您怎樣檢索數據?android.os.Handler
類允許您在線程之間協調和通信。清單 3 顯示了一個使用 Handler
的已更新 refreshStockData
方法。
清單 3. 實際工作的多線程 — 通過使用 Handler
private void refreshStockData(){ final ArrayList<Stock> localStocks = new ArrayList<Stock>(stocks.size()); for (Stock stock : stocks){ localStocks.add(new Stock(stock, stock.getId())); } final Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { for (int i=0;i<stocks.size();i++){ stocks.set(i, localStocks.get(i)); } refresh(); } }; Runnable task = new Runnable(){ public void run() { try { ArrayList<Stock> newStocks = fetchStockData(localStocks.toArray( new Stock[localStocks.size()])); for (int i=0;i<localStocks.size();i++){ Stock ns = newStocks.get(i); Stock ls = localStocks.get(i); ls.setName(ns.getName()); ls.setCurrentPrice(ns.getCurrentPrice()); } handler.sendEmptyMessage(RESULT_OK); } catch (Exception e) { Log.e("StockPortfolioVIEwStocks", "Exception getting stock data", e); } } }; Thread dataThread = new Thread(task); dataThread.start(); }
在 清單 2 和 清單 3 中的代碼有兩個主要的不同。明顯的差異是Handler
的存在。第二個不同是,在衍生線程中,您不能修改 UI。相反的,當您將消息發送到 Handler
,然後由 Handler
來修改 UI。也要注意,在線程中您不能修改 stocks
成員變量,正如您之前所做的。相反地您可以修改數據的本地副本。嚴格地來說,這是不是必須的,但這更為安全。
清單 3 說明了在並發編程中一些非常普遍的模式:復制數據、將數據解析到執行長期任務的線程中、將結果數據傳遞回主 UI 線程、以及根據所屬數據更新主 UI 線程。Handlers
是 android 中的主要通信機制,它們使這個模式易於實現。然而,清單 3 中仍然有一些樣本代碼。幸好,android 提供方法來封裝和消除大多數樣本代碼。清單 4 演示了這一過程。
清單 4. 用一個 AsyncTask
使多線程更容易
private void refreshStockData() { new AsyncTask<Stock, Void, ArrayList<Stock>>(){ @Override protected void onPostExecute(ArrayList<Stock> result) { ViewStocks.this.stocks = result; refresh(); } @Override protected ArrayList<Stock> doInBackground(Stock... stocks){ try { return fetchStockData(stocks); } catch (Exception e) { Log.e("StockPortfolioVIEwStocks", "Exception getting stock data", e); } return null; } }.execute(stocks.toArray(new Stock[stocks.size()])); }
如您所見,清單 4 比起 清單 3 樣本代碼明顯減少。您不能創建任何線程或 Handlers
。使用 AsyncTask
來封裝所有樣本代碼。要創建AsyncTask
,您必須實現 doInBackground
方法。該方法總是在獨立的線程中執行,因此您可以自由調用長期運行任務。它的輸入類型來自您所創建的 AsyncTask
的類型參數。在本例中,第一個類型參數是 Stock
,因此 doInBackground
獲得傳遞給它的一組 Stock
對象。類似地,它返回一個 ArrayList<Stock>
,因為這是 AsyncTask
的第三個類型參數。在此例中,我也選擇重寫 onPostExecute
方法。這是一個可選方法,如果您需要使用從 doInBackground
返回的數據來進行一些操作,您可以選用這種方法來實現。這個方法總是在主 UI 線程上被執行,因此對於修改 UI 這是一個很好的選擇。
有了 AsyncTask
,您就完全可以簡化多線程代碼。它可以將許多並發陷阱從您的開發路徑刪除,您仍然可以使用 AsyncTask
尋找一些潛在問題,例如,在 doInBackground
方法對象執行的同時設備上的方向發生改變時可能發生什麼。更多關於如何處理這類案例的技術,見參考資料 的鏈接。
現在我們開始討論另一個常見任務,其中 android 明顯背離常用的Java 方法 — 使用數據庫進行處理。
android開發平台是開放的平台,而位於四層框架頂端的應用開發,必然涉及到android組件。本文將為大家詳細介紹android組件。 組件(Component)
連接 JavaScript 接口下一步是啟用 Activity 中的 Java 代碼,以與 WebVIEw 管理的 Html 文件中的 JavaScript 代碼交互。
創建內容提供器和 Google Maps 應用程序您已經看到了一個完整的應用程序示例,現在簡要討論一下更加復雜的應用程序。內容提供器和 Google Maps本教程中討
開始之前本教程介紹了如何在 android 平台之上處理 XML。要按照本教程構建樣例應用程序,必須在開發計算機中安裝和運行 Android SDK。推薦使用 Ecli