首页 > 代码库 > 解决加载多图oom,内存和硬盘缓冲,过滤重复链接,同一视图没被缓冲前,某些view不显示的问题。

解决加载多图oom,内存和硬盘缓冲,过滤重复链接,同一视图没被缓冲前,某些view不显示的问题。

文章,参考自:http://blog.csdn.net/guolin_blog/article/details/34093441

不过本身自己的代码也也修改了很多。


在网络上找了很多例子,但是很多都是,不十分满意,

1.要不就是图片错乱,

2.第一次运行,如果滚动加载过快,要不就是无限多的线程,

3.要不就是在同一视图里面,如果没被缓冲,而且又有相同链接的话,某些视图即不显示出来。


网络下载,我用的是开源框格volley。


异步下载方式:
 1.在页面滚动的时候,中断线程,停止下载,
  2.页面停止的时候,开启线程下载图片。
  
  
  缓冲技术:
  1.图片显示,如果内存有,优先从内存取,
  2.如果内存没有,从硬盘取,然后保存进内存中
  3.如果所有缓冲都没有,即网络下载,然后再缓冲到硬盘和内存中(硬盘和内存里面的并非全部一样,内存有可能会溢出,会删除排在前面的一些缓冲,但是硬盘一直有,除非内存卡爆满)
  
  
  过滤相同链接的重复下载,
  1.在同一视图里,加载所有要下载的图片(key),链接(value)到map
  2.map.containvalue(url),过滤重复的链接,防止重复下载
  
  解决重复链接,没被缓冲前,在同一视图里,后面的图片不显示的问题:
  1.在下载某个链接的图片后,
  2.在map寻找一样的下载链接,
  3.把相同的链接的图片显示出来
  4.每下载完一个链接图片,在map里面删除key:作用:1.减少内存占用 2.判断是否该停止线程运行(主要)

/***
 * 
 * 
 * 异步下载方式:
 * 1.在页面滚动的时候,中断线程,停止下载,
 * 2.页面停止的时候,开启线程下载图片。
 * 
 * 
 * 缓冲技术:
 * 1.图片显示,如果内存有,从内存取,
 * 2.如果硬盘有,从硬盘取,然后保存进内存中
 * 3.网络下载,缓冲到硬盘和内存中
 * 
 * 
 * 过滤相同链接的重复下载,
 * 1.Hashmap<View, String> ----> 在同一视图里,加载所有要下载的图片(key),链接(value)
 * 2.过滤重复的链接,防止重复下载
 * 
 * 解决重复图片不显示,在同一个视图里面,如果有重复的链接图片,并且都还没有被缓冲,以前的版本不显示的
 * 1.在下载某个链接的图片后,
 * 2.在Hashmap<View, String>寻找一样的下载链接,
 * 3.就把相同的显示出来
 * 4.每下载完一个链接图片,在map里面删除key,减少内存占用
 * 5.在下载完map里面的所有链接,关闭线程
 * 6.下次视图在开始启动线程下载图片的时候,先清空map,才加载要下载的图片(key),链接(value)
 */
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;

import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageRequest;
import com.android.volley.toolbox.Volley;

/**
 * GridView的适配器,负责异步从网络上下载图片展示在照片墙上。
 * 
 * @author guolin
 */
public class PhotoWallAdapter extends ArrayAdapter<String> implements
		OnScrollListener {

	/**
	 * GridView的实例
	 */
	private GridView mPhotoWall;

	/**
	 * 第一张可见图片的下标
	 */
	private int mFirstVisibleItem;

	/**
	 * 一屏有多少张图片可见
	 */
	private int mVisibleItemCount;

	/**
	 * 记录是否刚打开程序,用于解决进入程序不滚动屏幕,不会下载图片的问题。
	 */
	private boolean isFirstEnter = true;

	/**
	 * volley的request
	 */
	private RequestQueue mQueue = null;
	/**
	 * 内存缓冲
	 */
	private BitmapCache mMemoryCache = null;

	/**
	 * 硬盘缓冲
	 */
	private FilesUtil mHardDriveCache = null;

	/**
	 * 解决同一屏幕,相同链接图片不显示的问题
	 */
	private HashMap<View, String> mSameViewUrls = null;

	@SuppressLint("NewApi")
	public PhotoWallAdapter(Context context, int textViewResourceId,
			String[] objects, GridView photoWall) {
		super(context, textViewResourceId, objects);
		this.mPhotoWall = photoWall;
		this.mPhotoWall.setOnScrollListener(this);
		// 初始化volley.内存缓冲,硬盘缓冲等相关参数
		init(context);
		this.mSameViewUrls = new HashMap<View, String>();
	}

	/**
	 * 初始化volley osmdroid
	 * 
	 * @param context
	 */
	private void init(Context context) {
		this.mQueue = Volley.newRequestQueue(context);
		this.mMemoryCache = new BitmapCache();
		this.mHardDriveCache = new FilesUtil(context);
		// this.mHardDriveCache.removeAll();
	}

	@SuppressLint("NewApi")
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		final String url = getItem(position);
		View view;
		if (convertView == null) {
			view = LayoutInflater.from(getContext()).inflate(
					R.layout.gv_img_wall_item, null);
		} else {
			view = convertView;
		}
		final ImageView photo = (ImageView) view.findViewById(R.id.photo);
		final TextView tv = (TextView) view.findViewById(R.id.num);
		tv.setText((position + 6) + "");

		// 给ImageView设置一个Tag,保证异步加载图片时不会乱序
		photo.setTag(url);
		setImageView(url, photo);
		return view;
	}

	/**
	 * 给ImageView设置图片。首先从本地硬盘或者内存中取图片,如果没有, 就给ImageView设置一张默认图片。
	 * 
	 * @param imageUrl
	 *            图片的URL地址,用于作为LruCache的键。
	 * @param imageView
	 *            用于显示图片的控件。
	 */
	private void setImageView(String imageUrl, ImageView imageView) {
		Bitmap bitmap = getBitmapFromCache(imageUrl);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			Log.i("-*--------", "本地硬盘或者内存添加");
		} else {
			imageView.setImageResource(R.drawable.empty_photo);
			Log.i("-*--------", "设置默认图片");
		}
	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		mFirstVisibleItem = firstVisibleItem;
		mVisibleItemCount = visibleItemCount;
		// 下载的任务应该由onScrollStateChanged里调用,但首次进入程序时onScrollStateChanged并不会调用,
		// 因此在这里为首次进入程序开启下载任务。
		if (isFirstEnter && visibleItemCount > 0) {
			loadBitmaps(view, firstVisibleItem, visibleItemCount);
			isFirstEnter = false;
		}
	}

	@Override
	/**
	 *  仅当GridView静止时才去下载图片,GridView滑动时取消所有正在下载的任务
	 */
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		if (scrollState == SCROLL_STATE_IDLE) {
			boolean isDownload = loadBitmaps(view, mFirstVisibleItem,
					mVisibleItemCount);
			if (isDownload) {
				mQueue.start();
				Log.i("----------", "开启下载");
			} else {
				mQueue.stop();
				Log.i("----------", "中断停止下载");
			}
		} else {
			mQueue.stop();
		}
	}

	/**
	 * 从LruCache中获取一张图片,如果不存在就返回null。
	 * 
	 * @param imageUrl
	 *            LruCache的键,这里传入图片的URL地址。
	 * @return 对应传入键的Bitmap对象,或者null。
	 */
	@SuppressLint("NewApi")
	public Bitmap getBitmapFromCache(String imageUrl) {
		// 先从内存取,看看是否有值
		Bitmap bitmap = mMemoryCache.getBitmap(imageUrl);
		// 没有的话,再从硬盘里面取
		if (bitmap == null) {
			bitmap = mHardDriveCache.getBitmap(imageUrl
					.replaceAll("[^\\w]", ""));
			if (bitmap != null) {
				mMemoryCache.putBitmap(imageUrl, bitmap);
			}
			Log.i("----------", "从硬盘取的");
		} else {
			Log.i("----------", "从内存取的");
		}
		return bitmap;
	}

	/**
	 * 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
	 * 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
	 * 
	 * @param view
	 * @param firstVisibleItem
	 *            第一个可见的ImageView的下标
	 * @param visibleItemCount
	 *            屏幕中总共可见的元素数
	 * @return 如果有需要下载的图片,返回true;否则返回false
	 */
	private boolean loadBitmaps(AbsListView view, int firstVisibleItem,
			int visibleItemCount) {
		boolean isDownload = false;
		mSameViewUrls.clear();
		try {
			for (int i = firstVisibleItem; i < firstVisibleItem
					+ visibleItemCount; i++) {
				final String imageUrl = Images.imageThumbUrls[i];
				ImageView imageView = (ImageView) mPhotoWall
						.findViewWithTag(imageUrl);
				// 先从内存和硬盘查找看看,没有的话,再去下载
				Bitmap bitmap = getBitmapFromCache(imageUrl);
				if (bitmap != null) {
					imageView.setImageBitmap(bitmap);
				} else {
					View viewItem = view.getChildAt(i - firstVisibleItem)
							.findViewById(R.id.photo);
					// 过滤重复链接
					if (!mSameViewUrls.containsValue(imageUrl)) {
						addDownloadTask(imageUrl);
					}
					mSameViewUrls.put(viewItem, imageUrl);
					isDownload = true;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return isDownload;
	}

	/**
	 * 下载图片,再把图片放到内存还有硬盘缓冲里面
	 * 
	 * @param imageUrl
	 */
	private void addDownloadTask(final String imageUrl) {
		final ImageRequest imageRequest = new ImageRequest(imageUrl,
				new Response.Listener<Bitmap>() {
					@Override
					public void onResponse(Bitmap response) {
						try {
							if (response != null) {
								// 保存到内存里
								mMemoryCache.putBitmap(imageUrl, response);
								// 保存到硬盘里
								mHardDriveCache.savaBitmap(
										imageUrl.replaceAll("[^\\w]", ""),
										response);
								// 显示图片
								showBitmap(response);
							}
						} catch (Exception e) {
							// TODO: handle exception
							Log.e("------------", "error", e);
						}
					}

					private void showBitmap(Bitmap response) throws Exception {
						Iterator<Entry<View, String>> iterator = mSameViewUrls
								.entrySet().iterator();
						//搞个列表,删去已经下载的链接,减少下次过滤相同链接的循环。
						ArrayList<View> delList = new ArrayList<View>();
						while (iterator.hasNext()) {
							Entry<View, String> entry = iterator.next();
							//设置有相同链接的图片,
							if (entry.getValue().equals(imageUrl)) {
								ImageView key = (ImageView) entry.getKey();
								key.setImageBitmap(response);
								//添加已经下载的连接,准备删除
								delList.add(key);
							}
						}
						//删去已经下载的链接
						for (View view : delList) {
							mSameViewUrls.remove(view);
						}
						//如果全部链接下载完结,中断线程
						if (mSameViewUrls.isEmpty()) {
							mQueue.stop();
						}
					}
				}, 0, 0, Config.RGB_565, new Response.ErrorListener() {
					@Override
					public void one rrorResponse(VolleyError error) {
						try {
							ImageView imageView = (ImageView) mPhotoWall
									.findViewWithTag(imageUrl);
							imageView.setImageResource(R.drawable.ic_launcher);
						} catch (Exception e) {
							// TODO: handle exception
							Log.e("------------", "error", e);
						}
					}
				});
		mQueue.add(imageRequest);
	}

	public void stopNetTasks() {
		// TODO Auto-generated method stub
		mQueue.stop();
	}
}

源码下载

解决加载多图oom,内存和硬盘缓冲,过滤重复链接,同一视图没被缓冲前,某些view不显示的问题。