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.) 和如何编写出人机更为互动的界面。我会把全部源代码打包放在后面。 由于是第一篇教程,肯能存在较多的问题, 如果大家有所疑惑欢迎提出意见和问题。 我会及时更新, 完善我们的教程。