在此先列出参考过的博客:
http://www.iteye.com/topic/685986
http://blog.orange11.nl/2009/09/17/exploring-the-world-of-android-part-2/
http://codehenge.net/blog/2011/06/android-development-tutorial-asynchronous-lazy-loading-and-caching-of-listview-images/
最近在试着自己做一个weibo的android app,用以巩固下已经学过的知识以及尝试下新的东西。在做的过程中碰到了图片异步下载、加载的问题,在网上查了些资料,然后自己动手做了下实验,现在将所学到的东西记录在这里以便以后查阅。
现在的需求是,ListView 的每行既有图片又有文字,这些资源都需要从网上下载然后再加载到界面上。文本的下载速度很快,可以很快的显示,但是图片比较大,如果网络环境不好,会耗很多时间。为了有好的体验,文字要先显示,图片资源异步下载完成后再显示。
那么如何实现呢?
1、listview显示数据需要用到Adapter,而Adapter中控制界面元素显示的是getView()方法;
2、getView()方法会返回一个View,也就是每一行的视图,需要设置的ImageView就在这个View中;
3、这个方法应该尽快的返回,不能因为图片的下载而阻塞,否则列表中没有内容显示,于是图片需要异步下载;
4、但是与每一行相关联的图片在getView()方法中就需要设置好,这里我们需要获得相关的ImageView的引用以便以后在设置与图片的关联;
5、和界面有关的改变需要在主线程也就是UI线程中设置,那么getView方法是否在主线程中呢?当然。
图片的异步下载很简单,你可以用AsyncTask也可以另开工作线程完成,在此我就用上面blog中用的方法
public class AsyncImageLoader {
private HashMap<String, SoftReference<Drawable>> imageCache;
public AsyncImageLoader(){
imageCache = new HashMap<String, SoftReference<Drawable>>();
}
public Drawable loadDrawable(final String imageUrl,final ImageCallback imageCallback){
if(imageCache.containsKey(imageUrl)){
SoftReference<Drawable> softReference = imageCache.get(imageUrl);
Drawable drawable = softReference.get();
if(drawable != null){
return drawable;
}
}
final Handler handler = new Handler(){
public void handleMessage(Message msg) {
imageCallback.imageLoaded((Drawable)msg.obj, imageUrl);
}
};
new Thread("img"){
public void run() {
Bitmap bitmap = ImgUtil.downloadImageFromUrl(imageUrl);//根据url下载图片
if(bitmap == null){
return;
}
Drawable drawable = new BitmapDrawable(context.getResources(),bitmap);
imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
Message message = handler.obtainMessage(0, drawable);
handler.sendMessage(message);
}
}.start();
return null;
}
public interface ImageCallback{
public void imageLoaded(Drawable imageDrawable, String imageUrl);
}
}
这里只是给了个大概的框架,便于说明。
上面的代码比较好理解,主要就是loadDrawable这个函数,他另外开了个线程用来下载图片,然后通过message,触发handler里的响应,执行接口里的方法。这里他为什么要大费周折的用到handler呢?因为只有在UI线程中才能改变界面中的显示,而handler是附属与它声明的这个类执行的线程的,在handler里触发执行的方法都是在它附属的那个线程下执行的,在后面的代码中我们可以知道loadDrawable方法是在主线程中执行的。
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if(row == null){
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.row, parent,false);
}
Holder holder = (Holder) row.getTag();
if(holder == null){
holder = new Holder(row);
row.setTag(holder);
}
final String url = listContent.get(position).get("url");
holder.testimg.setTag(url);
Drawable cachedimg = async.loadDrawable(url, new ImageCallback() {
public void imageLoaded(Drawable imageDrawable, String imageUrl) {
ImageView viewBytag = (ImageView) mList.findViewWithTag(url);
if(viewBytag != null){
viewBytag.setImageDrawable(imageDrawable);
Log.v("useimg", "inasync");
}
}
});
if(cachedimg == null)
holder.testimg.setImageDrawable(null);
else
holder.testimg.setImageDrawable(cachedimg);
return row;
}
这里就是getView方法的实现,我们主要还是关注图片的异步加载,我们可以发现在接口的实现里我们用 ImageView viewBytag = (ImageView) mList.findViewWithTag(url);
获得了与这个图像关联的imageView的引用,然后在接口中我们对imageView的图像资源进行了set。就像上面的一个blog中说的那样,获得这个引用的才是精华,只要获得了引用,设置什么的都好说。
在做这个的时候,异步下载已经不是问题,但是总是想不出怎么异步加载图片,现在看来,还是得灵活的运用面向对象中的各种元素才行。