Android并发性实践
如果您在应用程序的主UI线程上运行清单1中的代码,可能会出现Application Not Responding对话框,具体视用户网络速度而定。因此必须确定生成一个线程来获取数据。清单2显示了一种解决方法。
清单2.Naïve多线程(别这样,这行不通!)
- privatevoidrefreshStockData(){
- Runnabletask=newRunnable(){
- publicvoidrun(){
- try{
- ArrayList<Stock>newStocks=
- fetchStockData(stocks.toArray(
- newStock[stocks.size()]));
- for(inti=0;i<stocks.size();i++){
- Stocks=stocks.get(i);
- s.setCurrentPrice(
- newStocks.get(i).getCurrentPrice());
- s.setName(newStocks.get(i).getName());
- refresh();
- }
- }catch(Exceptione){
- Log.e("StockPortfolioViewStocks",
- "Exceptiongettingstockdata",e);
- }
- }
- };
- Threadt=newThread(task);
- 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
- privatevoidrefreshStockData(){
- finalArrayList<Stock>localStocks=
- newArrayList<Stock>(stocks.size());
- for(Stockstock:stocks){
- localStocks.add(newStock(stock,stock.getId()));
- }
- finalHandlerhandler=newHandler(){
- @Override
- publicvoidhandleMessage(Messagemsg){
- for(inti=0;i<stocks.size();i++){
- stocks.set(i,localStocks.get(i));
- }
- refresh();
- }
- };
- Runnabletask=newRunnable(){
- publicvoidrun(){
- try{
- ArrayList<Stock>newStocks=
- fetchStockData(localStocks.toArray(
- newStock[localStocks.size()]));
- for(inti=0;i<localStocks.size();i++){
- Stockns=newStocks.get(i);
- Stockls=localStocks.get(i);
- ls.setName(ns.getName());
- ls.setCurrentPrice(ns.getCurrentPrice());
- }
- handler.sendEmptyMessage(RESULT_OK);
- }catch(Exceptione){
- Log.e("StockPortfolioViewStocks",
- "Exceptiongettingstockdata",e);
- }
- }
- };
- ThreaddataThread=newThread(task);
- dataThread.start();
- }
在清单2和清单3中的代码有两个主要的不同。明显的差异是Handler的存在。第二个不同是,在衍生线程中,您不能修改UI。相反的,当您将消息发送到Handler,然后由Handler来修改UI。也要注意,在线程中您不能修改stocks成员变量,正如您之前所做的。相反地您可以修改数据的本地副本。严格地来说,这是不是必须的,但这更为安全。
清单3说明了在并发编程中一些非常普遍的模式:复制数据、将数据解析到执行长期任务的线程中、将结果数据传递回主UI线程、以及根据所属数据更新主UI线程。Handlers是Android中的主要通信机制,它们使这个模式易于实现。然而,清单3中仍然有一些样本代码。幸好,Android提供方法来封装和消除大多数样本代码。清单4演示了这一过程。
清单4.用一个AsyncTask使多线程更容易
- privatevoidrefreshStockData(){
- newAsyncTask<Stock,Void,ArrayList<Stock>>(){
- @Override
- protectedvoidonPostExecute(ArrayList<Stock>result){
- ViewStocks.this.stocks=result;
- refresh();
- }
- @Override
- protectedArrayList<Stock>doInBackground(Stock...stocks){
- try{
- returnfetchStockData(stocks);
- }catch(Exceptione){
- Log.e("StockPortfolioViewStocks","Exceptiongettingstockdata",e);
- }
- returnnull;
- }
- }.execute(stocks.toArray(newStock[stocks.size()]));
- }
如您所见,清单4比起清单3样本代码明显减少。您不能创建任何线程或Handlers。使用AsyncTask来封装所有样本代码。要创建AsyncTask,您必须实现doInBackground方法。该方法总是在独立的线程中执行,因此您可以自由调用长期运行任务。它的输入类型来自您所创建的AsyncTask的类型参数。在本例中,第一个类型参数是Stock,因此doInBackground获得传递给它的一组Stock对象。类似地,它返回一个ArrayList,因为这是AsyncTask的第三个类型参数。在此例中,我也选择重写onPostExecute方法。这是一个可选方法,如果您需要使用从doInBackground返回的数据来进行一些操作,您可以选用这种方法来实现。这个方法总是在主UI线程上被执行,因此对于修改UI这是一个很好的选择。
有了AsyncTask,您就完全可以简化多线程代码。它可以将许多并发陷阱从您的开发路径删除,您仍然可以使用AsyncTask寻找一些潜在问题,例如,在doInBackground方法对象执行的同时设备上的方向发生改变时可能发生什么。更多关于如何处理这类案例的技术,见参考资料的链接。
现在我们开始讨论另一个常见任务,其中Android明显背离常用的Java方法——使用数据库进行处理。