`
yidongkaifa
  • 浏览: 4059486 次
文章分类
社区版块
存档分类
最新评论

Android应用程序的Java技术2

 
阅读更多

Android并发性实践

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

清单2.Naïve多线程(别这样,这行不通!)

  1. privatevoidrefreshStockData(){
  2. Runnabletask=newRunnable(){
  3. publicvoidrun(){
  4. try{
  5. ArrayList<Stock>newStocks=
  6. fetchStockData(stocks.toArray(
  7. newStock[stocks.size()]));
  8. for(inti=0;i<stocks.size();i++){
  9. Stocks=stocks.get(i);
  10. s.setCurrentPrice(
  11. newStocks.get(i).getCurrentPrice());
  12. s.setName(newStocks.get(i).getName());
  13. refresh();
  14. }
  15. }catch(Exceptione){
  16. Log.e("StockPortfolioViewStocks",
  17. "Exceptiongettingstockdata",e);
  18. }
  19. }
  20. };
  21. Threadt=newThread(task);
  22. t.start();
  23. }

清单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. privatevoidrefreshStockData(){
  2. finalArrayList<Stock>localStocks=
  3. newArrayList<Stock>(stocks.size());
  4. for(Stockstock:stocks){
  5. localStocks.add(newStock(stock,stock.getId()));
  6. }
  7. finalHandlerhandler=newHandler(){
  8. @Override
  9. publicvoidhandleMessage(Messagemsg){
  10. for(inti=0;i<stocks.size();i++){
  11. stocks.set(i,localStocks.get(i));
  12. }
  13. refresh();
  14. }
  15. };
  16. Runnabletask=newRunnable(){
  17. publicvoidrun(){
  18. try{
  19. ArrayList<Stock>newStocks=
  20. fetchStockData(localStocks.toArray(
  21. newStock[localStocks.size()]));
  22. for(inti=0;i<localStocks.size();i++){
  23. Stockns=newStocks.get(i);
  24. Stockls=localStocks.get(i);
  25. ls.setName(ns.getName());
  26. ls.setCurrentPrice(ns.getCurrentPrice());
  27. }
  28. handler.sendEmptyMessage(RESULT_OK);
  29. }catch(Exceptione){
  30. Log.e("StockPortfolioViewStocks",
  31. "Exceptiongettingstockdata",e);
  32. }
  33. }
  34. };
  35. ThreaddataThread=newThread(task);
  36. dataThread.start();
  37. }

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

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

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

  1. privatevoidrefreshStockData(){
  2. newAsyncTask<Stock,Void,ArrayList<Stock>>(){
  3. @Override
  4. protectedvoidonPostExecute(ArrayList<Stock>result){
  5. ViewStocks.this.stocks=result;
  6. refresh();
  7. }
  8. @Override
  9. protectedArrayList<Stock>doInBackground(Stock...stocks){
  10. try{
  11. returnfetchStockData(stocks);
  12. }catch(Exceptione){
  13. Log.e("StockPortfolioViewStocks","Exceptiongettingstockdata",e);
  14. }
  15. returnnull;
  16. }
  17. }.execute(stocks.toArray(newStock[stocks.size()]));
  18. }

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

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

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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics