享受Android应用程序的Java技术盛宴

移动开发 Android
本文介绍几种处理复杂情况的Android SDK工具。开发Android应用程序,需要最新版Android SDK,这需要一个Java Development Kit(JDK)。

尽管很多人反感“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;  

在这段代码中有一组Stock对象。这是基本的数据结构对象,保存用户拥有股票信息(比如,代号、价格等)以及更多的个人信息(比如,用户付了多少钱)。您可以使用HttpClient类从Yahoo Finance检索动态数据(例如,这支股票目前的价格)。HttpClient包含一个HttpUriRequest,在本例中,您可以使用HttpGet,这是HttpUriRequest的一个子类。类似地,当您需要向远程服务器发送数据时,可以使用HttpPost类,当您从客户端得到HttpResponse时,您能接触到响应的潜在InputStream、对其进行缓冲、解析来获取股票信息。

现在,您看到了如何通过网络检索数据、如何用这个数据来通过使用多线程智能地更新Android UI。

#p#

Android并发性实践

如果您在应用程序的主UI线程上运行清单1中的代码,可能会出现Application Not Responding对话框,具体视用户网络速度而定。因此必须确定生成一个线程来获取数据。清单2显示了一种解决方法。

清单2.Naï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();  

清单2的标题声明这是naï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();  

在清单2和清单3中的代码有两个主要的不同。明显的差异是Handler的存在。第二个不同是,在衍生线程中,您不能修改UI。相反的,当您将消息发送到Handler,然后由Handler来修改UI。也要注意,在线程中您不能修改stocks成员变量,正如您之前所做的。相反地您可以修改数据的本地副本。严格地来说,这是不是必须的,但这更为安全。

清单3说明了在并发编程中一些非常普遍的模式:复制数据、将数据解析到执行长期任务的线程中、将结果数据传递回主UI线程、以及根据所属数据更新主UI线程。Handlers是Android中的主要通信机制,它们使这个模式易于实现。然而,清单3中仍然有一些样本代码。幸好,Android提供方法来封装和消除大多数样本代码。清单4演示了这一过程。

清单4.用一个AsyncTask使多线程更容易

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

如您所见,清单4比起清单3样本代码明显减少。您不能创建任何线程或Handlers。使用AsyncTask来封装所有样本代码。要创建AsyncTask,您必须实现doInBackground方法。该方法总是在独立的线程中执行,因此您可以自由调用长期运行任务。它的输入类型来自您所创建的AsyncTask的类型参数。在本例中,***个类型参数是Stock,因此doInBackground获得传递给它的一组Stock对象。类似地,它返回一个ArrayList,因为这是AsyncTask的第三个类型参数。在此例中,我也选择重写onPostExecute方法。这是一个可选方法,如果您需要使用从doInBackground返回的数据来进行一些操作,您可以选用这种方法来实现。这个方法总是在主UI线程上被执行,因此对于修改UI这是一个很好的选择。

有了AsyncTask,您就完全可以简化多线程代码。它可以将许多并发陷阱从您的开发路径删除,您仍然可以使用AsyncTask寻找一些潜在问题,例如,在doInBackground方法对象执行的同时设备上的方向发生改变时可能发生什么。更多关于如何处理这类案例的技术,见参考资料的链接。

现在我们开始讨论另一个常见任务,其中Android明显背离常用的Java方法——使用数据库进行处理。

#p#

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.  
  27.             @Override  
  28.             public void onUpgrade(SQLiteDatabase db, int oldVersion,   
  29.                     int newVersion) {  
  30.                 throw new UnsupportedOperationException();  
  31.             }  
  32.         };  
  33.         db = helper.getWritableDatabase();  
  34.         stmt = db.compileStatement(INSERT_SQL);  
  35.     }  
  36.     public Stock addStock(Stock stock){  
  37.         stmt.bindString(1, stock.getSymbol());  
  38.         stmt.bindDouble(2, stock.getMaxPrice());  
  39.         stmt.bindDouble(3, stock.getMinPrice());  
  40.         stmt.bindDouble(4, stock.getPricePaid());  
  41.         stmt.bindLong(5, stock.getQuantity());  
  42.         int id = (int) stmt.executeInsert();  
  43.         return new Stock (stock, id);  
  44.     }  
  45.     public ArrayList<Stock> getStocks() {  
  46.         Cursor results = db.rawQuery(READ_SQL, null);  
  47.         ArrayList<Stock> stocks =   
  48.                  new ArrayList<Stock>(results.getCount());  
  49.         if (results.moveToFirst()){  
  50.             int idCol = results.getColumnIndex("id");  
  51.             int symbolCol = results.getColumnIndex("symbol");  
  52.             int maxCol = results.getColumnIndex("max_price");  
  53.             int minCol = results.getColumnIndex("min_price");  
  54.             int priceCol = results.getColumnIndex("price_paid");  
  55.             int quanitytCol = results.getColumnIndex("quantity");  
  56.             do {  
  57.                 Stock stock = new Stock(results.getString(symbolCol),   
  58.                         results.getDouble(priceCol),   
  59.                         results.getInt(quanitytCol),   
  60.                                     results.getInt(idCol));  
  61.                 stock.setMaxPrice(results.getDouble(maxCol));  
  62.                 stock.setMinPrice(results.getDouble(minCol));  
  63.                 stocks.add(stock);  
  64.             } while (results.moveToNext());  
  65.         }  
  66.         if (!results.isClosed()){  
  67.             results.close();  
  68.         }  
  69.         return stocks;  
  70.     }  
  71.     public void close(){  
  72.         helper.close();  
  73.     }      
  74. }  

清单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. 多图详解 “Android UI”设计官方教程
  2. Android应该改名叫Java GE
  3. 图文并茂 在MyEclipse 8.6上搭建Android开发环境
  4. 两大动力指引Android向更多领域进军
责任编辑:佚名 来源: developerworks
相关推荐

2012-04-06 14:23:53

技术门诊Android

2011-12-07 12:01:31

ibmdw

2022-09-27 15:16:42

开发Android应用程序

2012-04-25 22:56:10

Android

2010-03-03 16:45:46

Android应用程序

2011-05-24 16:09:57

Androi

2010-01-25 13:29:53

Android本地应用

2012-05-14 17:35:28

移动Web

2011-11-03 09:41:35

Android签名安全性

2010-02-07 10:25:11

Android

2010-03-02 14:24:00

Android应用程序

2017-03-02 11:10:39

AndroidMVVM应用程序

2010-02-05 18:21:24

Android应用程序

2011-05-27 10:13:39

Android 签名

2011-05-10 13:56:27

Android应用程序Android MarAndroid

2010-02-04 10:17:38

Android应用程序

2009-06-10 11:47:32

Android应用程序模块

2013-12-04 15:20:33

Android SDK应用程序

2010-03-04 16:21:15

Android Mar

2014-05-27 14:16:08

AndroidActivitysingleTask
点赞
收藏

51CTO技术栈公众号