【轉】 AyncTask 實戰 模擬GridView 動態更新效果

5點多就在睡夢中驚醒,睡不着,於是醒着也是醒着,還不如跟進下項目,於是網上看到這樣一篇文章,剛好解決現在的問題,貼出來。

 

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

 

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.) 和如何編寫出人機更爲互動的界面。我會把全部源代碼打包放在後面。 由於是第一篇教程,肯能存在較多的問題, 如果大家有所疑惑歡迎提出意見和問題。 我會及時更新, 完善我們的教程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章