安卓ListView自定义Adapter实时加载网络图片与缓存软引用图片之说明

最近开发个应用,里面大量的activity要用到listView这个控件.由于为了更加美观显示,就要自定义一个.
这下问题出来了.因为是获取网络图片.按传统的做法没办法及时加载对应的图片或者图片错位.
在网上找了很久.也抠了一天的源码..发现网上的都没有比较系统的说明.所以这里整理一下.
方便以后自己回看.
========================
先说一下思路: 我的理解为---- 因为要网络操作.所以加载图片在子线程中. 有延迟.但主线程都不等你子线程是否获取结果.它就走下去了.这样setImageDrawable(这里当然是没有了).
所以就会在你到ListView加载完时.看不到图片的原因. 
那么.在加载图片的子线程中,如果获取到图片之后.就handler发送一个信息到主线程.让它根据当前行的下标(postion)来更新图片.我管你主线程跟到哪了.管你等不等我.
反正我慢慢地下载图..下载到了我再叫你更新.
========================
首先说自定义的SimpleAdapter..
这里的传统做法大家都应懂的了.就是那个getView() 方法可以有点难理解
简单地说. 就是加载每一行数据(单行ListView).就调一次getView() 
public View getView(int position, View convertView, ViewGroup parent){}
position: 这个参数是指当前一行的下标. (从0开始的);
converTiew: 是可以理解为当前一屏..(不知对不对.我是这样理解的.)第一次执行convertView,如果是第一次就进行布局资源的创建操作 
如果拖进屏幕时.就可以复用到它了.不用每一屏都新建一个.这里下面代码里有说明
到图片加载了.我们定义一个图片加载的类.用一个静态方法来获取图片的Drawable
但由于优化内存使用,为了ListView加载了太多图片在内存中.那么.我们就进行缓存软引用机制来管理图片.
说得这么绕..无非就是指, 把得到的Drawable变成一个软引用.然后再把它放进map中.让系统自己的决定什么时候回收内存中的图片.
关于软引用...我个人的用法就是.但到一个drawable之后.马上new SoftReference<Drawable> (drawable) 存到map 中...那什么时候变回普通drawable呢
我认为当要从map中取出来之后.第一步就要变回普通的drawable(--softReference.get()--).这样的话.当我回来拖进listView时...就不会因为系统清理我的软引用导致看不到图了
下面上代码..先上异步获取图片的类
AsyncImageTask.java
package com.naxieshu.util;

import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;

/**
 * 异步加截图片类
 * @author Hai Jiang
 * @Email 672335219@qq.ciom
 * @Data 2014-2-26
 */
public class AsyncImageTask {
	//缓存图片 把图片的软引用放到map中
	private Map<String, SoftReference<Drawable>> imageMap;
	//构造器
	public AsyncImageTask() {  
        	super();  
        	this.imageMap = new HashMap<String, SoftReference<Drawable>>();  
    	} 
	
	//ID为标记,标记哪条记录image . 这个ID来自于自定义adapter的getView()方法中其中一个参数position
	public Drawable loadImage(final int id, final String imageUrl, 
			final ImageCallback callback){
		
		//先看缓存(Map)中是否存在
		if(imageMap.containsKey(imageUrl)){
			SoftReference<Drawable> softReference = imageMap.get(imageUrl);
			Drawable drawable = softReference.get();
			if(drawable != null){
				return drawable;
			}
		}
		
		//主线程更新图片
		final Handler handler = new Handler() {  
            		public void handleMessage(Message message) {  
                    		callback.imageLoaded((Drawable) message.obj, id);  
            		}  
       	 	};
			
		//加载图片的线程
		new Thread() {  
	            public void run() {  
	                //加载图片  
	                Drawable drawable = AsyncImageTask.loadImageByUrl(imageUrl);  
	                //加入缓存集合中 注意  这里就要把得到的图片变成软引用放到map中了
	                imageMap.put(imageUrl, new SoftReference<Drawable>(drawable));  
	                //通知消息主线程更新UI  . 这里就是是否能异步刷新的留意点.
	                Message message = handler.obtainMessage(0, drawable);  
	                handler.sendMessage(message);  
	            }  
	        }.start();
		return null;
		//到这里就获取图片的静态方法就完了
	}
	
	//根据图片地址加载图片,并保存为Drawable
	//这里不用说了吧.都是一些基本的.从API从可以看
	public static Drawable loadImageByUrl(String imageUrl){
		URL url = null;
		InputStream inputStream = null;
		try {
			url = new URL(Constant.TARGETURL+imageUrl);
			inputStream = (InputStream) url.getContent();  
	        Drawable drawable = Drawable.createFromStream(inputStream,"src");
	        return drawable;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(inputStream != null)
					inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	//利用接口回调,更新图片UI  
    	public interface ImageCallback {  
        	public void imageLoaded(Drawable obj, int id);  
    	} 
	
}



这里是自定义adapter类
MyListAdapter.java
package com.naxieshu.adapter;

import java.lang.ref.SoftReference;
import java.util.List;
import java.util.Map;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;

import com.naxieshu.activity.FindActivity;
import com.naxieshu.activity.R;
import com.naxieshu.domain.Book;
import com.naxieshu.util.AsyncImageTask;
import com.naxieshu.util.AsyncImageTask.ImageCallback;
import com.naxieshu.util.ImageUtil;

/**
 * 自定义List内容控件
 * @author Hai Jiang
 * @Email 672335219@qq.ciom
 * @Data 2014-2-26
 */
public class MyListAdapter extends SimpleAdapter{
	//要显示在LISTVIEW中的数据源
	public List<? extends Map<String, ?>> data;
	private LayoutInflater inflater;
	private AsyncImageTask imageTask;
	private ListView listView;
	
	public MyListAdapter(ListView listView,Context context,
			List<? extends Map<String, ?>> data) {
		super(context, data, 0, null, null);
		this.data = data;
		this.listView = listView;  
        	inflater = LayoutInflater.from(context);  
        	imageTask = new AsyncImageTask();
	}
	
	/**
	 * 在创建View资源对象的时候提供效率的缓存策略
	 */
	class ViewHold{
		//book.cover
		public ImageView image;
		//book.title book.shortIntro
		public TextView namtView,idView,introView;
	}
	ViewHold hold =null;
	
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		//判断是否第一次执行convertView(显示屏),如果是第一次就进行布局资源的创建操作
		if (convertView == null){
			hold = new ViewHold();
			//填充加载布局资源
			convertView = inflater.inflate(R.layout.activity_find_listview, null);
			hold.image = (ImageView)convertView.findViewById(R.id.bookImage);
			hold.namtView = (TextView)convertView.findViewById(R.id.bookName);
			hold.idView = (TextView)convertView.findViewById(R.id.bookId);
			hold.introView = (TextView)convertView.findViewById(R.id.bookShortIntro);
			//保存标记
			convertView.setTag(hold);
		} else {
			hold = (ViewHold) convertView.getTag();
		}
		
		/**获取数据,进行数据填充*/
		
		// 标记图片视图,注意不能放在上面  
       		hold.image.setTag(position);
        	Book book = (Book) data.get(position).get(position+"");
 
        	//异步获取图片.注意: 这里就是上面说到的.我个人为了方便理解的: 它里面子线程获取图片时可能会有延迟.
	        //                   主线程是不会等你这里的drawble得到值再继续往下走的.下面的判断是否获取网络图片就先给一个本地图它用着
        	Drawable drawable = imageTask.loadImage(position, book.getCover(),
        			new ImageCallback(){
			//这里的position就是AsyncImageTask里面要用到的便到确定子线程通知主线程更新哪一行的ID.也就是下标
			public void imageLoaded(Drawable image, int position) {
				if (image != null) {  
                           	 	//获取刚才标识的组件,并更新   
                           		ImageView imageView = (ImageView) listView  
      							.findViewWithTag(position);  
                           		if (imageView != null) {
                        	   		imageView.setImageDrawable(image);  
                           		}  
                      		}
			}
       		 });
		//判断是否获取网络图片.如果还没有取到.就用本地的
        	if (drawable != null) {  
            		hold.image.setImageDrawable(drawable);  
       		} else {
        		hold.image.setImageResource(R.drawable.ic_launcher);
        	}
		hold.namtView.setText(book.getTitle()); 
		hold.idView.setText(book.getId());
		hold.idView.setVisibility(View.GONE);
		hold.introView.setText(book.getShortIntro());
		return convertView;
	}

	@Override  
    public int getCount() {  
        return data.size();  
    }  
  
    @Override  
    public Object getItem(int position) {  
        return position;  
    }  
  
    @Override  
    public long getItemId(int position) {  
        return position;  
    }
}




到这里关键的都说完了.Activity那里那就贴代码了
主要是什么时候实例MyListAdapter要注意一下
如果你的是像搜结果显示在listView中的这种.
那么MyListAdapter的数据源就要放在Button的点击响应事件里获取..然后通Message把数据源发送到handler中.在这handler中实例MyListAdapter对象.再绑定到listView.
顺便说一下.另一种情况
得到数据源.但ListView不显示内容.这是为什么 ?
一般有两种原因.
1, ListView不在handler中绑定数据..因为对组件的更新更改操作.一 定要在主线程中弄 
2.就是布局问题.你的ListView里的item不指定高度.----这个是最常见的..ListView的item一定要指定高度.
就是你定义准备套在ListView中的那个layout_xxxx.xml这个文件中的LinearLayout这些要指定高度(最外面一层)

安卓ListView自定义Adapter实时加载网络图片与缓存软引用图片之说明,布布扣,bubuko.com

安卓ListView自定义Adapter实时加载网络图片与缓存软引用图片之说明

上一篇:Android测试原理(三)——使用Eclipse的ADT进行测试


下一篇:Windows下完整下载Android源代码