【转】 AyncTask 实战 模拟GridView 动态更新效果

来源:互联网 发布:mac 一键关闭所有窗口 编辑:程序博客网 时间:2024/06/05 05:00

5点多就在睡梦中惊醒,睡不着,于是醒着也是醒着,还不如跟进下项目,于是网上看到这样一篇文章,刚好解决现在的问题,贴出来。

 

原文:http://www.ophonesdn.com/article/show/80

 

OPhone 线程模型:

      首先先要普及一下OPhone的线程模型: 当一个OPhone得应用运行后, 就会有一个UI的main线程启动, 这是一个非常重要的线程,它负责把事件分派到相应的控件,其中就包括屏幕绘图事件,它同样是用户与OPhone控件交互的线程。比如,当你在屏幕上的EditText上输入文字,UI线程会把这个事件分发给刚输入文字的EditText,紧接会向事件队列发送一个更新(invalidate)请求。UI线程会把这个请求移出事件队列并通知EditText在屏幕上重新绘制自身。

      这种单线线程模型就会使得OPhone的应用程序性能低下, 如果在这个单线程里执行一些耗时的操作, 比如访问数据库, 或是从网络端下载图片, 就会会阻塞整个用户界面。 比如如下操作:

view plaincopy to clipboardprint?
  1. public void onClick( View v ) {    
  2.          // 这里有可能是非常耗时的操作  
  3.             Bitmap b = loadImageFromNetwork();    
  4.             mImageView.setImageBitmap( b );    
  5.      }  


     在这种情况下你会发现, 界面僵死在那里并且OPhone在系统5秒中后没有反应,会显示如下错误:

      有的同学可能会采取以下的办法:

view plaincopy to clipboardprint?
  1. public void onClick( View v ) {    
  2.             new Thread(new Runnable() {  
  3.                 public void run() {  
  4.                     Bitmap b = loadImageFromNetwork();    
  5.                         mImageView.setImageBitmap( b );    
  6.                 }  
  7.             }).start();  
  8.              
  9.      }  

    但这样会发生一些很难察觉的错误, 因为我们知道UI线程不是线程安全的。

当然有很多种方法来处理这个问题:
OPhone提供了几种在其他线程中访问UI线程的方法。
• Activity.runOnUiThread( Runnable )
• View.post( Runnable )
• View.postDelayed( Runnable, long )
• Hanlder

 

view plaincopy to clipboardprint?
  1. public void onClick( View v ) {    
  2.     new Thread( new Runnable() {    
  3.             public void run() {    
  4.                      final Bitmap b = loadImageFromNetwork();    
  5.                      mImageView.post( new Runnable() {    
  6.                      mImageView.setImageBitmap( b );    
  7. });    
  8.           }    
  9.     }).start();    

        这种方法比较繁琐, 同时当你需要实现一些很复杂的操作并需要频繁地更新UI时这会变得更糟糕。为了解决这个问题,OPhone 1.5提供了一个工具类:AsyncTask,它使创建需要与用户界面交互的长时间运行的任务变得更简单。
我这里不详细介绍AsyncTask的用法了, 大家可以参考詹建飞写的“联网应用开发中的线程管理与界面更新”里面有详细介绍。 或者是参考这篇OPhone Developers Blog: Painless threading。 当然你也可以参考sdk的文档, 里面写的很详细。


          GridView动态更新效果实验:
  下面我就用AsyncTask来完成从网络动态下载图片,并且更新在我的GridView上的小实验。

        现在说一下我这个小实验的需求, 目标, 实现。

 需求: 从网络上下载各种图片, 并且动态显示在我的GridView上。
目标:展示AsyncTask的常见功能和动态显示的效果。
实现:利用AsyncTask和listener模式完成

下面是整个实验的代码实现, 我会一步步的展示给大家看:

1. 创建一个GridView Layout.

 

view plaincopy to clipboardprint?
  1. <LinearLayout xmlns:OPhone="http://schemas.OPhone.com/apk/res/OPhone"  
  2.     OPhone:orientation="vertical" OPhone:layout_width="fill_parent"  
  3.     OPhone:layout_height="fill_parent">  
  4.     <GridView OPhone:id="@+id/showPhotoGridView"  
  5.         OPhone:layout_width="fill_parent"  
  6.             OPhone:layout_height="0dip"  
  7.             OPhone:layout_weight="1.0"  
  8.         OPhone:numColumns="3" OPhone:verticalSpacing="10dp"  
  9.         OPhone:horizontalSpacing="10dp" OPhone:columnWidth="90dp"  
  10.         OPhone:stretchMode="columnWidth" OPhone:gravity="center" />  
  11. </LinearLayout>  
  12.    

2. 模拟从网上下载图片的Downloader程序:主要是模拟从网上下载图片, 当然你可以选择你想要下载的数量。 然后通过网络编程来获取URL信息。获得图片的bitmap。

 

view plaincopy to clipboardprint?
  1. class DownLoader {  
  2.     int downloadNo = 0;  
  3.     private static DownLoader downloader = new DownLoader();  
  4.     private static String[] myImageURL = null;  
  5.     private List<Photo> photos = new ArrayList<Photo>();  
  6.   
  7.     public static DownLoader getInstance() {  
  8.         initImageURL();  
  9.         return downloader;  
  10.     }  
  11.   
  12.     /** 
  13.      * 这里模拟从网上下载100张图片 
  14.      */  
  15.     static void initImageURL() {  
  16.         int no = 0;  
  17.         myImageURL = new String[100];  
  18.         for (int i = 0; i < myImageURL.length; i++) {  
  19.             myImageURL[i] = "http://cp.a8.com/image/128X128GIF/8" + no + ".gif";  
  20.             no++;  
  21.             if (no % 10 == 0) {  
  22.                 no = 0;  
  23.             }  
  24.         }  
  25.     }  
  26.   
  27.     /** 
  28.      * 返回一个Photo的List 注意这里注册了PhotoDownloadListener来监听每一张图片。  
  29.      * 如果有图片下载完了就更新的界面 
  30.      * @param listener 监听器 
  31.      * @return 
  32.      */  
  33.     public List<Photo> downLoadImage(PhotoDownloadListener listener) {  
  34.         List<String> urls = Arrays.asList(myImageURL);  
  35.         List<Photo> photos = new ArrayList<Photo>();  
  36.         URL aryURI = null;  
  37.         URLConnection conn = null;  
  38.         InputStream is = null;  
  39.         Bitmap bm = null;  
  40.         Photo photo = null;  
  41.         for (String url : urls) {  
  42.             try {  
  43.                 aryURI = new URL(url);  
  44.                 conn = aryURI.openConnection();  
  45.                 is = conn.getInputStream();  
  46.                 bm = BitmapFactory.decodeStream(is);  
  47.                 photo = new Photo(bm);  
  48.                 // update listener  
  49.                 listener.onPhotoDownloadListener(photo);  
  50.                 photos.add(photo);  
  51.                 Log.i("downlaod no: ", ++downloadNo + "");  
  52.             } catch (Exception e) {  
  53.                 throw new RuntimeException(e);  
  54.             } finally {  
  55.                 try {  
  56.                     if (is != null)  
  57.                     is.close();  
  58.                 } catch (IOException e) {  
  59.                     e.printStackTrace();  
  60.                 }  
  61.             }  
  62.         }  
  63.         return photos;  
  64.     }  
  65. }  


3. 通过ImageAdapter使得Gridview获得图片位置和现实内容。 Adapter是OPhone中用来更新或者说用来配合那些特殊的View, 例如ListView, GridView来显示里面的内容的。里面有个notifyDataSetChanged()的方法可以告诉Adapter来更新自己。在Doc中描述如下:Notifies the attached View that the underlying data has been changed and it should refresh itself.

 

view plaincopy to clipboardprint?
  1. class ImageAdapter extends BaseAdapter {  
  2.   
  3.     private Context mContext;  
  4.     private List<Photo> photos = new ArrayList<Photo>();  
  5.   
  6.     public ImageAdapter(Context context) {  
  7.         this.mContext = context;  
  8.     }  
  9.   
  10.     public void addPhoto(Photo photo) {  
  11.         photos.add(photo);  
  12.     }  
  13.       
  14. //通过这个api来动态通知adapter需要更新界面  
  15.     @Override  
  16.     public void notifyDataSetChanged() {  
  17.         super.notifyDataSetChanged();  
  18.     }  
  19.       
  20.   
  21.     @Override  
  22.     public int getCount() {  
  23.         return photos.size();  
  24.     }  
  25.   
  26.     @Override  
  27.     public Object getItem(int position) {  
  28.         return photos.get(position);  
  29.     }  
  30.   
  31.     @Override  
  32.     public long getItemId(int position) {  
  33.         return position;  
  34.     }  
  35.   
  36.     @Override  
  37.     public View getView(int position, View convertView, ViewGroup parent) {  
  38.         if (convertView == null) {  
  39.             convertView = new ImageView(mContext);;  
  40.         }   
  41.         ((ImageView)convertView).setImageBitmap(photos.get(position).getBm());  
  42.         return convertView;  
  43.     }  
  44.   
  45. }  


4. 普通的pojo类, 注意里面应用了(onPhotoDownloadListener)Listener模式来通知Adapter, 一个图片download完了。 你可以更新了。当然这个是在后面得重点类AsyncTask中完成。 这里只是定义一个接口。

 

view plaincopy to clipboardprint?
  1. class Photo {  
  2.     private Bitmap bm;  
  3.   
  4.     public Photo(Bitmap bm) {  
  5.         this.bm = bm;  
  6.     }  
  7.   
  8.     public Bitmap getBm() {  
  9.         return bm;  
  10.     }  
  11.   
  12.     public void setBm(Bitmap bm) {  
  13.         this.bm = bm;  
  14.     }  
  15.   
  16.     interface PhotoDownloadListener {  
  17.         public void onPhotoDownloadListener(Photo photo);  
  18.     }  
  19. }  


5. 利用AsyncTask进行多线程的下载图片, 并且利用adapter进行更新。
其中当有一个图片下载完了以后, Downloader就会调用onPhotoDownloadListener(),
DownloadPhotosTask看都有photo下载完了以后就调用publishProgress这个方法。 从而触发onProgressUpdate, 在这个方法里面去通知adapter有data更新了。

 

view plaincopy to clipboardprint?
  1. class DownloadPhotosTask extends AsyncTask<Void, Photo, Void> implements  
  2.             PhotoDownloadListener {  
  3.   
  4.         @Override  
  5.         protected Void doInBackground(Void... params) {  
  6.             DownLoader.getInstance().downLoadImage(this);  
  7.             return null;  
  8.         }  
  9.   
  10.         @Override  
  11.         public void onPhotoDownloadListener(Photo photo) {  
  12.             if (photo != null && !isCancelled()) {  
  13.                 //通过这个调用onProgressUpdate  
  14.                 publishProgress(photo);  
  15.             }  
  16.         }  
  17.   
  18.         @Override  
  19.         public void onProgressUpdate(Photo... photos) {  
  20.             for (Photo photo : photos) {  
  21.                 imageAdapter.addPhoto(photo);  
  22.             }  
  23.             // 这个是通知adapter有新的photo update.  
  24.             imageAdapter.notifyDataSetChanged();  
  25.         }  
  26.   
  27.     }  


6. 最后 onCreate() 方法启动我们整个实验

 

view plaincopy to clipboardprint?
  1. /** Called when the activity is first created. */  
  2.     @Override  
  3.     public void onCreate(Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.         setContentView(R.layout.main);  
  6.   
  7.         gridView = (GridView) findViewById(R.id.showPhotoGridView);  
  8.         button = (Button) findViewById(R.id.button);  
  9.           
  10.         imageAdapter = new ImageAdapter(this);  
  11.         gridView.setAdapter(imageAdapter);  
  12.         button.setOnClickListener(new View.OnClickListener() {  
  13.             @Override  
  14.             public void onClick(View v) {  
  15.                 button.setEnabled(false);  
  16.                 downloadPhotosTask = (DownloadPhotosTask) new DownloadPhotosTask()  
  17.                         .execute();  
  18.             }  
  19.         });  
  20.     }  

        效果图如下:下面所有的图都可以动态更新。 有兴趣的朋友可以用传统的方式试试, 就可以知道一个好的UI用起来是多么的舒服。

 最后, 我会把这个小程序的所有相关的source code放在附件里面供大家参考。 Code里面同时包括了动态更新ListView的代码。 大家可以自己试试写一个动态跟新ListView的小程序。

总结:
希望通过这个实例可以帮助大家进一步的了解OPhone的AsyncTask的用法(有点类似于Java Swing 里面的SwingWorker.) 和如何编写出人机更为互动的界面。我会把全部源代码打包放在后面。 由于是第一篇教程,肯能存在较多的问题, 如果大家有所疑惑欢迎提出意见和问题。 我会及时更新, 完善我们的教程。