Listview异步加载缓存图片,解决快速滑动问题

搜了大量资料见的大多避免oom的方法有压缩和缓存,这里也采用这2种方法吧。

   压缩就不用我说了,缓存图片我用的LruCache这个类,本身已经实现了同步,这里就不再多说什么了,不知道的同学可以去研究下,这里主要想讲的的异步加载的时机。在这里写下也只是分享下我的体验,欢迎拍砖~~~

  大家做过这个的都碰到过,快速滑动时由于大量异步加载和message消息的等待排队,当快速滑动停止时要等好一会才能轮到当前可视item图片的加载显示,如何避免这个问题? 

  那就从问题来源入手:快速滑过的那些item图片可以先暂不加载,等到用户正常划过时再去加载不迟~~~
  
  我的代码思路:在getView方法里面判断Listview的滑动状态,如果正在滑动,则不加载,但要保存此时的图片信息,等到适当机会再去加载,否则异步加载图片~~~

  实现Listview状态的标识,代码:
  
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
listView.setOnScrollListener( new OnScrollListener() {
                         @Override
                         public void onScrollStateChanged(AbsListView view, int scrollState) {
                                 //标识正在滑动中
                                 if (scrollState==OnScrollListener.SCROLL_STATE_FLING){
                                        isBusy= true ;
                                 } else {
                                        isBusy= false ;
                                        asyncLoading();
                                 }
                         }
                         
                         @Override
                         public void onScroll(AbsListView view, int firstVisibleItem,
                                        int visibleItemCount, int totalItemCount) {
                                 firstItem=firstVisibleItem;
                                 bottmItem=firstItem+visibleItemCount;
                         }
                });
         }


isBusy用来标识Listview是否处在滑动状态,asyncLoading()方法下面会说到

现在知道了当前listview的滚动状态,那么现在就开始在getview里面加载资源吧:
  
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
                         Bitmap bitmap=cache.get( "" +position); //取出缓存中的bitmap
                         final ViewHolder holder;
                         if (convertView== null ){
                                 holder= new ViewHolder();
                                 convertView=getLayoutInflater().inflate(R.layout.item, null );
                                 holder.txt=(TextView) convertView.findViewById(R.id.txt);
                                 holder.img=(ImageView) convertView.findViewById(R.id.img);
                                 convertView.setTag(holder);
                         } else {
                                 holder=(ViewHolder) convertView.getTag();
                         }
                           if (bitmap== null ){ //如果没有缓存
                                                 if (!isBusy){ //判断当前listview不在滑动状态
                                                         Log.d( "==========" , "正常加载当前位置图片:" +position);
                                                         executor.execute( new Runnable() { //异步加载图片
                                                                @Override
                                                                public void run() {
                                                                         Bitmap bitmap=DownLoadImage(); //模拟下载图片
                                                                         if (bitmap!= null ){
                                                                                 cache.put( "" +position, bitmap);
                                                                                 Message message=handler.obtainMessage();
                                                                                 message.obj=holder.img;
                                                                                 message.arg1=position;
                                                                                 message.what=LOADIMAGE;
                                                                                 handler.sendMessage(message); //图片下载完成后通知更新
                                                                         }
                                                                }
                                                         });
                                                 }
                                                 else { //如果当前listview在快速滑动状态
                                                         Message message=handler.obtainMessage();
                                                         message.obj=holder.img;
                                                         message.arg1=position;
                                                         message.what=LOADIMAGE;
                                                         messages.add(message); ///记录当前getview的图片资源信息,保存在message中,放入到集合等待加载
                                                 }
                                                 holder.img.setImageBitmap(defaultBitmap); //设置默认图片,等图片资源下载完毕后在更新
                                        } else {
                                                 holder.img.setImageBitmap(bitmap);
                                        }
                         holder.txt.setText(position+ "" );
                         return convertView;
                


代码的注释很清楚了,我在这里再说下,如果当前listview不在滑动状态时,正常加载图片,否则就记录当前的图片信息,存放到一个集合中,等待适当的机会去加载,什么时候适当呢?那就是listview不在滑动的时候,即isbusy=false时
那回过头来看上面出现过的asyncLoading()这个方法吧

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private   void asyncLoading(){
                int size=messages.size();
                for ( int i=size- 1 ;i>= 0 ;i--){ //从集合的末尾开始遍历
                         final Message msg=messages.get(i);
                         if (msg.arg1>=firstItem- 1 &&msg.arg1<bottmItem+ 1 ){ //取出当前的position(也就是msg.arg1),判断该position是否在当前可视位置,(如果不要这个判断的话,则加载所有先前滑动过去的item的图片)
                                 Log.e( "==========" , "滑动结束,手动加载当前位置图片:" +msg.arg1);
                                 cache.put(msg.arg1+ "" , defaultBitmap); //这边一定要将position事先存放进去,占个位置,表明该位置已经有图片在下载了,不然可能会出现重复下载的情况
                                 FILLINGexecutor.execute( new Runnable() {
                                        @Override
                                        public void run() {
                                                 Bitmap bitmap=DownLoadImage();
                                                 if (bitmap!= null ){
                                                         cache.put(msg.arg1+ "" , bitmap); //下载完成后,再将defaultBitmap图片覆盖
                                                         handler.sendMessageAtFrontOfQueue(msg); //放到消息队前,因为要最先加载当前可视的item图片资源
                                                 }
                                        }
                                 });
                         } else {
                                 break ; //跳出循环(只要当前不满足,之后的也不会满足)
                         }
                }
                messages.clear(); //清空消息
         }


注释很清楚了,也就不再说了。

总之,demo实现了这样一个效果,每个item的图片都属于自己,没有重用(因为同一张图片每次都是重新加载到内存的),当正常滑动listview时,则正常加载图片,当快速滑动时,只加载listview停下后可视item的图片,这就避免了等待啦,因为之前被滑过item的图片没有被异步加载哦~~欢迎提出意见,大家一起学习~~~

当然,真正的应用是不只在getview方法里面去加载的,因为滑动过后才会去加载,体验效果会很差,这里只是学习用


存在错位bug:

你会发现有时同一张图片会连续变动1、2次,甚至更多次,那是因为convertView缓存的缘故,
比如消息队列中有消息要通知刷新position=10,16,22这3个图片的位置,而这三个item恰好使用同一个convertView,所以他们也是使用的同一个ImageVIew
此时position=22的item是可视
那么这个ImageVIew.setImageBitmap()的方法会被连续调用3次
导致你们会看到图片连续变动

这个bug在本demo中很好解决,2个方法:
1:就是判断当前可视的item位置再去设
2:在手动发送message之前清空消息队列(这种方法适合于一定要是非常快速滑动的那种,因为listview稍微滑下就会处于busy状态,此时正常加载的position还是可视的)

提出问题:如果一开始就去加载全部图片后缓存,而不是在getview里面开线程去加载图片,那么如何通知到对应的图片UI更新以及错位呢?这过程会发生什么问题?你可以亲自动手实现下,提醒下加载图片是耗时的哦~~~
tip:我自己碰到了message消息过期的问题,正在解决中。。



奉上代码:主要是图片大,代码就一个类
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值