文档章节

ListView异步加载图片--图片缓存和错位问题解决方案

北ing
 北ing
发布于 2015/10/20 19:22
字数 2491
阅读 63
收藏 0
点赞 0
评论 0

问题1:
加载太多的图片很容易造成OOM异常。

一、图片缓存

方法1:使用二级缓存 ->自己维护一个缓存区
只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象。
所以可以这么做:map里面的键是用来放图片地址的,既可以是网络上的图片地址,也可以SDcard上的图片地址,
map里面的值里面放的是持有软引用的Bitmap.

private Map<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>();

每次为ImageView设置图片时,先去map中寻找,存在就直接用,不存在则从网络加载,下载好后存到map中。

方法2:使用一级缓存,LruCache
2.1 LRU算法简介:
LRU是Least Recently Used 近期最少使用算法。
内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。
什么是LRU算法? LRU是Least Recently Used的缩写,即最少使用页面置换算法,是为虚拟页式存储管理服务的。

2.2 在Android中,有一个叫做LruCache类专门用来做图片缓存处理的。
它有一个特点,当缓存的图片达到了预先设定的值的时候,那么近期使用次数最少的图片就会被回收掉。
步骤:1)要先设置缓存图片的内存大小,这里设置为手机内存的1/10
手机内存的获取方式:int MAXMEMONRY = (int) (Runtime.getRuntime() .maxMemory() / 10);
2)LruCache里面的键值对分别是URL和对应的图片
3)使用时和上面类似,每次给item中的ImageView设置图片时先从缓存中查找,存在直接用,不存在则从网络加载,加载完成存到缓存中。

private LruCache<String, BitmapDrawable> mMemoryCache;
     public MyCache() {
           // 获取应用程序最大可用内存
           int maxMemory = (int) Runtime.getRuntime().maxMemory();
           int cacheSize = maxMemory / 10;//大小为当前程序运行时内存的1/10
           mMemoryCache = new LruCache<String, BitmapDrawable>(cacheSize) {
               @Override
               protected int sizeOf(String key, BitmapDrawable drawable) {
                    return drawable.getBitmap().getByteCount();
              }
          };
     }

方法3.使用三级缓存:利用外部存储(即文件系统来缓存下载的图片)
只是把缓存放到了外部存储中,其他用法不变。
注意:
3.1根据实际情况决定在程序结束后要不要把缓存文件删除,如果程序中加载的图片更新速度很快(如新闻,更新速度很快),就需要在程序结束后清空缓存文件。反之,可以不用删除。

方法4.一级三级缓存配合使用

设置图片时先从一级缓存中取,存在则直接使用,不存在再去三级缓存中查找,存在直接使用,不存在再从网络加载,
注意:
4.1加载完成后,分别存到一级缓存和二级缓存。使用这种方案时,可以把一级缓存设置小一点。
4.2根据实际情况决定在程序结束后要不要把缓存文件删除,如果程序中加载的图片更新速度很快(如新闻,更新速度很快),就需要在程序结束后清空缓存文件。反之,可以不用删除。

**

二、四种方案的比较

**
1.:方案1、2使用的内存是运行时内存,速度相对较快,方案3使用的是二级缓存,速度相对较慢,方案4是一级缓存和二级缓存的配合使用,速度适中。
2.:方案1是自己维护一个缓存,缓存中的图片何时被回收完全由GC决定,所以可能会占用很大内存;方案2是用Android提供的缓存,内部存储原理和方案1类似,但使用了LRU算法,优点能较智能的决定那些图片该回收,哪些不能回收,所以会把内存控制在一个较合理的状态。方案3使用二级缓存,速度虽然慢了点,但是外部存储基本不用担心内存不够用。方案4是一级缓存和二级缓存的结合使用,一级缓存加快使用速度,二级缓存增加使用内存。但操作较麻烦,保存需要两次,也浪费了一些内存。

总结:根据实际情况使用不同方案,方案1不推荐使用,方案2 3 4 各有优点.

**

三、listView异步加载图片错位的问题

**
1.问题:使用ListView异步加载图片时,如果不做处理,会出现图片图片错位的情况,特别是在网速不给力的情况下。
效果图:

这里写图片描述

2.原因:由ListVIew内部回收机制所致。
RecycleBin机制:
这里写图片描述

这里写图片描述

分析:当重用 convertView 时,最初一屏显示 7 条记录, getView 被调用 7 次,创建了 7 个 convertView.
当 Item1 划出屏幕, Item8 进入屏幕时,这时没有为 Item8 创建新的 view 实例, Item8 复用的是
Item1 的 view 如果没有异步不会有任何问题,虽然 Item8 和 Item1 指向的是同一个 view,但滑到
Item8 时刷上了 Item8 的数据,这时 Item1 的数据和 Item8 是一样的,因为它们指向的是同一块内存,
但 Item1 已滚出了屏幕你看不见。当 Item1 再次可见时这块 view 又涮上了 Item1 的数据。

但当有异步下载时就有问题了,假设 Item1 的图片下载的比较慢,Item8 的图片下载的比较快,你滚上去
使 Item8 可见,这时 Item8 先显示它自己下载的图片没错,但等到 Item1 的图片也下载完时你发现
Item8 的图片也变成了 Item1 的图片,因为它们复用的是同一个 view。 如果 Item1 的图片下载的比
Item8 的图片快, Item1 先刷上自己下载的图片,这时你滑下去,Item8 的图片还没下载完, Item8
会先显示 Item1 的图片,因为它们是同一快内存,当 Item8 自己的图片下载完后 Item8 的图片又刷成
了自己的,你再滑上去使 Item1 可见, Item1 的图片也会和 Item8 的图片是一样的,
因为它们指向的是同一块内存。

**

四、解决方案

**
1.给ImageView添加tag
1)在getView()方法中得到一个Imageview时,添加一个tag,tag一般是一个url
2)在异步任务加载图片成功时,通过listView的findViewWithTag(tag)方法得到一个imageview,然后设置图片


@Override
     public View getView( int position, View convertView, ViewGroup parent) {
          if (listView == null)
               listView = (ListView) parent;
          View view = null;
          ImageView imageView = null;
           if (convertView != null)
              view = convertView;
           else {
              view = LayoutInflater.from(context).inflate(R.layout. image_item,
                        parent, false);
          }
          imageView = (ImageView) view.findViewById(R.id.image );
          imageView.setImageResource(R.drawable. ic_launcher);
          String url = data[position];
          imageView.setTag(url);
          BitmapDrawable bitmapDrawable = cache.getBitmapFromMemoryCache(url);
           if (bitmapDrawable != null && imageView != null) {
              imageView.setImageDrawable(bitmapDrawable);
          } else {
               new ImageLoadAsyncTask(listView , context , cache).execute(url);
          }
           return view;
     }

@Override
     protected void onPostExecute(BitmapDrawable drawable) {
          ImageView mImageView = (ImageView) listView.findViewWithTag(imageUrl );
           if (mImageView != null && drawable != null) {
              mImageView.setImageDrawable(drawable);
          }
     }

2、使用Volley的NetworkImageView
1)在item布局文件中,用NetworkImageView替换ImageView
2)使用ImageLoader图片加载器

<?xml version= "1.0" encoding ="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width= "match_parent" android:layout_height= "match_parent" android:orientation= "vertical" >
    <com.android.volley.toolbox.NetworkImageView  android:id="@+id/image" android:layout_width="match_parent" android:layout_height="120dp" android:scaleType="fitXY" android:src="@drawable/ic_launcher" />
</LinearLayout>
@Override
     public View getView( int position, View convertView, ViewGroup parent) {
          String url = getItem(position);
          View view;
           if (convertView == null) {
              view = LayoutInflater.from(getContext()).inflate(
                        R.layout. image_item, n ull) ;
          } else {
              view = convertView;
          }
          NetworkImageView image = (NetworkImageView) view
                   .findViewById(R.id. image);
          image.setDefaultImageResId(R.drawable. ic_launcher);
          image.setErrorImageResId(R.drawable. ic_launcher);
          image.setImageUrl(url, mImageLoader);
           return view;
     }

3、使用弱引用把asynctask和imageview相关联
1)本质是要让ImageView和 BitmapWorkerTask之间建立一个双向关联,互相持有对方的引用,再通过适当的逻辑判断来解决图片乱序问题,然后为了防止出现内存泄漏的情 况,双向关联要使用弱引用的方式建立。

1.自定义的一个 Drawable,让这个Drawable持有BitmapWorkerTask的弱引用。

/** * 自定义的一个 Drawable,让这个 Drawable持有BitmapWorkerTask的弱引用。 */
public class AsyncDrawable extends BitmapDrawable {
     private WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

     public AsyncDrawable(Resources res, Bitmap bitmap,
              BitmapWorkerTask bitmapWorkerTask) {
           super(res, bitmap);
           bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(
                   bitmapWorkerTask);
     }

     public BitmapWorkerTask getBitmapWorkerTask() {
           return bitmapWorkerTaskReference .get();
     }
}

2.定义自己的异步加载任务,让这个任务拥有ImageView的弱引用

/** * 异步下载图片的任务。 * * @author guolin */
class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {

    String imageUrl;

    private BitmapCache bitmapCache;

    private Context context;

    private WeakReference<ImageView> imageViewReference;

    public BitmapWorkerTask(ImageView imageView, Context context,
            BitmapCache bitmapCache) {
        imageViewReference = new WeakReference<ImageView>(imageView);
        this.context = context;
        this.bitmapCache = bitmapCache;
    }

    @Override
    protected BitmapDrawable doInBackground(String... params) {
        imageUrl = params[0];
        // 在后台开始下载图片
        Bitmap bitmap = downloadBitmap(imageUrl);
        BitmapDrawable drawable = new BitmapDrawable(context.getResources(),
                bitmap);
        bitmapCache.addBitmapToMemoryCache(imageUrl, drawable);
        return drawable;
    }

    @Override
    protected void onPostExecute(BitmapDrawable drawable) {
        ImageView imageView = getAttachedImageView();
        if (imageView != null && drawable != null) {
            imageView.setImageDrawable(drawable);
        }
    }

    /** * 获取当前BitmapWorkerTask所关联的ImageView。 */
    private ImageView getAttachedImageView() {
        ImageView imageView = imageViewReference.get();
        BitmapWorkerTask bitmapWorkerTask = BitmapWorkManager.getBitmapWorkerTask(imageView);
        if (this == bitmapWorkerTask) {
            return imageView;
        }
        return null;
    }

    /** * 建立HTTP请求,并获取Bitmap对象。 * * @param imageUrl * 图片的URL地址 * @return 解析后的Bitmap对象 */
    private Bitmap downloadBitmap(String imageUrl) {
        Bitmap bitmap = null;
        HttpURLConnection con = null;
        try {
            URL url = new URL(imageUrl);
            con = (HttpURLConnection) url.openConnection();
            con.setConnectTimeout(5 * 1000);
            con.setReadTimeout(10 * 1000);
            bitmap = BitmapFactory.decodeStream(con.getInputStream());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (con != null) {
                con.disconnect();
            }
        }
        return bitmap;
    }

}

3.使用

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (mListView == null) {
            mListView = (ListView) parent;
        }
        String url = getItem(position);
        View view;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(
                    R.layout.image_item, null);
        } else {
            view = convertView;
        }
        ImageView image = (ImageView) view.findViewById(R.id.image);
        BitmapDrawable drawable = mMemoryCache.getBitmapFromMemoryCache(url);// 从缓存中取对应url图片
        if (drawable != null) {
            image.setImageDrawable(drawable);
            // cancelPotentialWork返回true代表当前ImageView正在加载另一张图片,
            // 所以要cancel上个任务,来执行新的任务
        } else if (BitmapWorkManager.cancelPotentialWork(url, image)) {
            BitmapWorkerTask task = new BitmapWorkerTask(image, getContext(),
                    mMemoryCache);
            AsyncDrawable asyncDrawable = new AsyncDrawable(getContext()
                    .getResources(), mLoadingBitmap, task);
            image.setImageDrawable(asyncDrawable);
            task.execute(url);
        }
        return view;
    }

源码

注:Android中对于图片的加载有很多开源的框架,这里列举几个:
1.xUtils(中国人做的哦)
https://github.com/wyouflf/xUtils

2.Volley(谷歌,非常好用)
https://github.com/adamrocker/volley

3.Picasso(感觉没前两个好)
https://github.com/square/picasso

4.Glide
https://github.com/bumptech/glide

5.Universal ImageLoader

6.Fresco (Facebook,非常强大)
https://github.com/facebook/fresco

版权声明:本文为博主原创文章,未经博主允许不得转载。

© 著作权归作者所有

共有 人打赏支持
北ing
粉丝 0
博文 12
码字总数 17332
作品 0
海淀
ImageView异步加载图片--ImageLoaderSample

这个可以实现ImageView异步加载图片,内存缓存,文件缓存,imageview显示图片时增加淡入淡出动画。 解决了: 1. listview加载oom问题 listview加载时卡顿的现象 listview加载时item中图片重复...

咕噜不爱猫 ⋅ 2014/02/28 ⋅ 0

android viewHolder处理listView滑动

在没有用viewHolder的情况下,listView表现效率低下。如果加载的数量过多则会一点点的消耗内存,直到抛出oom。开始异步加载图片会出现图片错位的问题,后来查阅资料将holder里边的图片地址和...

风过后 ⋅ 2013/12/11 ⋅ 4

Android ListView滑动过程中图片显示重复错乱闪烁问题解决

最新内容建议直接访问原文:Android ListView滑动过程中图片显示重复错乱闪烁问题解决 主要分析Android ListView滚动过程中图片显示重复、错乱、闪烁的原因及解决方法,顺带提及ListView的缓...

Trinea ⋅ 2013/08/07 ⋅ 4

Android基础之ListView的比较特别的属性

本文来源:终端研发部 listView面试汇总,应用开发者必须掌握的基本知识 1.首先是stackFromBottom属性,值为true和false stackFromBottom="true" 从下到上依次填充listview stackFromBottom=...

正阳Android ⋅ 2017/11/27 ⋅ 0

(BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之

前几天的原文有一个线程管理与加载源过多,造成浪费流量的问题.下面对这进下改进的一些说明(红色为新加) 这两天一直在优化这个问题.google也很多种做法.但发现都是比较不全面. 比如: 一些只实...

Duke__ ⋅ 2014/03/04 ⋅ 0

Android异步加载全解析之使用多线程

异步加载之使用多线程 初次尝试 异步、异步,其实说白了就是多任务处理,也就是多线程执行,多线程那就会有各种问题,我们一步步来看,首先,我们创建一个class——ImageLoaderWithoutCache...

eclipse_xu ⋅ 2015/03/19 ⋅ 0

Android快速开发框架:ThinkAndroid

ThinkAndroid是包含Android mvc和简易sqliteorm以及ioc模块,它封装了Android httpclitent中的http模块,具有快速构建文件缓存功能,无需考虑什么格式的文件,都可以非常轻松的实现缓存,它实...

丨小丶牧灬 ⋅ 2015/07/30 ⋅ 1

android常用开源库分享

android常用开源库分享 这次就介绍了这些android开源库,各位有好介绍的可以私信我 [SinaWeiBo][15]我的博客是[慕容博客][16],欢迎来和我交流 异步ImageView [android-smart-image-view][6]...

inmyfree ⋅ 2013/05/09 ⋅ 5

Android 快速开发框架--ThinkAndroid

ThinkAndroid简介 ThinkAndroid是一个免费的开源的、简易的、遵循Apache2开源协议发布的Android开发框架,其开发宗旨是简单、快速的进行Android应用程序的开发,包含Androidmvc、简易sqlite ...

white-cat ⋅ 2013/05/05 ⋅ 6

android listview异步加载图片问题

学做android,自己想模仿QQ空间做一个小demo listview异步加载图片的时候遇到了一个问题 异步加载用到了SoftReference 和文件缓存的方法 每次加载图片的时候,也在内存或缓存中找到了图片 第...

喵_小黑 ⋅ 2014/09/11 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

OSChina 周六乱弹 —— 假如你被熊困到树上

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @小小编辑:推荐歌曲《如果写不出好的和弦就该在洒满阳光的钢琴前一起吃布丁》 《如果写不出好的和弦就该在洒满阳光的钢琴前一起吃布丁》- 谢...

小小编辑 ⋅ 30分钟前 ⋅ 2

vbs 取文件大小 字节

dim namedim fs, s'name = Inputbox("姓名")'msgbox(name)set fs = wscript.createobject("scripting.filesystemobject") 'fs为FSO实例if (fs.folderexists("c:\temp"))......

vga ⋅ 今天 ⋅ 1

高并发之Nginx的限流

首先Nginx的版本号有要求,最低为1.11.5 如果低于这个版本,在Nginx的配置中 upstream web_app { server 到达Ip1:端口 max_conns=10; server 到达Ip2:端口 max_conns=10; } server { listen ...

算法之名 ⋅ 今天 ⋅ 0

Spring | IOC AOP 注解 简单使用

写在前面的话 很久没更新笔记了,有人会抱怨:小冯啊,你是不是在偷懒啊,没有学习了。老哥,真的冤枉:我觉得我自己很菜,还在努力学习呢,正在学习Vue.js做管理系统呢。即便这样,我还是不...

Wenyi_Feng ⋅ 今天 ⋅ 0

博客迁移到 https://www.jianshu.com/u/aa501451a235

博客迁移到 https://www.jianshu.com/u/aa501451a235 本博客不再更新

为为02 ⋅ 今天 ⋅ 0

win10怎么彻底关闭自动更新

win10自带的更新每天都很多,每一次下载都要占用大量网络,而且安装要等得时间也蛮久的。 工具/原料 Win10 方法/步骤 单击左下角开始菜单点击设置图标进入设置界面 在设置窗口中输入“服务”...

阿K1225 ⋅ 今天 ⋅ 0

Elasticsearch 6.3.0 SQL功能使用案例分享

The best elasticsearch highlevel java rest api-----bboss Elasticsearch 6.3.0 官方新推出的SQL检索插件非常不错,本文一个实际案例来介绍其使用方法。 1.代码中的sql检索 @Testpu...

bboss ⋅ 今天 ⋅ 0

informix数据库在linux中的安装以及用java/c/c++访问

一、安装前准备 安装JDK(略) 到IBM官网上下载informix软件:iif.12.10.FC9DE.linux-x86_64.tar放在某个大家都可以访问的目录比如:/mypkg,并解压到该目录下。 我也放到了百度云和天翼云上...

wangxuwei ⋅ 今天 ⋅ 0

PHP语言系统ZBLOG或许无法重现月光博客的闪耀历史[图]

最近在写博客,希望通过自己努力打造一个优秀的教育类主题博客,名动江湖,但是问题来了,现在写博客还有前途吗?面对强大的自媒体站点围剿,还有信心和可能型吗? 至于程序部分,我选择了P...

原创小博客 ⋅ 今天 ⋅ 0

IntelliJ IDEA 2018.1新特性

工欲善其事必先利其器,如果有一款IDE可以让你更高效地专注于开发以及源码阅读,为什么不试一试? 本文转载自:netty技术内幕 3月27日,jetbrains正式发布期待已久的IntelliJ IDEA 2018.1,再...

Romane ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部