Android ListView 異步加載圖片
https://www.iteye.com/topic/1118828
https://www.iteye.com/topic/1127914
先說說這篇文章的優點把,開啟線程異步加載圖片,然後刷新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
}
};
package cindy.android.test.synclistview;
Syncimageloader代碼
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卡緩存)
TestSyncListView.rar (147.8 KB)
大家普遍爆出new
Thread太多會導致性能和資源浪費的問題,我想了一下的確如此,有人說用AsyncTask會更好點,因為實現的原理是線程池,肯定是比new Thread強,這個我也沒有考證,後來根據自己的一套做了一些修改,隻是一直沒發出來,然後有些同學線下又找我要修改後的源碼,我就索性把我修改的發出來給大家分享一下。
其實改動不大,就是把之前的new Thread改成了 Handler Looper Thread的模式,這樣在第一次滑動的時候就進入了wait狀態,又因為handler裏麵的runnable是隊列執行的,所以handler一直在添加的runnable也在等待,這樣就避免了多次new
thread的問題,從頭到尾就隻有一個thread,別的不多說,看修改後的代碼。
源碼我就不上傳了,就添加了一個類,修改了一個類:
Runinotherthread代碼
package cindy.android.util;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
public class RunInOtherThread {
private static final String LOG_TAG = "RunInOtherThread";
private LooperThread localThread = new LooperThread();
private boolean isRunning = true;
public Handler getHandler(){
return localThread.getHandler();
}
private class LooperThread extends Thread {
private Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
onReceiveMessage(msg.what);
}
};
Looper.loop();
}
Handler getHandler(){
return mHandler;
}
}
public void start(){
localThread.start();
}
public void quit(){
localThread.getHandler().getLooper().quit();
}
public void sendMessage(int what){
getHandler().sendEmptyMessage(what);
}
public Thread getThread(){
return localThread;
}
public void onReceiveMessage(int what){};
}
Syncimageloader代碼
package cindy.android.util;
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 cindy.android.debug.DebugUtil;
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>>();
RunInOtherThread runInOutherThread;
public SyncImageLoader() {
super();
runInOutherThread = new RunInOtherThread();
runInOutherThread.start();
}
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;
runInOutherThread.getHandler().post(new Runnable() {
@Override
public void run() {
if (!mAllowLoad) {
synchronized (lock) {
try {
DebugUtil.debug("wait start.....");
lock.wait();
DebugUtil.debug("wait end.....");
} 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);
}
}
});
}
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;
}
}
}
最後更新:2017-04-02 22:14:28