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

OPhone 线程模型:

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

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

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


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

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

  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.      }  
public void onClick( View v ) {  
			new Thread(new Runnable() {
				public void run() {
					Bitmap b = loadImageFromNetwork();  
			            mImageView.setImageBitmap( b );  
				}
			}).start();
           
     }

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

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

 

  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();    
public void onClick( View v ) {  
    new Thread( new Runnable() {  
            public void run() {  
                     final Bitmap b = loadImageFromNetwork();  
                     mImageView.post( new Runnable() {  
                     mImageView.setImageBitmap( b );  
});  
          }  
    }).start();  

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


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

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

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

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

1. 创建一个GridView Layout.

 

  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.    
<LinearLayout xmlns:OPhone="http://schemas.OPhone.com/apk/res/OPhone"
	OPhone:orientation="vertical" OPhone:layout_width="fill_parent"
	OPhone:layout_height="fill_parent">
	<GridView OPhone:id="@+id/showPhotoGridView"
		OPhone:layout_width="fill_parent"
            OPhone:layout_height="0dip"
            OPhone:layout_weight="1.0"
		OPhone:numColumns="3" OPhone:verticalSpacing="10dp"
		OPhone:horizontalSpacing="10dp" OPhone:columnWidth="90dp"
		OPhone:stretchMode="columnWidth" OPhone:gravity="center" />
</LinearLayout>
 

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

 

  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. }  
class DownLoader {
	int downloadNo = 0;
	private static DownLoader downloader = new DownLoader();
	private static String[] myImageURL = null;
	private List<Photo> photos = new ArrayList<Photo>();

	public static DownLoader getInstance() {
		initImageURL();
		return downloader;
	}

	/**
	 * 这里模拟从网上下载100张图片
	 */
	static void initImageURL() {
		int no = 0;
		myImageURL = new String[100];
		for (int i = 0; i < myImageURL.length; i++) {
			myImageURL[i] = "http://cp.a8.com/image/128X128GIF/8" + no + ".gif";
			no++;
			if (no % 10 == 0) {
				no = 0;
			}
		}
	}

	/**
	 * 返回一个Photo的List 注意这里注册了PhotoDownloadListener来监听每一张图片。 
	 * 如果有图片下载完了就更新的界面
	 * @param listener 监听器
	 * @return
	 */
	public List<Photo> downLoadImage(PhotoDownloadListener listener) {
		List<String> urls = Arrays.asList(myImageURL);
		List<Photo> photos = new ArrayList<Photo>();
		URL aryURI = null;
		URLConnection conn = null;
		InputStream is = null;
		Bitmap bm = null;
		Photo photo = null;
		for (String url : urls) {
			try {
				aryURI = new URL(url);
				conn = aryURI.openConnection();
				is = conn.getInputStream();
				bm = BitmapFactory.decodeStream(is);
				photo = new Photo(bm);
				// update listener
				listener.onPhotoDownloadListener(photo);
				photos.add(photo);
				Log.i("downlaod no: ", ++downloadNo + "");
			} catch (Exception e) {
				throw new RuntimeException(e);
			} finally {
				try {
					if (is != null)
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return photos;
	}
}


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.

 

  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. }  
class ImageAdapter extends BaseAdapter {

	private Context mContext;
	private List<Photo> photos = new ArrayList<Photo>();

	public ImageAdapter(Context context) {
		this.mContext = context;
	}

	public void addPhoto(Photo photo) {
		photos.add(photo);
	}
	
//通过这个api来动态通知adapter需要更新界面
	@Override
	public void notifyDataSetChanged() {
		super.notifyDataSetChanged();
	}
	

	@Override
	public int getCount() {
		return photos.size();
	}

	@Override
	public Object getItem(int position) {
		return photos.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		if (convertView == null) {
			convertView = new ImageView(mContext);;
		} 
		((ImageView)convertView).setImageBitmap(photos.get(position).getBm());
		return convertView;
	}

}


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

 

  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. }  
class Photo {
	private Bitmap bm;

	public Photo(Bitmap bm) {
		this.bm = bm;
	}

	public Bitmap getBm() {
		return bm;
	}

	public void setBm(Bitmap bm) {
		this.bm = bm;
	}

	interface PhotoDownloadListener {
		public void onPhotoDownloadListener(Photo photo);
	}
}


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

 

  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.     }  
class DownloadPhotosTask extends AsyncTask<Void, Photo, Void> implements
			PhotoDownloadListener {

		@Override
		protected Void doInBackground(Void... params) {
			DownLoader.getInstance().downLoadImage(this);
			return null;
		}

		@Override
		public void onPhotoDownloadListener(Photo photo) {
			if (photo != null && !isCancelled()) {
				//通过这个调用onProgressUpdate
				publishProgress(photo);
			}
		}

		@Override
		public void onProgressUpdate(Photo... photos) {
			for (Photo photo : photos) {
				imageAdapter.addPhoto(photo);
			}
			// 这个是通知adapter有新的photo update.
			imageAdapter.notifyDataSetChanged();
		}

	}


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

 

  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.     }  
/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		gridView = (GridView) findViewById(R.id.showPhotoGridView);
		button = (Button) findViewById(R.id.button);
		
		imageAdapter = new ImageAdapter(this);
		gridView.setAdapter(imageAdapter);
		button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				button.setEnabled(false);
				downloadPhotosTask = (DownloadPhotosTask) new DownloadPhotosTask()
						.execute();
			}
		});
	}


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

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

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值