5點多就在睡夢中驚醒,睡不着,於是醒着也是醒着,還不如跟進下項目,於是網上看到這樣一篇文章,剛好解決現在的問題,貼出來。
原文:http://www.ophonesdn.com/article/show/80
OPhone 線程模型:
首先先要普及一下OPhone的線程模型: 當一個OPhone得應用運行後, 就會有一個UI的main線程啓動, 這是一個非常重要的線程,它負責把事件分派到相應的控件,其中就包括屏幕繪圖事件,它同樣是用戶與OPhone控件交互的線程。比如,當你在屏幕上的 EditText上輸入文字,UI線程會把這個事件分發給剛輸入文字的EditText,緊接會向事件隊列發送一個更新(invalidate)請求。 UI線程會把這個請求移出事件隊列並通知EditText在屏幕上重新繪製自身。
這種單線線程模型就會使得OPhone的應用程序性能低下, 如果在這個單線程裏執行一些耗時的操作, 比如訪問數據庫, 或是從網絡端下載圖片, 就會會阻塞整個用戶界面。 比如如下操作:
- public void onClick( View v ) {
- // 這裏有可能是非常耗時的操作
- Bitmap b = loadImageFromNetwork();
- mImageView.setImageBitmap( b );
- }
public void onClick( View v ) {
// 這裏有可能是非常耗時的操作
Bitmap b = loadImageFromNetwork();
mImageView.setImageBitmap( b );
}
在這種情況下你會發現, 界面僵死在那裏並且OPhone在系統5秒中後沒有反應,會顯示如下錯誤:
有的同學可能會採取以下的辦法:
- public void onClick( View v ) {
- new Thread( new Runnable() {
- public void run() {
- Bitmap b = loadImageFromNetwork();
- mImageView.setImageBitmap( b );
- }
- }).start();
- }
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
- public void onClick( View v ) {
- new Thread( new Runnable() {
- public void run() {
- final Bitmap b = loadImageFromNetwork();
- mImageView.post( new Runnable() {
- mImageView.setImageBitmap( b );
- });
- }
- }).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.
- <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>
<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。
- 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;
- }
- }
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.
- 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;
- }
- }
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中完成。 這裏只是定義一個接口。
- 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);
- }
- }
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更新了。
- 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();
- }
- }
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() 方法啓動我們整個實驗
- /** 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();
- }
- });
- }
/** 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.) 和如何編寫出人機更爲互動的界面。我會把全部源代碼打包放在後面。 由於是第一篇教程,肯能存在較多的問題,
如果大家有所疑惑歡迎提出意見和問題。 我會及時更新, 完善我們的教程。