优化 Android ListView 异步加载图片
写这篇文章并不是教大家怎么样用listview异步加载图片,因为这样的文章在网上已经有很多了,比如这位仁兄写的就很好:
http://www.iteye.com/topic/685986
我也是因为看了这篇文章而受到了启发。
先说说这篇文章的优点把,开启线程异步加载图片,然后刷新UI显示图片,而且通过弱引用缓存网络加载的图片,节省了再次连接网络的开销。
这样做无疑是非常可取的方法,但是加载图片时仍然会感觉到轻微的卡屏现象,特别是listview里的item在进行快速滑动的时候。
我找了一下原因,可能是在listview快速滑动屏幕的时候划过的item太多 而且每次调用getView方法后就会异步的在过去某个时间内用handler刷新一下UI,
如果在同一时间调用handler刷新UI次数多了就会造成这样的卡屏现象。
后来又一想,其实我们完全没有必要在listview正在滑动的时候去后台加载图片(不管这是图片是在缓存里还是在网络上),这样无疑造成了很大的资源浪费。
我们只需要在listview滑动停止之后再去加载listview里面显示的几个item里面的图片就好了。
根据以上想法,我做了一些设计改造:
1.在adapter 的 getview方法里面启动加载图片的thread,如果listview在滑动则wait
2.监听listview滑动停止事件,获得listview显示的item的最上面和最下面的序号,并唤醒所有加载图片的thread,判断加载图片的序号是否是在范围内,如果是则继续加载,如果不是则结束thread
部分代码如下:
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if(convertView == null){
- convertView = mInflater.inflate(R.layout.book_item_adapter, null);
- }
- BookModel model = mModels.get(position);
- convertView.setTag(position);
- ImageView iv = (ImageView) convertView.findViewById(R.id.sItemIcon);
- TextView sItemTitle = (TextView) convertView.findViewById(R.id.sItemTitle);
- TextView sItemInfo = (TextView) convertView.findViewById(R.id.sItemInfo);
- sItemTitle.setText(model.book_name);
- sItemInfo.setText(model.out_book_url);
- iv.setBackgroundResource(R.drawable.rc_item_bg);
- syncImageLoader.loadImage(position,model.out_book_pic,imageLoadListener);
- return convertView;
- }
- SyncImageLoader.OnImageLoadListener imageLoadListener = new SyncImageLoader.OnImageLoadListener(){
- @Override
- public void onImageLoad(Integer t, Drawable drawable) {
- //BookModel model = (BookModel) getItem(t);
- View view = mListView.findViewWithTag(t);
- if(view != null){
- ImageView iv = (ImageView) view.findViewById(R.id.sItemIcon);
- iv.setBackgroundDrawable(drawable);
- }
- }
- @Override
- public void onError(Integer t) {
- BookModel model = (BookModel) getItem(t);
- View view = mListView.findViewWithTag(model);
- if(view != null){
- ImageView iv = (ImageView) view.findViewById(R.id.sItemIcon);
- iv.setBackgroundResource(R.drawable.rc_item_bg);
- }
- }
- };
- public void loadImage(){
- int start = mListView.getFirstVisiblePosition();
- int end =mListView.getLastVisiblePosition();
- if(end >= getCount()){
- end = getCount() -1;
- }
- syncImageLoader.setLoadLimit(start, end);
- syncImageLoader.unlock();
- }
- AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- switch (scrollState) {
- case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
- DebugUtil.debug("SCROLL_STATE_FLING");
- syncImageLoader.lock();
- break;
- case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
- DebugUtil.debug("SCROLL_STATE_IDLE");
- loadImage();
- //loadImage();
- break;
- case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
- syncImageLoader.lock();
- break;
- default:
- break;
- }
- }
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem,
- int visibleItemCount, int totalItemCount) {
- // TODO Auto-generated method stub
- }
- };
@Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView == null){ convertView = mInflater.inflate(R.layout.book_item_adapter, null); } BookModel model = mModels.get(position); convertView.setTag(position); ImageView iv = (ImageView) convertView.findViewById(R.id.sItemIcon); TextView sItemTitle = (TextView) convertView.findViewById(R.id.sItemTitle); TextView sItemInfo = (TextView) convertView.findViewById(R.id.sItemInfo); sItemTitle.setText(model.book_name); sItemInfo.setText(model.out_book_url); iv.setBackgroundResource(R.drawable.rc_item_bg); syncImageLoader.loadImage(position,model.out_book_pic,imageLoadListener); return convertView; } SyncImageLoader.OnImageLoadListener imageLoadListener = new SyncImageLoader.OnImageLoadListener(){ @Override public void onImageLoad(Integer t, Drawable drawable) { //BookModel model = (BookModel) getItem(t); View view = mListView.findViewWithTag(t); if(view != null){ ImageView iv = (ImageView) view.findViewById(R.id.sItemIcon); iv.setBackgroundDrawable(drawable); } } @Override public void onError(Integer t) { BookModel model = (BookModel) getItem(t); View view = mListView.findViewWithTag(model); if(view != null){ ImageView iv = (ImageView) view.findViewById(R.id.sItemIcon); iv.setBackgroundResource(R.drawable.rc_item_bg); } } }; public void loadImage(){ int start = mListView.getFirstVisiblePosition(); int end =mListView.getLastVisiblePosition(); if(end >= getCount()){ end = getCount() -1; } syncImageLoader.setLoadLimit(start, end); syncImageLoader.unlock(); } AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { switch (scrollState) { case AbsListView.OnScrollListener.SCROLL_STATE_FLING: DebugUtil.debug("SCROLL_STATE_FLING"); syncImageLoader.lock(); break; case AbsListView.OnScrollListener.SCROLL_STATE_IDLE: DebugUtil.debug("SCROLL_STATE_IDLE"); loadImage(); //loadImage(); break; case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: syncImageLoader.lock(); break; default: break; } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // TODO Auto-generated method stub } };package cindy.android.test.synclistview;
- import java.io.DataInputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.lang.ref.SoftReference;
- import java.net.URL;
- import java.util.HashMap;
- import android.graphics.drawable.Drawable;
- import android.os.Environment;
- import android.os.Handler;
- public class SyncImageLoader {
- private Object lock = new Object();
- private boolean mAllowLoad = true;
- private boolean firstLoad = true;
- private int mStartLoadLimit = 0;
- private int mStopLoadLimit = 0;
- final Handler handler = new Handler();
- private HashMap<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();
- public interface OnImageLoadListener {
- public void onImageLoad(Integer t, Drawable drawable);
- public void onError(Integer t);
- }
- public void setLoadLimit(int startLoadLimit,int stopLoadLimit){
- if(startLoadLimit > stopLoadLimit){
- return;
- }
- mStartLoadLimit = startLoadLimit;
- mStopLoadLimit = stopLoadLimit;
- }
- public void restore(){
- mAllowLoad = true;
- firstLoad = true;
- }
- public void lock(){
- mAllowLoad = false;
- firstLoad = false;
- }
- public void unlock(){
- mAllowLoad = true;
- synchronized (lock) {
- lock.notifyAll();
- }
- }
- public void loadImage(Integer t, String imageUrl,
- OnImageLoadListener listener) {
- final OnImageLoadListener mListener = listener;
- final String mImageUrl = imageUrl;
- final Integer mt = t;
- new Thread(new Runnable() {
- @Override
- public void run() {
- if(!mAllowLoad){
- DebugUtil.debug("prepare to load");
- synchronized (lock) {
- try {
- lock.wait();
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- if(mAllowLoad && firstLoad){
- loadImage(mImageUrl, mt, mListener);
- }
- if(mAllowLoad && mt <= mStopLoadLimit && mt >= mStartLoadLimit){
- loadImage(mImageUrl, mt, mListener);
- }
- }
- }).start();
- }
- private void loadImage(final String mImageUrl,final Integer mt,final OnImageLoadListener mListener){
- if (imageCache.containsKey(mImageUrl)) {
- SoftReference<Drawable> softReference = imageCache.get(mImageUrl);
- final Drawable d = softReference.get();
- if (d != null) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if(mAllowLoad){
- mListener.onImageLoad(mt, d);
- }
- }
- });
- return;
- }
- }
- try {
- final Drawable d = loadImageFromUrl(mImageUrl);
- if(d != null){
- imageCache.put(mImageUrl, new SoftReference<Drawable>(d));
- }
- handler.post(new Runnable() {
- @Override
- public void run() {
- if(mAllowLoad){
- mListener.onImageLoad(mt, d);
- }
- }
- });
- } catch (IOException e) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onError(mt);
- }
- });
- e.printStackTrace();
- }
- }
- public static Drawable loadImageFromUrl(String url) throws IOException {
- DebugUtil.debug(url);
- if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
- File f = new File(Environment.getExternalStorageDirectory()+"/TestSyncListView/"+MD5.getMD5(url));
- if(f.exists()){
- FileInputStream fis = new FileInputStream(f);
- Drawable d = Drawable.createFromStream(fis, "src");
- return d;
- }
- URL m = new URL(url);
- InputStream i = (InputStream) m.getContent();
- DataInputStream in = new DataInputStream(i);
- FileOutputStream out = new FileOutputStream(f);
- byte[] buffer = new byte[1024];
- int byteread=0;
- while ((byteread = in.read(buffer)) != -1) {
- out.write(buffer, 0, byteread);
- }
- in.close();
- out.close();
- Drawable d = Drawable.createFromStream(i, "src");
- return loadImageFromUrl(url);
- }else{
- URL m = new URL(url);
- InputStream i = (InputStream) m.getContent();
- Drawable d = Drawable.createFromStream(i, "src");
- return d;
- }
- }
- }
import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.URL; import java.util.HashMap; import android.graphics.drawable.Drawable; import android.os.Environment; import android.os.Handler; public class SyncImageLoader { private Object lock = new Object(); private boolean mAllowLoad = true; private boolean firstLoad = true; private int mStartLoadLimit = 0; private int mStopLoadLimit = 0; final Handler handler = new Handler(); private HashMap<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>(); public interface OnImageLoadListener { public void onImageLoad(Integer t, Drawable drawable); public void onError(Integer t); } public void setLoadLimit(int startLoadLimit,int stopLoadLimit){ if(startLoadLimit > stopLoadLimit){ return; } mStartLoadLimit = startLoadLimit; mStopLoadLimit = stopLoadLimit; } public void restore(){ mAllowLoad = true; firstLoad = true; } public void lock(){ mAllowLoad = false; firstLoad = false; } public void unlock(){ mAllowLoad = true; synchronized (lock) { lock.notifyAll(); } } public void loadImage(Integer t, String imageUrl, OnImageLoadListener listener) { final OnImageLoadListener mListener = listener; final String mImageUrl = imageUrl; final Integer mt = t; new Thread(new Runnable() { @Override public void run() { if(!mAllowLoad){ DebugUtil.debug("prepare to load"); synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } if(mAllowLoad && firstLoad){ loadImage(mImageUrl, mt, mListener); } if(mAllowLoad && mt <= mStopLoadLimit && mt >= mStartLoadLimit){ loadImage(mImageUrl, mt, mListener); } } }).start(); } private void loadImage(final String mImageUrl,final Integer mt,final OnImageLoadListener mListener){ if (imageCache.containsKey(mImageUrl)) { SoftReference<Drawable> softReference = imageCache.get(mImageUrl); final Drawable d = softReference.get(); if (d != null) { handler.post(new Runnable() { @Override public void run() { if(mAllowLoad){ mListener.onImageLoad(mt, d); } } }); return; } } try { final Drawable d = loadImageFromUrl(mImageUrl); if(d != null){ imageCache.put(mImageUrl, new SoftReference<Drawable>(d)); } handler.post(new Runnable() { @Override public void run() { if(mAllowLoad){ mListener.onImageLoad(mt, d); } } }); } catch (IOException e) { handler.post(new Runnable() { @Override public void run() { mListener.onError(mt); } }); e.printStackTrace(); } } public static Drawable loadImageFromUrl(String url) throws IOException { DebugUtil.debug(url); if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ File f = new File(Environment.getExternalStorageDirectory()+"/TestSyncListView/"+MD5.getMD5(url)); if(f.exists()){ FileInputStream fis = new FileInputStream(f); Drawable d = Drawable.createFromStream(fis, "src"); return d; } URL m = new URL(url); InputStream i = (InputStream) m.getContent(); DataInputStream in = new DataInputStream(i); FileOutputStream out = new FileOutputStream(f); byte[] buffer = new byte[1024]; int byteread=0; while ((byteread = in.read(buffer)) != -1) { out.write(buffer, 0, byteread); } in.close(); out.close(); Drawable d = Drawable.createFromStream(i, "src"); return loadImageFromUrl(url); }else{ URL m = new URL(url); InputStream i = (InputStream) m.getContent(); Drawable d = Drawable.createFromStream(i, "src"); return d; } } }为了让大家更好的理解,我添加了源代码例子,还特地美化了一下UI
除了本身已有的弱引用缓存图片,我还添加了本地SD卡缓存图片(这两种缓存方法各有好处,如果图片经常变化建议内存缓存图片,如果是不经常修改的图片建议SD卡缓存)
欢迎大家拍砖讨论