先说说这篇文章的优点把,开启线程异步加载图片,然后刷新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
01 |
@Override |
02 |
public View getView( int position, View convertView, ViewGroup parent) { |
03 |
if (convertView == null ){ |
04 |
convertView = mInflater.inflate(R.layout.book_item_adapter, null ); |
05 |
} |
06 |
BookModel model = mModels.get(position); |
07 |
convertView.setTag(position); |
08 |
ImageView iv = (ImageView) convertView.findViewById(R.id.sItemIcon); |
09 |
TextView sItemTitle = (TextView) convertView.findViewById(R.id.sItemTitle); |
10 |
TextView sItemInfo = (TextView) convertView.findViewById(R.id.sItemInfo); |
11 |
sItemTitle.setText(model.book_name); |
12 |
sItemInfo.setText(model.out_book_url); |
13 |
iv.setBackgroundResource(R.drawable.rc_item_bg); |
14 |
syncImageLoader.loadImage(position,model,imageLoadListener); |
15 |
return convertView; |
16 |
} |
17 |
SyncImageLoader.OnImageLoadListener imageLoadListener = new SyncImageLoader.OnImageLoadListener(){ |
18 |
@Override |
19 |
public void onImageLoad(Integer t, Drawable drawable) { |
20 |
//BookModel model = (BookModel) getItem(t); |
21 |
View view = mListView.findViewWithTag(t); |
22 |
if (view != null ){ |
23 |
ImageView iv = (ImageView) view.findViewById(R.id.sItemIcon); |
24 |
iv.setBackgroundDrawable(drawable); |
25 |
} |
26 |
} |
27 |
@Override |
28 |
public void onError(Integer t) { |
29 |
BookModel model = (BookModel) getItem(t); |
30 |
View view = mListView.findViewWithTag(model); |
31 |
if (view != null ){ |
32 |
ImageView iv = (ImageView) view.findViewById(R.id.sItemIcon); |
33 |
iv.setBackgroundResource(R.drawable.rc_item_bg); |
34 |
} |
35 |
} |
36 |
|
37 |
}; |
38 |
public void loadImage(){ |
39 |
int start = mListView.getFirstVisiblePosition(); |
40 |
int end =mListView.getLastVisiblePosition(); |
41 |
if (end >= getCount()){ |
42 |
end = getCount() - 1 ; |
43 |
} |
44 |
syncImageLoader.setLoadLimit(start, end); |
45 |
syncImageLoader.unlock(); |
46 |
} |
47 |
AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() { |
48 |
|
49 |
@Override |
50 |
public void onScrollStateChanged(AbsListView view, int scrollState) { |
51 |
switch (scrollState) { |
52 |
case AbsListView.OnScrollListener.SCROLL_STATE_FLING: |
53 |
DebugUtil.debug( "SCROLL_STATE_FLING" ); |
54 |
syncImageLoader.lock(); |
55 |
break ; |
56 |
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE: |
57 |
DebugUtil.debug( "SCROLL_STATE_IDLE" ); |
58 |
loadImage(); |
59 |
//loadImage(); |
60 |
break ; |
61 |
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: |
62 |
syncImageLoader.lock(); |
63 |
break ; |
64 |
default : |
65 |
break ; |
66 |
} |
67 |
|
68 |
} |
69 |
|
70 |
@Override |
71 |
public void onScroll(AbsListView view, int firstVisibleItem, |
72 |
int visibleItemCount, int totalItemCount) { |
73 |
// TODO Auto-generated method stub |
74 |
|
75 |
} |
76 |
}; |
package cindy.android.test.synclistview;
Syncimageloader代码
001 |
import java.io.DataInputStream; |
002 |
import java.io.File; |
003 |
import java.io.FileInputStream; |
004 |
import java.io.FileOutputStream; |
005 |
import java.io.IOException; |
006 |
import java.io.InputStream; |
007 |
import java.lang.ref.SoftReference; |
008 |
import java.net.URL; |
009 |
import java.util.HashMap; |
010 |
import android.graphics.drawable.Drawable; |
011 |
import android.os.Environment; |
012 |
import android.os.Handler; |
013 |
public class SyncImageLoader { |
014 |
private Object lock = new Object(); |
015 |
|
016 |
private boolean mAllowLoad = true ; |
017 |
|
018 |
private boolean firstLoad = true ; |
019 |
|
020 |
private int mStartLoadLimit = 0 ; |
021 |
|
022 |
private int mStopLoadLimit = 0 ; |
023 |
|
024 |
final Handler handler = new Handler(); |
025 |
|
026 |
private HashMap<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>(); |
027 |
|
028 |
public interface OnImageLoadListener { |
029 |
public void onImageLoad(Integer t, Drawable drawable); |
030 |
public void onError(Integer t); |
031 |
} |
032 |
|
033 |
public void setLoadLimit( int startLoadLimit, int stopLoadLimit){ |
034 |
if (startLoadLimit > stopLoadLimit){ |
035 |
return ; |
036 |
} |
037 |
mStartLoadLimit = startLoadLimit; |
038 |
mStopLoadLimit = stopLoadLimit; |
039 |
} |
040 |
|
041 |
public void restore(){ |
042 |
mAllowLoad = true ; |
043 |
firstLoad = true ; |
044 |
} |
045 |
|
046 |
public void lock(){ |
047 |
mAllowLoad = false ; |
048 |
firstLoad = false ; |
049 |
} |
050 |
|
051 |
public void unlock(){ |
052 |
mAllowLoad = true ; |
053 |
synchronized (lock) { |
054 |
lock.notifyAll(); |
055 |
} |
056 |
} |
057 |
public void loadImage(Integer t, String imageUrl, |
058 |
OnImageLoadListener listener) { |
059 |
final OnImageLoadListener mListener = listener; |
060 |
final String mImageUrl = imageUrl; |
061 |
final Integer mt = t; |
062 |
|
063 |
new Thread( new Runnable() { |
064 |
@Override |
065 |
public void run() { |
066 |
if (!mAllowLoad){ |
067 |
DebugUtil.debug( "prepare to load" ); |
068 |
synchronized (lock) { |
069 |
try { |
070 |
lock.wait(); |
071 |
} catch (InterruptedException e) { |
072 |
// TODO Auto-generated catch block |
073 |
e.printStackTrace(); |
074 |
} |
075 |
} |
076 |
} |
077 |
|
078 |
if (mAllowLoad && firstLoad){ |
079 |
loadImage(mImageUrl, mt, mListener); |
080 |
} |
081 |
|
082 |
if (mAllowLoad && mt <= mStopLoadLimit && mt >= mStartLoadLimit){ |
083 |
loadImage(mImageUrl, mt, mListener); |
084 |
} |
085 |
} |
086 |
}).start(); |
087 |
} |
088 |
|
089 |
private void loadImage( final String mImageUrl, final Integer mt, final OnImageLoadListener mListener){ |
090 |
|
091 |
if (imageCache.containsKey(mImageUrl)) { |
092 |
SoftReference<Drawable> softReference = imageCache.get(mImageUrl); |
093 |
final Drawable d = softReference.get(); |
094 |
if (d != null ) { |
095 |
handler.post( new Runnable() { |
096 |
@Override |
097 |
public void run() { |
098 |
if (mAllowLoad){ |
099 |
mListener.onImageLoad(mt, d); |
100 |
} |
101 |
} |
102 |
}); |
103 |
return ; |
104 |
} |
105 |
} |
106 |
try { |
107 |
final Drawable d = loadImageFromUrl(mImageUrl); |
108 |
if (d != null ){ |
109 |
imageCache.put(mImageUrl, new SoftReference<Drawable>(d)); |
110 |
} |
111 |
handler.post( new Runnable() { |
112 |
@Override |
113 |
public void run() { |
114 |
if (mAllowLoad){ |
115 |
mListener.onImageLoad(mt, d); |
116 |
} |
117 |
} |
118 |
}); |
119 |
} catch (IOException e) { |
120 |
handler.post( new Runnable() { |
121 |
@Override |
122 |
public void run() { |
123 |
mListener.onError(mt); |
124 |
} |
125 |
}); |
126 |
e.printStackTrace(); |
127 |
} |
128 |
} |
129 |
public static Drawable loadImageFromUrl(String url) throws IOException { |
130 |
DebugUtil.debug(url); |
131 |
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ |
132 |
File f = new File(Environment.getExternalStorageDirectory()+ "/TestSyncListView/" +MD5.getMD5(url)); |
133 |
if (f.exists()){ |
134 |
FileInputStream fis = new FileInputStream(f); |
135 |
Drawable d = Drawable.createFromStream(fis, "src" ); |
136 |
return d; |
137 |
} |
138 |
URL m = new URL(url); |
139 |
InputStream i = (InputStream) m.getContent(); |
140 |
DataInputStream in = new DataInputStream(i); |
141 |
FileOutputStream out = new FileOutputStream(f); |
142 |
byte [] buffer = new byte [ 1024 ]; |
143 |
int byteread= 0 ; |
144 |
while ((byteread = in.read(buffer)) != - 1 ) { |
145 |
out.write(buffer, 0 , byteread); |
146 |
} |
147 |
in.close(); |
148 |
out.close(); |
149 |
Drawable d = Drawable.createFromStream(i, "src" ); |
150 |
return loadImageFromUrl(url); |
151 |
} else { |
152 |
URL m = new URL(url); |
153 |
InputStream i = (InputStream) m.getContent(); |
154 |
Drawable d = Drawable.createFromStream(i, "src" ); |
155 |
return d; |
156 |
} |
157 |
|
158 |
} |
159 |
} |
为了让大家更好的理解,我添加了源代码例子,还特地美化了一下UI
很多同学说在这里一直new Thread可能会造成资源浪费的问题,针对这个问题我后来又做了优化:
其实改动不大,就是把之前的new Thread改成了 Handler Looper Thread的模式,这样在第一次滑动的时候就进入了wait状态,又因为handler里面的runnable是队列执行的,所以handler一直在添加的runnable也在等待,这样就避免了多次new thread的问题,从头到尾就只有一个thread,别的不多说,看修改后的代码。
源码我就不上传了,就添加了一个类,修改了一个类:
01 |
package cindy.android.util; |
02 |
import android.os.Handler; |
03 |
import android.os.Looper; |
04 |
import android.os.Message; |
05 |
public class RunInOtherThread { |
06 |
private static final String LOG_TAG = "RunInOtherThread" ; |
07 |
|
08 |
private LooperThread localThread = new LooperThread(); |
09 |
|
10 |
private boolean isRunning = true ; |
11 |
public Handler getHandler(){ |
12 |
return localThread.getHandler(); |
13 |
} |
14 |
|
15 |
private class LooperThread extends Thread { |
16 |
private Handler mHandler; |
17 |
public void run() { |
18 |
Looper.prepare(); |
19 |
mHandler = new Handler() { |
20 |
public void handleMessage(Message msg) { |
21 |
onReceiveMessage(msg.what); |
22 |
} |
23 |
}; |
24 |
Looper.loop(); |
25 |
} |
26 |
|
27 |
Handler getHandler(){ |
28 |
return mHandler; |
29 |
} |
30 |
|
31 |
} |
32 |
|
33 |
public void start(){ |
34 |
localThread.start(); |
35 |
} |
36 |
|
37 |
public void quit(){ |
38 |
localThread.getHandler().getLooper().quit(); |
39 |
} |
40 |
|
41 |
public void sendMessage( int what){ |
42 |
getHandler().sendEmptyMessage(what); |
43 |
} |
44 |
|
45 |
public Thread getThread(){ |
46 |
return localThread; |
47 |
} |
48 |
|
49 |
public void onReceiveMessage( int what){}; |
50 |
|
51 |
} |
RunInOtherThread
01 |
package cindy.android.util; |
02 |
import android.os.Handler; |
03 |
import android.os.Looper; |
04 |
import android.os.Message; |
05 |
public class RunInOtherThread { |
06 |
private static final String LOG_TAG = "RunInOtherThread" ; |
07 |
|
08 |
private LooperThread localThread = new LooperThread(); |
09 |
|
10 |
private boolean isRunning = true ; |
11 |
public Handler getHandler(){ |
12 |
return localThread.getHandler(); |
13 |
} |
14 |
|
15 |
private class LooperThread extends Thread { |
16 |
private Handler mHandler; |
17 |
public void run() { |
18 |
Looper.prepare(); |
19 |
mHandler = new Handler() { |
20 |
public void handleMessage(Message msg) { |
21 |
onReceiveMessage(msg.what); |
22 |
} |
23 |
}; |
24 |
Looper.loop(); |
25 |
} |
26 |
|
27 |
Handler getHandler(){ |
28 |
return mHandler; |
29 |
} |
30 |
|
31 |
} |
32 |
|
33 |
public void start(){ |
34 |
localThread.start(); |
35 |
} |
36 |
|
37 |
public void quit(){ |
38 |
localThread.getHandler().getLooper().quit(); |
39 |
} |
40 |
|
41 |
public void sendMessage( int what){ |
42 |
getHandler().sendEmptyMessage(what); |
43 |
} |
44 |
|
45 |
public Thread getThread(){ |
46 |
return localThread; |
47 |
} |
48 |
|
49 |
public void onReceiveMessage( int what){}; |
50 |
|
51 |
} |
SyncImageLoader
001 |
package cindy.android.util; |
002 |
import java.io.DataInputStream; |
003 |
import java.io.File; |
004 |
import java.io.FileInputStream; |
005 |
import java.io.FileOutputStream; |
006 |
import java.io.IOException; |
007 |
import java.io.InputStream; |
008 |
import java.lang.ref.SoftReference; |
009 |
import java.net.URL; |
010 |
import java.util.HashMap; |
011 |
import cindy.android.debug.DebugUtil; |
012 |
import android.graphics.drawable.Drawable; |
013 |
import android.os.Environment; |
014 |
import android.os.Handler; |
015 |
public class SyncImageLoader { |
016 |
private Object lock = new Object(); |
017 |
private boolean mAllowLoad = true ; |
018 |
private boolean firstLoad = true ; |
019 |
private int mStartLoadLimit = 0 ; |
020 |
private int mStopLoadLimit = 0 ; |
021 |
final Handler handler = new Handler(); |
022 |
private HashMap<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>(); |
023 |
RunInOtherThread runInOutherThread; |
024 |
public SyncImageLoader() { |
025 |
super (); |
026 |
runInOutherThread = new RunInOtherThread(); |
027 |
runInOutherThread.start(); |
028 |
} |
029 |
public interface OnImageLoadListener { |
030 |
public void onImageLoad(Integer t, Drawable drawable); |
031 |
public void onError(Integer t); |
032 |
} |
033 |
public void setLoadLimit( int startLoadLimit, int stopLoadLimit) { |
034 |
if (startLoadLimit > stopLoadLimit) { |
035 |
return ; |
036 |
} |
037 |
mStartLoadLimit = startLoadLimit; |
038 |
mStopLoadLimit = stopLoadLimit; |
039 |
} |
040 |
public void restore() { |
041 |
mAllowLoad = true ; |
042 |
firstLoad = true ; |
043 |
} |
044 |
public void lock() { |
045 |
mAllowLoad = false ; |
046 |
firstLoad = false ; |
047 |
} |
048 |
public void unlock() { |
049 |
mAllowLoad = true ; |
050 |
synchronized (lock) { |
051 |
lock.notifyAll(); |
052 |
} |
053 |
} |
054 |
public void loadImage(Integer t, String imageUrl, |
055 |
OnImageLoadListener listener) { |
056 |
final OnImageLoadListener mListener = listener; |
057 |
final String mImageUrl = imageUrl; |
058 |
final Integer mt = t; |
059 |
|
060 |
runInOutherThread.getHandler().post( new Runnable() { |
061 |
@Override |
062 |
public void run() { |
063 |
if (!mAllowLoad) { |
064 |
synchronized (lock) { |
065 |
try { |
066 |
DebugUtil.debug( "wait start....." ); |
067 |
lock.wait(); |
068 |
DebugUtil.debug( "wait end....." ); |
069 |
} catch (InterruptedException e) { |
070 |
// TODO Auto-generated catch |
071 |
e.printStackTrace(); |
072 |
} |
073 |
} |
074 |
} |
075 |
|
076 |
if (mAllowLoad && firstLoad) { |
077 |
loadImage(mImageUrl, mt, mListener); |
078 |
} |
079 |
if (mAllowLoad && mt <= mStopLoadLimit && mt >= mStartLoadLimit) { |
080 |
loadImage(mImageUrl, mt, mListener); |
081 |
} |
082 |
} |
083 |
}); |
084 |
} |
085 |
|
086 |
private void loadImage( final String mImageUrl, final Integer mt, |
087 |
final OnImageLoadListener mListener) { |
088 |
if (imageCache.containsKey(mImageUrl)) { |
089 |
SoftReference<Drawable> softReference = imageCache.get(mImageUrl); |
090 |
final Drawable d = softReference.get(); |
091 |
if (d != null ) { |
092 |
handler.post( new Runnable() { |
093 |
@Override |
094 |
public void run() { |
095 |
if (mAllowLoad) { |
096 |
mListener.onImageLoad(mt, d); |
097 |
} |
098 |
} |
099 |
}); |
100 |
return ; |
101 |
} |
102 |
} |
103 |
try { |
104 |
final Drawable d = loadImageFromUrl(mImageUrl); |
105 |
if (d != null ) { |
106 |
imageCache.put(mImageUrl, new SoftReference<Drawable>(d)); |
107 |
} |
108 |
handler.post( new Runnable() { |
109 |
@Override |
110 |
public void run() { |
111 |
if (mAllowLoad) { |
112 |
mListener.onImageLoad(mt, d); |
113 |
} |
114 |
} |
115 |
}); |
116 |
} catch (IOException e) { |
117 |
handler.post( new Runnable() { |
118 |
@Override |
119 |
public void run() { |
120 |
mListener.onError(mt); |
121 |
} |
122 |
}); |
123 |
e.printStackTrace(); |
124 |
} |
125 |
} |
126 |
public static Drawable loadImageFromUrl(String url) throws IOException { |
127 |
//DebugUtil.debug(url); |
128 |
if (Environment.getExternalStorageState().equals( |
129 |
Environment.MEDIA_MOUNTED)) { |
130 |
File f = new File(Environment.getExternalStorageDirectory() |
131 |
+ "/TestSyncListView/" + MD5.getMD5(url)); |
132 |
if (f.exists()) { |
133 |
FileInputStream fis = new FileInputStream(f); |
134 |
Drawable d = Drawable.createFromStream(fis, "src" ); |
135 |
return d; |
136 |
} |
137 |
URL m = new URL(url); |
138 |
InputStream i = (InputStream) m.getContent(); |
139 |
DataInputStream in = new DataInputStream(i); |
140 |
FileOutputStream out = new FileOutputStream(f); |
141 |
byte [] buffer = new byte [ 1024 ]; |
142 |
int byteread = 0 ; |
143 |
while ((byteread = in.read(buffer)) != - 1 ) { |
144 |
out.write(buffer, 0 , byteread); |
145 |
} |
146 |
in.close(); |
147 |
out.close(); |
148 |
Drawable d = Drawable.createFromStream(i, "src" ); |
149 |
return loadImageFromUrl(url); |
150 |
} else { |
151 |
URL m = new URL(url); |
152 |
InputStream i = (InputStream) m.getContent(); |
153 |
Drawable d = Drawable.createFromStream(i, "src" ); |
154 |
return d; |
155 |
} |
156 |
} |
157 |
} |
- 本文固定链接: http://www.ithtw.com/1011.html