先说说这篇文章的优点把,开启线程异步加载图片,然后刷新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