listview异步加载优化


         异步加载图片基本思想:

1.      先从内存缓存中获取图片显示(内存缓冲)

2.      获取不到的话从SD卡里获取(SD卡缓冲)

3.      都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示(视情况看是否要显示)


OK,先上adapter的代码:



01 publicclassLoaderAdapter extendsBaseAdapter{
02  
03         privatestaticfinal String TAG = "LoaderAdapter";
04         privatebooleanmBusy = false;
05  
06         publicvoidsetFlagBusy(booleanbusy) {
07                 this.mBusy = busy;
08         }
09  
10          
11         privateImageLoader mImageLoader;
12         privateintmCount;
13         privateContext mContext;
14         privateString[] urlArrays;
15          
16          
17         publicLoaderAdapter(intcount, Context context, String []url) {
18                 this.mCount = count;
19                 this.mContext = context;
20                 urlArrays = url;
21                 mImageLoader = newImageLoader(context);
22         }
23          
24         publicImageLoader getImageLoader(){
25                 returnmImageLoader;
26         }
27  
28         @Override
29         publicintgetCount() {
30                 returnmCount;
31         }
32  
33         @Override
34         publicObject getItem(intposition) {
35                 returnposition;
36         }
37  
38         @Override
39         publiclonggetItemId(intposition) {
40                 returnposition;
41         }
42  
43         @Override
44         publicView getView(intposition, View convertView, ViewGroup parent) {
45  
46                 ViewHolder viewHolder = null;
47                 if(convertView == null) {
48                         convertView = LayoutInflater.from(mContext).inflate(
49                                         R.layout.list_item,null);
50                         viewHolder = newViewHolder();
51                         viewHolder.mTextView = (TextView) convertView
52                                         .findViewById(R.id.tv_tips);
53                         viewHolder.mImageView = (ImageView) convertView
54                                         .findViewById(R.id.iv_image);
55                         convertView.setTag(viewHolder);
56                 }else{
57                         viewHolder = (ViewHolder) convertView.getTag();
58                 }
59                 String url = "";
60                 url = urlArrays[position % urlArrays.length];
61                  
62                 viewHolder.mImageView.setImageResource(R.drawable.ic_launcher);
63                  
64  
65                 if(!mBusy) {
66                         mImageLoader.DisplayImage(url, viewHolder.mImageView, false);
67                         viewHolder.mTextView.setText("--"+ position
68                                         +"--IDLE ||TOUCH_SCROLL");
69                 }else{
70                         mImageLoader.DisplayImage(url, viewHolder.mImageView, true);               
71                         viewHolder.mTextView.setText("--"+ position + "--FLING");
72                 }
73                 returnconvertView;
74         }
75  
76         staticclassViewHolder {
77                 TextView mTextView;
78                 ImageView mImageView;
79         }
80 }



关键代码是ImageLoader的DisplayImage方法,再看ImageLoader的实现


001 publicclassImageLoader {
002  
003         privateMemoryCache memoryCache = newMemoryCache();
004         privateAbstractFileCache fileCache;
005         privateMap<ImageView, String> imageViews = Collections
006                         .synchronizedMap(newWeakHashMap<ImageView, String>());
007         // 线程池
008         privateExecutorService executorService;
009  
010         publicImageLoader(Context context) {
011                 fileCache = newFileCache(context);
012                 executorService = Executors.newFixedThreadPool(5);
013         }
014  
015         // 最主要的方法
016         publicvoidDisplayImage(String url, ImageView imageView, booleanisLoadOnlyFromCache) {
017                 imageViews.put(imageView, url);
018                 // 先从内存缓存中查找
019  
020                 Bitmap bitmap = memoryCache.get(url);
021                 if(bitmap != null)
022                         imageView.setImageBitmap(bitmap);
023                 elseif(!isLoadOnlyFromCache){
024                          
025                         // 若没有的话则开启新线程加载图片
026                         queuePhoto(url, imageView);
027                 }
028         }
029  
030         privatevoidqueuePhoto(String url, ImageView imageView) {
031                 PhotoToLoad p = newPhotoToLoad(url, imageView);
032                 executorService.submit(newPhotosLoader(p));
033         }
034  
035         privateBitmap getBitmap(String url) {
036                 File f = fileCache.getFile(url);
037                  
038                 // 先从文件缓存中查找是否有
039                 Bitmap b = null;
040                 if(f != null&& f.exists()){
041                         b = decodeFile(f);
042                 }
043                 if(b != null){
044                         returnb;
045                 }
046                 // 最后从指定的url中下载图片
047                 try{
048                         Bitmap bitmap = null;
049                         URL imageUrl = newURL(url);
050                         HttpURLConnection conn = (HttpURLConnection) imageUrl
051                                         .openConnection();
052                         conn.setConnectTimeout(30000);
053                         conn.setReadTimeout(30000);
054                         conn.setInstanceFollowRedirects(true);
055                         InputStream is = conn.getInputStream();
056                         OutputStream os = newFileOutputStream(f);
057                         CopyStream(is, os);
058                         os.close();
059                         bitmap = decodeFile(f);
060                         returnbitmap;
061                 }catch(Exception ex) {
062                         Log.e("","getBitmap catch Exception...\nmessage = " + ex.getMessage());
063                         returnnull;
064                 }
065         }
066  
067         // decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的
068         privateBitmap decodeFile(File f) {
069                 try{
070                         // decode image size
071                         BitmapFactory.Options o = newBitmapFactory.Options();
072                         o.inJustDecodeBounds = true;
073                         BitmapFactory.decodeStream(newFileInputStream(f),null, o);
074  
075                         // Find the correct scale value. It should be the power of 2.
076                         finalintREQUIRED_SIZE = 100;
077                         intwidth_tmp = o.outWidth, height_tmp = o.outHeight;
078                         intscale = 1;
079                         while(true) {
080                                 if(width_tmp / 2< REQUIRED_SIZE
081                                                 || height_tmp / 2< REQUIRED_SIZE)
082                                         break;
083                                 width_tmp /= 2;
084                                 height_tmp /= 2;
085                                 scale *= 2;
086                         }
087  
088                         // decode with inSampleSize
089                         BitmapFactory.Options o2 = newBitmapFactory.Options();
090                         o2.inSampleSize = scale;
091                         returnBitmapFactory.decodeStream(newFileInputStream(f),null, o2);
092                 }catch(FileNotFoundException e) {
093                 }
094                 returnnull;
095         }
096  
097         // Task for the queue
098         privateclassPhotoToLoad {
099                 publicString url;
100                 publicImageView imageView;
101  
102                 publicPhotoToLoad(String u, ImageView i) {
103                         url = u;
104                         imageView = i;
105                 }
106         }
107  
108         classPhotosLoaderimplementsRunnable {
109                 PhotoToLoad photoToLoad;
110  
111                 PhotosLoader(PhotoToLoad photoToLoad) {
112                         this.photoToLoad = photoToLoad;
113                 }
114  
115                 @Override
116                 publicvoidrun() {
117                         if(imageViewReused(photoToLoad))
118                                 return;
119                         Bitmap bmp = getBitmap(photoToLoad.url);
120                         memoryCache.put(photoToLoad.url, bmp);
121                         if(imageViewReused(photoToLoad))
122                                 return;
123                         BitmapDisplayer bd = newBitmapDisplayer(bmp, photoToLoad);
124                         // 更新的操作放在UI线程中
125                         Activity a = (Activity) photoToLoad.imageView.getContext();
126                         a.runOnUiThread(bd);
127                 }
128         }
129  
130         /**
131          * 防止图片错位
132          *
133          * @param photoToLoad
134          * @return
135          */
136         booleanimageViewReused(PhotoToLoad photoToLoad) {
137                 String tag = imageViews.get(photoToLoad.imageView);
138                 if(tag == null|| !tag.equals(photoToLoad.url))
139                         returntrue;
140                 returnfalse;
141         }
142  
143         // 用于在UI线程中更新界面
144         classBitmapDisplayerimplementsRunnable {
145                 Bitmap bitmap;
146                 PhotoToLoad photoToLoad;
147  
148                 publicBitmapDisplayer(Bitmap b, PhotoToLoad p) {
149                         bitmap = b;
150                         photoToLoad = p;
151                 }
152  
153                 publicvoidrun() {
154                         if(imageViewReused(photoToLoad))
155                                 return;
156                         if(bitmap != null)
157                                 photoToLoad.imageView.setImageBitmap(bitmap);
158          
159                 }
160         }
161  
162         publicvoidclearCache() {
163                 memoryCache.clear();
164                 fileCache.clear();
165         }
166  
167         publicstaticvoid CopyStream(InputStream is, OutputStream os) {
168                 finalintbuffer_size = 1024;
169                 try{
170                         byte[] bytes = newbyte[buffer_size];
171                         for(;;) {
172                                 intcount = is.read(bytes, 0, buffer_size);
173                                 if(count == -1)
174                                         break;
175                                 os.write(bytes,0, count);
176                         }
177                 }catch(Exception ex) {
178                         Log.e("","CopyStream catch Exception...");
179                 }
180         }
181 }



先从内存中加载,没有则开启线程从SD卡或网络中获取,这里注意从SD卡获取图片是放在子线程里执行的,否则快速滑屏的话会不够流畅,这是优化一。于此同时,在adapter里有个busy变量,表示listview是否处于滑动状态,如果是滑动状态则仅从内存中获取图片,没有的话无需再开启线程去外存或网络获取图片,这是优化二。ImageLoader里的线程使用了线程池,从而避免了过多线程频繁创建和销毁,有的童鞋每次总是new一个线程去执行这是非常不可取的,好一点的用的AsyncTask类,其实内部也是用到了线程池。在从网络获取图片时,先是将其保存到sd卡,然后再加载到内存,这么做的好处是在加载到内存时可以做个压缩处理,以减少图片所占内存,这是优化三。  

而图片错位问题的本质源于我们的listview使用了缓存convertView,假设一种场景,一个listview一屏显示九个item,那么在拉出第十个item的时候,事实上该item是重复使用了第一个item,也就是说在第一个item从网络中下载图片并最终要显示的时候其实该item已经不在当前显示区域内了,此时显示的后果将是在可能在第十个item上输出图像,这就导致了图片错位的问题。所以解决之道在于可见则显示,不可见则不显示。在ImageLoader里有个imageViews的map对象,就是用于保存当前显示区域图像对应的url集,在显示前判断处理一下即可。

下面再说下内存缓冲机制,本例采用的是LRU算法,先看看MemoryCache的实现


01 publicclassMemoryCache {
02  
03         privatestaticfinal String TAG = "MemoryCache";
04         // 放入缓存时是个同步操作
05         // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU
06         // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
07         privateMap<String, Bitmap> cache = Collections
08                         .synchronizedMap(newLinkedHashMap<String, Bitmap>(10,1.5f,true));
09         // 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存
10         privatelongsize = 0;// current allocated size
11         // 缓存只能占用的最大堆内存
12         privatelonglimit = 1000000;// max memory in bytes
13  
14         publicMemoryCache() {
15                 // use 25% of available heap size
16                 setLimit(Runtime.getRuntime().maxMemory() / 10);
17         }
18  
19         publicvoidsetLimit(longnew_limit) {
20                 limit = new_limit;
21                 Log.i(TAG,"MemoryCache will use up to " + limit / 1024. / 1024. + "MB");
22         }
23  
24         publicBitmap get(String id) {
25                 try{
26                         if(!cache.containsKey(id))
27                                 returnnull;
28                         returncache.get(id);
29                 }catch(NullPointerException ex) {
30                         returnnull;
31                 }
32         }
33  
34         publicvoidput(String id, Bitmap bitmap) {
35                 try{
36                         if(cache.containsKey(id))
37                                 size -= getSizeInBytes(cache.get(id));
38                         cache.put(id, bitmap);
39                         size += getSizeInBytes(bitmap);
40                         checkSize();
41                 }catch(Throwable th) {
42                         th.printStackTrace();
43                 }
44         }
45  
46         /**
47          * 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
48          *
49          */
50         privatevoidcheckSize() {
51                 Log.i(TAG,"cache size=" + size + " length=" + cache.size());
52                 if(size > limit) {
53                         // 先遍历最近最少使用的元素
54                         Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator();
55                         while(iter.hasNext()) {
56                                 Entry<String, Bitmap> entry = iter.next();
57                                 size -= getSizeInBytes(entry.getValue());
58                                 iter.remove();
59                                 if(size <= limit)
60                                         break;
61                         }
62                         Log.i(TAG,"Clean cache. New size " + cache.size());
63                 }
64         }
65  
66         publicvoidclear() {
67                 cache.clear();
68         }
69  
70         /**
71          * 图片占用的内存
72          *
73          * <a href="\"http://www.eoeandroid.com/home.php?mod=space&uid=2768922\"" target="\"_blank\"">@Param</a> bitmap
74          *
75          * @return
76          */
77         longgetSizeInBytes(Bitmap bitmap) {
78                 if(bitmap == null)
79                         return0;
80                 returnbitmap.getRowBytes() * bitmap.getHeight();
81         }
82 }



首先限制内存图片缓冲的堆内存大小,每次有图片往缓存里加时判断是否超过限制大小,超过的话就从中取出最少使用的图片并将其移除,当然这里如果不采用这种方式,换做软引用也是可行的,二者目的皆是最大程度的利用已存在于内存中的图片缓存,避免重复制造垃圾增加GC负担,OOM溢出往往皆因内存瞬时大量增加而垃圾回收不及时造成的。只不过二者区别在于LinkedHashMap里的图片缓存在没有移除出去之前是不会被GC回收的,而SoftReference里的图片缓存在没有其他引用保存时随时都会被GC回收。所以在使用LinkedHashMap这种LRU算法缓存更有利于图片的有效命中,当然二者配合使用的话效果更佳,即从LinkedHashMap里移除出的缓存放到SoftReference里,这就是内存的二级缓存,有兴趣的童鞋不凡一试


代码在我的资源里可以下载。

http://download.csdn.net/detail/w3qrrty/7455147



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值