Listview异步加载图片之优化篇

在APP应用中,listview的异步加载图片方式能够带来很好的用户体验,同时也是考量程序性能的一个重要指标。关于listview的异步加载,网上其实很多示例了,中心思想都差不多,不过很多版本或是有bug,或是有性能问题有待优化。有鉴于此,本人在网上找了个相对理想的版本并在此基础上进行改造,下面就让在下阐述其原理以探索个中奥秘,与诸君共赏…

贴张效果图先:

2013-2-1 17:25 上传

下载附件 (214.08 KB)

异步加载图片基本思想:

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

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

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

OK,先上adapter的代码:

?

代码片段,双击复制

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

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80


public class LoaderAdapter extends BaseAdapter{

 

        private static final String TAG = "LoaderAdapter";

        private boolean mBusy = false;

 

        public void setFlagBusy(boolean busy) {

                this.mBusy = busy;

        }

 

        

        private ImageLoader mImageLoader;

        private int mCount;

        private Context mContext;

        private String[] urlArrays;

        

        

        public LoaderAdapter(int count, Context context, String []url) {

                this.mCount = count;

                this.mContext = context;

                urlArrays = url;

                mImageLoader = new ImageLoader(context);

        }

        

        public ImageLoader getImageLoader(){

                return mImageLoader;

        }

 

        @Override

        public int getCount() {

                return mCount;

        }

 

        @Override

        public Object getItem(int position) {

                return position;

        }

 

        @Override

        public long getItemId(int position) {

                return position;

        }

 

        @Override

        public View getView(int position, View convertView, ViewGroup parent) {

 

                ViewHolder viewHolder = null;

                if (convertView == null) {

                        convertView = LayoutInflater.from(mContext).inflate(

                                        R.layout.list_item, null);

                        viewHolder = new ViewHolder();

                        viewHolder.mTextView = (TextView) convertView

                                        .findViewById(R.id.tv_tips);

                        viewHolder.mImageView = (ImageView) convertView

                                        .findViewById(R.id.iv_image);

                        convertView.setTag(viewHolder);

                } else {

                        viewHolder = (ViewHolder) convertView.getTag();

                }

                String url = "";

                url = urlArrays[position % urlArrays.length];

                

                viewHolder.mImageView.setImageResource(R.drawable.ic_launcher);

                

 

                if (!mBusy) {

                        mImageLoader.DisplayImage(url, viewHolder.mImageView, false);

                        viewHolder.mTextView.setText("--" + position

                                        + "--IDLE ||TOUCH_SCROLL");

                } else {

                        mImageLoader.DisplayImage(url, viewHolder.mImageView, true);                

                        viewHolder.mTextView.setText("--" + position + "--FLING");

                }

                return convertView;

        }

 

        static class ViewHolder {

                TextView mTextView;

                ImageView mImageView;

        }

}

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

?

代码片段,双击复制

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

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181


public class ImageLoader {

 

        private MemoryCache memoryCache = new MemoryCache();

        private AbstractFileCache fileCache;

        private Map imageViews = Collections

                        .synchronizedMap(new WeakHashMap());

        // 线程池

        private ExecutorService executorService;

 

        public ImageLoader(Context context) {

                fileCache = new FileCache(context);

                executorService = Executors.newFixedThreadPool(5);

        }

 

        // 最主要的方法

        public void DisplayImage(String url, ImageView imageView, boolean isLoadOnlyFromCache) {

                imageViews.put(imageView, url);

                // 先从内存缓存中查找

 

                Bitmap bitmap = memoryCache.get(url);

                if (bitmap != null)

                        imageView.setImageBitmap(bitmap);

                else if (!isLoadOnlyFromCache){

                        

                        // 若没有的话则开启新线程加载图片

                        queuePhoto(url, imageView);

                }

        }

 

        private void queuePhoto(String url, ImageView imageView) {

                PhotoToLoad p = new PhotoToLoad(url, imageView);

                executorService.submit(new PhotosLoader(p));

        }

 

        private Bitmap getBitmap(String url) {

                File f = fileCache.getFile(url);

                

                // 先从文件缓存中查找是否有

                Bitmap b = null;

                if (f != null && f.exists()){

                        b = decodeFile(f);

                }

                if (b != null){

                        return b;

                }

                // 最后从指定的url中下载图片

                try {

                        Bitmap bitmap = null;

                        URL imageUrl = new URL(url);

                        HttpURLConnection conn = (HttpURLConnection) imageUrl

                                        .openConnection();

                        conn.setConnectTimeout(30000);

                        conn.setReadTimeout(30000);

                        conn.setInstanceFollowRedirects(true);

                        InputStream is = conn.getInputStream();

                        OutputStream os = new FileOutputStream(f);

                        CopyStream(is, os);

                        os.close();

                        bitmap = decodeFile(f);

                        return bitmap;

                } catch (Exception ex) {

                        Log.e("", "getBitmap catch Exception...\nmessage = " + ex.getMessage());

                        return null;

                }

        }

 

        // decode这个图片并且按比例缩放以减少内存消耗,虚拟机对每张图片的缓存大小也是有限制的

        private Bitmap decodeFile(File f) {

                try {

                        // decode image size

                        BitmapFactory.Options o = new BitmapFactory.Options();

                        o.inJustDecodeBounds = true;

                        BitmapFactory.decodeStream(new FileInputStream(f), null, o);

 

                        // Find the correct scale value. It should be the power of 2.

                        final int REQUIRED_SIZE = 100;

                        int width_tmp = o.outWidth, height_tmp = o.outHeight;

                        int scale = 1;

                        while (true) {

                                if (width_tmp / 2 < REQUIRED_SIZE

                                                || height_tmp / 2 < REQUIRED_SIZE)

                                        break;

                                width_tmp /= 2;

                                height_tmp /= 2;

                                scale *= 2;

                        }

 

                        // decode with inSampleSize

                        BitmapFactory.Options o2 = new BitmapFactory.Options();

                        o2.inSampleSize = scale;

                        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);

                } catch (FileNotFoundException e) {

                }

                return null;

        }

 

        // Task for the queue

        private class PhotoToLoad {

                public String url;

                public ImageView imageView;

 

                public PhotoToLoad(String u, ImageView i) {

                        url = u;

                        imageView = i;

                }

        }

 

        class PhotosLoader implements Runnable {

                PhotoToLoad photoToLoad;

 

                PhotosLoader(PhotoToLoad photoToLoad) {

                        this.photoToLoad = photoToLoad;

                }

 

                @Override

                public void run() {

                        if (imageViewReused(photoToLoad))

                                return;

                        Bitmap bmp = getBitmap(photoToLoad.url);

                        memoryCache.put(photoToLoad.url, bmp);

                        if (imageViewReused(photoToLoad))

                                return;

                        BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);

                        // 更新的操作放在UI线程中

                        Activity a = (Activity) photoToLoad.imageView.getContext();

                        a.runOnUiThread(bd);

                }

        }

 

        

        boolean imageViewReused(PhotoToLoad photoToLoad) {

                String tag = imageViews.get(photoToLoad.imageView);

                if (tag == null || !tag.equals(photoToLoad.url))

                        return true;

                return false;

        }

 

        // 用于在UI线程中更新界面

        class BitmapDisplayer implements Runnable {

                Bitmap bitmap;

                PhotoToLoad photoToLoad;

 

                public BitmapDisplayer(Bitmap b, PhotoToLoad p) {

                        bitmap = b;

                        photoToLoad = p;

                }

 

                public void run() {

                        if (imageViewReused(photoToLoad))

                                return;

                        if (bitmap != null)

                                photoToLoad.imageView.setImageBitmap(bitmap);

        

                }

        }

 

        public void clearCache() {

                memoryCache.clear();

                fileCache.clear();

        }

 

        public static void CopyStream(InputStream is, OutputStream os) {

                final int buffer_size = 1024;

                try {

                        byte[] bytes = new byte[buffer_size];

                        for (;;) {

                                int count = is.read(bytes, 0, buffer_size);

                                if (count == -1)

                                        break;

                                os.write(bytes, 0, count);

                        }

                } catch (Exception ex) {

                        Log.e("", "CopyStream catch Exception...");

                }

        }

}

先从内存中加载,没有则开启线程从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

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

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82


public class MemoryCache {

 

        private static final String TAG = "MemoryCache";

        // 放入缓存时是个同步操作

        // LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU

        // 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率

        private Map cache = Collections

                        .synchronizedMap(new LinkedHashMap(10, 1.5f, true));

        // 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存

        private long size = 0;// current allocated size

        // 缓存只能占用的最大堆内存

        private long limit = 1000000;// max memory in bytes

 

        public MemoryCache() {

                // use 25% of available heap size

                setLimit(Runtime.getRuntime().maxMemory() / 10);

        }

 

        public void setLimit(long new_limit) {

                limit = new_limit;

                Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");

        }

 

        public Bitmap get(String id) {

                try {

                        if (!cache.containsKey(id))

                                return null;

                        return cache.get(id);

                } catch (NullPointerException ex) {

                        return null;

                }

        }

 

        public void put(String id, Bitmap bitmap) {

                try {

                        if (cache.containsKey(id))

                                size -= getSizeInBytes(cache.get(id));

                        cache.put(id, bitmap);

                        size += getSizeInBytes(bitmap);

                        checkSize();

                } catch (Throwable th) {

                        th.printStackTrace();

                }

        }

 

        

        private void checkSize() {

                Log.i(TAG, "cache size=" + size + " length=" + cache.size());

                if (size > limit) {

                        // 先遍历最近最少使用的元素

                        Iterator> iter = cache.entrySet().iterator();

                        while (iter.hasNext()) {

                                Entry entry = iter.next();

                                size -= getSizeInBytes(entry.getValue());

                                iter.remove();

                                if (size <= limit)

                                        break;

                        }

                        Log.i(TAG, "Clean cache. New size " + cache.size());

                }

        }

 

        public void clear() {

                cache.clear();

        }

 

        

        long getSizeInBytes(Bitmap bitmap) {

                if (bitmap == null)

                        return 0;

                return bitmap.getRowBytes() * bitmap.getHeight();

        }

}

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

时间: 2024-10-12 20:17:38

Listview异步加载图片之优化篇的相关文章

又优化了一下 Android ListView 异步加载图片

写这篇文章并不是教大家怎么样用listview异步加载图片,因为这样的文章在网上已经有很多了,比如这位仁兄写的就很好: http://www.iteye.com/topic/685986 我也是因为看了这篇文章而受到了启发. 先说说这篇文章的优点把,开启线程异步加载图片,然后刷新UI显示图片,而且通过弱引用缓存网络加载的图片,节省了再次连接网络的开销. 这样做无疑是非常可取的方法,但是加载图片时仍然会感觉到轻微的卡屏现象,特别是listview里的item在进行快速滑动的时候. 我找了一下原因,

Android之ListView异步加载图片且仅显示可见子项中的图片

折腾了好多天,遇到 N 多让人崩溃无语的问题,不过今天终于有些收获了,这是实验的第一版,有些混乱,下一步进行改造细分,先把代码记录在这儿吧. 网上查了很多资料,发现都千篇一律,抄来抄去,很多细节和完整实例都没看到,只有自己一点点研究了,总体感觉 android 下面要显示个图片真不容易啊. 项目主要实现的功能: 异步加载图片图片内存缓存.异步磁盘文件缓存解决使用 viewHolder 后出现的图片错位问题优化列表滚动性能,仅显示可见子项中的图片无需固定图片显示高度,对高度进行缓存使列表滚动时不会

Android ListView异步加载图片乱序问题,原因分析及解决方案

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/45586553 在Android所有系统自带的控件当中,ListView这个控件算是用法比较复杂的了,关键是用法复杂也就算了,它还经常会出现一些稀奇古怪的问题,让人非常头疼.比如说在ListView中加载图片,如果是同步加载图片倒还好,但是一旦使用异步加载图片那么问题就来了,这个问题我相信很多Android开发者都曾经遇到过,就是异步加载图片会出现错位乱序的情况.遇到这个问题时,不

Android ListView异步加载图片错位、重复、闪烁分析以及解决方案

我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位.重复.闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化. 比如ListView上有100个Item,一屏只显示10个Item,我们知道getView()中convertView是用来复用View对象的,因为一个Item的对应一个View对象,而ImageView控件就是View对象通过findViewById()获得的,而我们在复用View对象时,同时这个Ima

Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案

Android中ListView异步加载图片错位.重复.闪烁问题分析及解决方案 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位.重复.闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化. 比如ListView上有100个Item,一屏只显示10个Item,我们知道getView()中convertView是用来复用View对象的,因为一个Item的对应一个View对象,而ImageView控件就是View对象通

android开发干货:实现listview异步加载图片

针对listview异步加载图片这个问题,麦子学院android开发老师讲了一种非常实用的方法,麦子学院android开发老师说凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.net.

android listview 异步加载图片并防止错位

网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertView 但没有异步操作也不会有问题. 我简单分析一下: 当重用 convertView 时,最初一屏显示 7 条记录, getView 被调用 7 次,创建了 7 个 convertView. 当 Item1 划出屏幕, Item8 进入屏幕时,这时没有为 Item8 创建新的 view 实例, Ite

android listview 异步加载图片并防止错位+双缓存

网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertView 但没有异步操作也不会有问题. 我简单分析一下: 当重用 convertView 时,最初一屏显示 7 条记录, getView 被调用 7 次,创建了 7 个 convertView. 当 Item1 划出屏幕, Item8 进入屏幕时,这时没有为 Item8 创建新的 view 实例, Ite

android listview异步加载图片错位,重复,闪烁分析以及解决方案

我们在使用listview异步加载图片 的时候,在快速滑动或者网络不好的情况下,会出现图片错位,重复,闪烁等问题,其实这些问题总结起来就是一个问题, 比如listview上有100个item,一屏只显示10个item,我们知道getView()中converView是用来复用view对象的,因为一个item的view对象,而imageview控件就是view通过findViewById()获得的,而我们在复用view对象时,也就是说这个imageview也被复用了,比如第11个item的view