先说说这篇文章的优点把,开启线程异步加载图片,然后刷新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
相关推荐
Android 异步加载图片,对ListView的异步加载图片的功能演示,主要根据url读取图片返回流的方法。为了方便演示,将请求图片的链接先固定,每读取好一个图片就更新,界面比较简单,当然你可以做成比较好的,像很多好...
Android Listview异步加载图片,图片错位解决方案
android listview异步加载图片实例 用到了线程池 下载的图片会保存到本地 并在数据库中保留记录 再次加载时会直接从本地读取
NULL 博文链接:https://zjingye.iteye.com/blog/1936268
Android实现ListView异步加载图片
Android ListView 异步加载图片,一点也不卡,使用AsyncTask和WeakReference,注释详尽
Android ListView异步加载图片,优化滚动效果,不卡顿、流畅显示。主要给新人了解ListView和AsyncTask、Json等使用。
android listView图片异步加载(拖动时不加载,双缓存)
android listview 异步加载网络图片
AystnPicture_Android ListView异步加载图片.rar
android中listView的优化,同时listView中的列表内容来源于网络,从网络上下载图片图片显示在listViw中,测试用的是1000个item,测试listView的优化!
AndroidListView异步加载图片乱序问题,原因分析及解决方案.docx
android ListView如果从网络加载图片,直接getView,肯定会阻塞UI导致滑动很不流畅,本实例优化了getView中图片的获取。
AsyncTask的使用及ListView的常见优化 asyncTask异步加载数据 使用了LruCache优化图片加载 通过滑动监听提高ListView滑动流畅度.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习...
android中ListView异步加载图片时的图片错位问题解决方案
在项目里面使用ListView,并要求ListView的条目中有图片显示,而且这个图片是通过网络动态获取的。 这时候,会发现ListView加载很慢,半天才显示出来,影响了用户的体验。...应该另外开辟线程异步下载图片。
①ListView异步加载图片的方式 ②给ImageView设置Tag,解决图片覆盖问题 ③采用LruCache缓存已经加载过的图片 ④当ListView滚动时不加载图片,滚动停止时才加载图片,从而达到ListView滑动很流畅的效果 ⑤当...