Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 高級開發 >> 享受Android應用程序的Java技術盛宴(1)

享受Android應用程序的Java技術盛宴(1)

編輯:高級開發

盡管很多人反感“android應該改名叫Java GE”這種說法,但是沒人能否認Java語言是android開發人員所選的必備工具。android運行時使用自己的虛擬機Dalvik,這並不是多數程序開發人員使用的普通Java虛擬機。Dalvik支持Java編程語言的大部分功能—但並不是全部。在本文中,您將學習高級Java功能及其如何在android中實現。這些功能包括並發性、聯網和數據庫訪問。

51CTO推薦:android開發專題

android應用程序一個最常見的任務就是檢索數據或通過網絡將數據發送到遠程服務器。這一操作的結果通常是一些您想要展示給用戶的新數據。這意味著您需要修改用戶界面。大多數開發人員知道您將不會執行一個潛在的長期運行任務,例如,在主UI線程上通過網絡訪問數據(特別使用一個網絡連接非常慢的手機)。凍結您的應用程序直至長期運行任務完成。事實上,如果這個任務超過5秒,android操作系統將出現臭名昭著的Application Not Responding對話框。

Application Not Responding 對話框
圖1.android臭名昭著的Application Not Responding對話框

您不可能知道用戶網絡連接能有多慢。為了避免冒險,您必須在不同的線程上執行任務,或者至少不在主UI線程上執行。許多Android應用程序,但不是全部,需要處理多線程,由此引起並發。應用程序經常需要本地保存數據,android數據庫是一個很好的選擇。這三個場景(不同線程,並發和本地保存數據)在Java環境中有許多標准方法可以用來處理。然而,正如您將要看到的,android提供不同的選擇。讓我們逐個看看,看看其優點和缺點。

android網絡

通過網絡使用Java編程進行調用是簡單的,我們熟悉的java.net包含幾個執行此操作的類。這些類大多數在android中都可用,事實上,您可以使用像java.net.URL和Java.Net.URLConnection這樣的類,就像您在其他Java應用程序中那樣。然而,Android包括pacheHttpClIEnt庫,這是在android上連接網絡的首選方法。即使您使用常用Java類,android實現仍然使用HttpClIEnt。清單1顯示了一個使用這個必不可少的庫的示例。

清單1.在android上使用Http ClIEnt庫

  1. private ArrayList<Stock> fetchStockData(Stock[] oldStocks)
  2. throws ClIEntProtocolException, IOException{
  3. StringBuilder sb = new StringBuilder();
  4. for (Stock stock : oldStocks){
  5. sb.append(stock.getSymbol());
  6. sb.append('+');
  7. }
  8. sb.deleteCharAt(sb.length() - 1);
  9. String urlStr =
  10. "http://finance.yahoo.com/d/quotes.csv?f=sb2n&s=" +
  11. sb.toString();
  12. HttpClIEnt clIEnt = new DefaultHttpClIEnt();
  13. HttpGet request = new HttpGet(urlStr.toString());
  14. HttpResponse response = clIEnt.execute(request);
  15. BufferedReader reader = new BufferedReader(
  16. new InputStreamReader(response.getEntity().getContent()));
  17. String line = reader.readLine();
  18. int i = 0;
  19. ArrayList<Stock> newnewStocks = new ArrayList<Stock>(oldStocks.length);
  20. while (line != null){
  21. String[] values = line.split(",");
  22. Stock stock = new Stock(oldStocks[i], oldStocks[i].getId());
  23. stock.setCurrentPrice(Double.parseDouble(values[1]));
  24. stock.setName(values[2]);
  25. newStocks.add(stock);
  26. line = reader.readLine();
  27. i++;
  28. }
  29. return newStocks;
  30. }

在這段代碼中有一組Stock對象。這是基本的數據結構對象,保存用戶擁有股票信息(比如,代號、價格等)以及更多的個人信息(比如,用戶付了多少錢)。您可以使用HttpClient類從Yahoo Finance檢索動態數據(例如,這支股票目前的價格)。HttpClIEnt包含一個HttpUriRequest,在本例中,您可以使用HttpGet,這是HttpUriRequest的一個子類。類似地,當您需要向遠程服務器發送數據時,可以使用HttpPost類,當您從客戶端得到HttpResponse時,您能接觸到響應的潛在InputStream、對其進行緩沖、解析來獲取股票信息。

現在,您看到了如何通過網絡檢索數據、如何用這個數據來通過使用多線程智能地更新android UI。


android並發性實踐

如果您在應用程序的主UI線程上運行清單1中的代碼,可能會出現Application Not Responding對話框,具體視用戶網絡速度而定。因此必須確定生成一個線程來獲取數據。清單2顯示了一種解決方法。

清單2.Na&iUML;ve多線程(別這樣,這行不通!)

  1. private void refreshStockData(){
  2. Runnable task = new Runnable(){
  3. public void run() {
  4. try {
  5. ArrayList<Stock> newStocks =
  6. fetchStockData(stocks.toArray(
  7. new Stock[stocks.size()]));
  8. for (int i=0;i<stocks.size();i++){
  9. Stock s = stocks.get(i);
  10. s.setCurrentPrice(
  11. newStocks.get(i).getCurrentPrice());
  12. s.setName(newStocks.get(i).getName());
  13. refresh();
  14. }
  15. } catch (Exception e) {
  16. Log.e("StockPortfolioVIEwStocks",
  17. "Exception getting stock data", e);
  18. }
  19. }
  20. };
  21. Thread t = new Thread(task);
  22. t.start();
  23. }

清單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

  1. private void refreshStockData(){
  2. final ArrayList<Stock> localStocks =
  3. new ArrayList<Stock>(stocks.size());
  4. for (Stock stock : stocks){
  5. localStocks.add(new Stock(stock, stock.getId()));
  6. }
  7. final Handler handler = new Handler(){
  8. @Override
  9. public void handleMessage(Message msg) {
  10. for (int i=0;i<stocks.size();i++){
  11. stocks.set(i, localStocks.get(i));
  12. }
  13. refresh();
  14. }
  15. };
  16. Runnable task = new Runnable(){
  17. public void run() {
  18. try {
  19. ArrayList<Stock> newStocks =
  20. fetchStockData(localStocks.toArray(
  21. new Stock[localStocks.size()]));
  22. for (int i=0;i<localStocks.size();i++){
  23. Stock ns = newStocks.get(i);
  24. Stock ls = localStocks.get(i);
  25. ls.setName(ns.getName());
  26. ls.setCurrentPrice(ns.getCurrentPrice());
  27. }
  28. handler.sendEmptyMessage(RESULT_OK);
  29. } catch (Exception e) {
  30. Log.e("StockPortfolioVIEwStocks",
  31. "Exception getting stock data", e);
  32. }
  33. }
  34. };
  35. Thread dataThread = new Thread(task);
  36. dataThread.start();
  37. }

在清單2和清單3中的代碼有兩個主要的不同。明顯的差異是Handler的存在。第二個不同是,在衍生線程中,您不能修改UI。相反的,當您將消息發送到Handler,然後由Handler來修改UI。也要注意,在線程中您不能修改stocks成員變量,正如您之前所做的。相反地您可以修改數據的本地副本。嚴格地來說,這是不是必須的,但這更為安全

清單3說明了在並發編程中一些非常普遍的模式:復制數據、將數據解析到執行長期任務的線程中、將結果數據傳遞回主UI線程、以及根據所屬數據更新主UI線程。Handlers是Android中的主要通信機制,它們使這個模式易於實現。然而,清單3中仍然有一些樣本代碼。幸好,android提供方法來封裝和消除大多數樣本代碼。清單4演示了這一過程。

清單4.用一個AsyncTask使多線程更容易

  1. private void refreshStockData() {
  2. new AsyncTask<Stock, Void, ArrayList<Stock>>(){
  3. @Override
  4. protected void onPostExecute(ArrayList<Stock> result) {
  5. VIEwStocks.this.stocks = result;
  6. refresh();
  7. }
  8. @Override
  9. protected ArrayList<Stock> doInBackground(Stock... stocks){
  10. try {
  11. return fetchStockData(stocks);
  12. } catch (Exception e) {
  13. Log.e("StockPortfolioVIEwStocks", "Exception getting stock data", e);
  14. }
  15. return null;
  16. }
  17. }.execute(stocks.toArray(new Stock[stocks.size()]));
  18. }

如您所見,清單4比起清單3樣本代碼明顯減少。您不能創建任何線程或Handlers。使用AsyncTask來封裝所有樣本代碼。要創建AsyncTask,您必須實現doInBackground方法。該方法總是在獨立的線程中執行,因此您可以自由調用長期運行任務。它的輸入類型來自您所創建的AsyncTask的類型參數。在本例中,第一個類型參數是Stock,因此doInBackground獲得傳遞給它的一組Stock對象。類似地,它返回一個ArrayList,因為這是AsyncTask的第三個類型參數。在此例中,我也選擇重寫onPostExecute方法。這是一個可選方法,如果您需要使用從doInBackground返回的數據來進行一些操作,您可以選用這種方法來實現。這個方法總是在主UI線程上被執行,因此對於修改UI這是一個很好的選擇。

有了AsyncTask,您就完全可以簡化多線程代碼。它可以將許多並發陷阱從您的開發路徑刪除,您仍然可以使用AsyncTask尋找一些潛在問題,例如,在doInBackground方法對象執行的同時設備上的方向發生改變時可能發生什麼。更多關於如何處理這類案例的技術,見參考資料的鏈接。

現在我們開始討論另一個常見任務,其中android明顯背離常用的Java方法——使用數據庫進行處理。


android數據庫連通性

android中一個非常有用的特征就是存在本地關系數據庫。保證您能在本地文件中存儲您的數據,但通常更有用的是使用一個關系型數據庫管理系統(RelationalDatabaseManagementSystem,RDBMS)來存儲。android提供給您常用的SQLite數據庫來進行處理,因為對於像Android這類嵌入式系統它是高度優化的。它被Android上的核心應用程序所用。例如,用戶地址簿是存儲在一個SQLite數據庫中。現在,對於給定的android的Java實現,您可以使用JDBC來訪問這些數據庫。出人意料的是,android甚至包括構成主要部分JDBC API的java.sql和Javax.sql包。然而,當涉及使用本地android數據庫進行處理時,這毫無用處。相反地,您想要使用android.database和android.database.sqlite包。清單5是一個使用這些類存儲和檢索數據的示例。

清單5.使用android進行數據庫訪問

  1. public class StocksDb {
  2. private static final String DB_NAME = "stocks.db";
  3. private static final int DB_VERSION = 1;
  4. private static final String TABLE_NAME = "stock";
  5. private static final String CREATE_TABLE = "CREATE TABLE " +
  6. TABLE_NAME + " (id INTEGER PRIMARY KEY, symbol TEXT, max_price DECIMAL(8,2), " +
  7. "min_price DECIMAL(8,2), price_paid DECIMAL(8,2), " +
  8. "quantity INTEGER)";
  9. private static final String INSERT_SQL = "INSERT INTO " + TABLE_NAME +
  10. " (symbol, max_price, min_price, price_paid, quantity) " +
  11. "VALUES (?,?,?,?,?)";
  12. private static final String READ_SQL = "SELECT id, symbol, max_price, " +
  13. "min_price, price_paid, quantity FROM " + TABLE_NAME;
  14. private final Context context;
  15. private final SQLiteOpenHelper helper;
  16. private final SQLiteStatement stmt;
  17. private final SQLiteDatabase db;
  18. public StocksDb(Context context){
  19. this.context = context;
  20. helper = new SQLiteOpenHelper(context, DB_NAME, null,
  21. DB_VERSION){
  22. @Override
  23. public void onCreate(SQLiteDatabase db) {
  24. db.execSQL(CREATE_TABLE);
  25. }
  26. @Override
  27. public void onUpgrade(SQLiteDatabase db, int oldVersion,
  28. int newVersion) {
  29. throw new UnsupportedOperationException();
  30. }
  31. };
  32. db = helper.getWritableDatabase();
  33. stmt = db.compileStatement(INSERT_SQL);
  34. }
  35. public Stock addStock(Stock stock){
  36. stmt.bindString(1, stock.getSymbol());
  37. stmt.bindDouble(2, stock.getMaxPrice());
  38. stmt.bindDouble(3, stock.getMinPrice());
  39. stmt.bindDouble(4, stock.getPricePaid());
  40. stmt.bindLong(5, stock.getQuantity());
  41. int id = (int) stmt.executeInsert();
  42. return new Stock (stock, id);
  43. }
  44. public ArrayList<Stock> getStocks() {
  45. Cursor results = db.rawQuery(READ_SQL, null);
  46. ArrayList<Stock> stocks =
  47. new ArrayList<Stock>(results.getCount());
  48. if (results.moveToFirst()){
  49. int idCol = results.getColumnIndex("id");
  50. int symbolCol = results.getColumnIndex("symbol");
  51. int maxCol = results.getColumnIndex("max_price");
  52. int minCol = results.getColumnIndex("min_price");
  53. int priceCol = results.getColumnIndex("price_paid");
  54. int quanitytCol = results.getColumnIndex("quantity");
  55. do {
  56. Stock stock = new Stock(results.getString(symbolCol),
  57. results.getDouble(priceCol),
  58. results.getInt(quanitytCol),
  59. results.getInt(idCol));
  60. stock.setMaxPrice(results.getDouble(maxCol));
  61. stock.setMinPrice(results.getDouble(minCol));
  62. stocks.add(stock);
  63. } while (results.moveToNext());
  64. }
  65. if (!results.isClosed()){
  66. results.close();
  67. }
  68. return stocks;
  69. }
  70. public void close(){
  71. helper.close();
  72. }
  73. }

清單5中的類完全封裝了一個用於存儲股票信息的SQLite數據庫。因為您將要使用一個嵌入式數據庫,不僅是您的應用程序要使用它,而且也要通過應用程序來創建它。您需要提供代碼來創建該數據庫。android提供一個有用的抽象幫助類SQLiteOpenHelper。要完成這一操作,您需要擴展這個抽象類並提供代碼通過使用onCreate方法創建您的數據庫。當您有一個幫助程序實例時,就可以獲取一個SQLiteDatabase實例,您可以用來執行任意SQL語句。您的數據庫類有兩個較為方便的方法。第一個是addStock,用於將新股票保存到數據庫中。注意,您使用了一個SQLiteStatement實例,這類似於一個Java.sql.PreparedStatement。需要注意的是,在您的類構造器中如何對其進行編譯,使其在每次調用addStock時都能重復利用。在每個addStock調用中,SQLiteStatement的變量(INSERT_SQL字符串中的問號)必然要將數據傳遞給addStock。再一次強調,這類似於PreparedStatement,您可以從JDBC了解它。

另一個方法是getStocks。顧名思義,它從數據庫中檢索所有股票。注意,您再次使用一個SQL字符串,正如您在JDBC中所用的那樣。您可以在SQLiteDatabase類上通過使用rawQuery方法來進行處理。這個類也有幾個查詢方法,讓您可以不使用SQL直接查詢數據庫。所有這些方法都返回一個Cursor對象,和Java.sql.ResultSet非常相似。您可以將Cursor移動到從數據庫中返回的數據所在行,在每一行,您可以使用getInt、getString和其他的方法來檢索您要查詢的數據庫中各列相關的值。再一次強調,這和ResultSet十分相似。也和ResultSet比較相似,當您完成操作之後,關閉Cursor也十分重要的。如果您沒有關閉Cursors,那麼可能會迅速地耗盡內存並導致您的應用程序崩潰。

查詢本地數據庫是一個比較慢的過程,特別是,如果您有多行數據或者您需要在多個表之間運行復雜的查詢語句。然而,數據庫查詢或插入超過5秒且出現一個Application Not Responding對話框,這種情況不太可能發生,但是當您的數據庫忙於讀取和寫入數據時,凍結您的UI是不明智的。當然,避免這種情況最好的辦法是使用AsyncTask。清單6展示了這個示例。

清單6.在一個單獨的線程上插入數據庫

  1. Button button = (Button) findVIEwById(R.id.btn);
  2. button.setOnClickListener(new OnClickListener(){
  3. public void onClick(VIEw v) {
  4. String symbol = symbolIn.getText().toString();
  5. symbolIn.setText("");
  6. double max = Double.parseDouble(maxIn.getText().toString());
  7. maxIn.setText("");
  8. double min = Double.parseDouble(minIn.getText().toString());
  9. minIn.setText("");
  10. double pricePaid =
  11. Double.parseDouble(priceIn.getText().toString());
  12. priceIn.setText("");
  13. int quantity = Integer.parseInt(quantIn.getText().toString());
  14. quantIn.setText("");
  15. Stock stock = new Stock(symbol, pricePaid, quantity);
  16. stock.setMaxPrice(max);
  17. stock.setMinPrice(min);
  18. new AsyncTask<Stock,Void,Stock>(){
  19. @Override
  20. protected Stock doInBackground(Stock... newStocks) {
  21. // There can be only one!
  22. return db.addStock(newStocks[0]);
  23. }
  24. @Override
  25. protected void onPostExecute(Stock s){
  26. addStockAndRefresh(s);
  27. }
  28. }.execute(stock);
  29. }
  30. });

您可以先為按鈕創建一個實踐監聽器。當用戶點擊按鈕時,您可以從各個小部件(確切地說是EditText小部件)讀取股票數據並填入一個新的Stock對象。您可以創建一個AsyncTask,並通過doInBackground方法從清單5中調用addStock方法。如此,addStock將在一個背景線程上執行,而不是在主UI線程上。完成之後,將新Stock對象從數據庫傳遞到在主UI線程上執行的addStockAndRefresh方法。

作者簡介:Michael Galpin是eBay的一名架構師。他從1998年開始做職業程序員,並擁有加州理工學院數學專業的學士學位。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved