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

研究了一天,终于达到了自己想要学习的目的:缓存图片+异步加载,

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

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

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

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

  实现Listview状态的标识,代码:
  [mw_shl_code=java,true]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;
                        }
                });
        }[/mw_shl_code]

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

现在知道了当前listview的滚动状态,那么现在就开始在getview里面加载资源吧:
  [mw_shl_code=java,true]
                        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;
                [/mw_shl_code]

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

[mw_shl_code=java,true]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();//清空消息
        }[/mw_shl_code]

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

总之,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更新以及错位呢?这过程会发生什么问题?你可以亲自动手实现下,提醒下加载图片是耗时的哦~~~
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值