转自:http://www.oschina.net/code/snippet_219356_18887?p=3#comments

ImageManager2这个类具有异步从网络下载图片,从sd读取本地图片,内存缓存,硬盘缓存,图片使用动画渐现等功能,已经将其应用在包含大量图片的应用中一年多,没有出现oom

Android程序常常会内存溢出,网上也有很多解决方案,如软引用,手动调用recycle等等。但经过我们实践发现这些方案,都没能起到很好的效果,我们的应用依然会出现很多oom,尤其我们的应用包含大量的图片。android3.0之后软引用基本已经失效,因为虚拟机只要碰到软引用就回收,所以带不来任何性能的提升。

我这里的解决方案是HandlerThread(异步加载)+LruCache(内存缓存)+DiskLruCache(硬盘缓存)。

作为程序员,我也不多说,直接和大家共享我的代码,用代码交流更方便些。

源码demo地址:https://github.com/yueyueniao2012/multiimagechooser

标签: <无>

代码片段(1) [全屏查看所有代码]

1. [文件] ImageManager2.java ~ 13KB     下载(468)

?
1
2
3
4
5
6
7
8
9
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
package com.example.util;
import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.media.ThumbnailUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.widget.ImageView;
import com.example.MyApplication;
/**
 * 图片加载类
 *
 * @author 月月鸟
 */
public class ImageManager2 {
    private static ImageManager2 imageManager;
    public LruCache<String, Bitmap> mMemoryCache;
    private static final int DISK_CACHE_SIZE = 1024 * 1024 * 20; // 10MB
    private static final String DISK_CACHE_SUBDIR = "thumbnails";
    public DiskLruCache mDiskCache;
    private static MyApplication myapp;
    /** 图片加载队列,后进先出 */
    private Stack<ImageRef> mImageQueue = new Stack<ImageRef>();
    /** 图片请求队列,先进先出,用于存放已发送的请求。 */
    private Queue<ImageRef> mRequestQueue = new LinkedList<ImageRef>();
    /** 图片加载线程消息处理器 */
    private Handler mImageLoaderHandler;
    /** 图片加载线程是否就绪 */
    private boolean mImageLoaderIdle = true;
    /** 请求图片 */
    private static final int MSG_REQUEST = 1;
    /** 图片加载完成 */
    private static final int MSG_REPLY = 2;
    /** 中止图片加载线程 */
    private static final int MSG_STOP = 3;
    /** 如果图片是从网络加载,则应用渐显动画,如果从缓存读出则不应用动画 */
    private boolean isFromNet = true;
    /**
     * 获取单例,只能在UI线程中使用。
     *
     * @param context
     * @return
     */
    public static ImageManager2 from(Context context) {
        // 如果不在ui线程中,则抛出异常
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new RuntimeException("Cannot instantiate outside UI thread.");
        }
        if (myapp == null) {
            myapp = (MyApplication) context.getApplicationContext();
        }
        if (imageManager == null) {
            imageManager = new ImageManager2(myapp);
        }
        return imageManager;
    }
    /**
     * 私有构造函数,保证单例模式
     *
     * @param context
     */
    private ImageManager2(Context context) {
        int memClass = ((ActivityManager) context
                .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        memClass = memClass > 32 ? 32 : memClass;
        // 使用可用内存的1/8作为图片缓存
        final int cacheSize = 1024 * 1024 * memClass / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight();
            }
        };
        File cacheDir = DiskLruCache
                .getDiskCacheDir(context, DISK_CACHE_SUBDIR);
        mDiskCache = DiskLruCache.openCache(context, cacheDir, DISK_CACHE_SIZE);
    }
    /**
     * 存放图片信息
     */
    class ImageRef {
        /** 图片对应ImageView控件 */
        ImageView imageView;
        /** 图片URL地址 */
        String url;
        /** 图片缓存路径 */
        String filePath;
        /** 默认图资源ID */
        int resId;
        int width = 0;
        int height = 0;
        /**
         * 构造函数
         *
         * @param imageView
         * @param url
         * @param resId
         * @param filePath
         */
        ImageRef(ImageView imageView, String url, String filePath, int resId) {
            this.imageView = imageView;
            this.url = url;
            this.filePath = filePath;
            this.resId = resId;
        }
        ImageRef(ImageView imageView, String url, String filePath, int resId,
                int width, int height) {
            this.imageView = imageView;
            this.url = url;
            this.filePath = filePath;
            this.resId = resId;
            this.width = width;
            this.height = height;
        }
    }
    /**
     * 显示图片
     *
     * @param imageView
     * @param url
     * @param resId
     */
    public void displayImage(ImageView imageView, String url, int resId) {
        if (imageView == null) {
            return;
        }
        if (imageView.getTag() != null
                && imageView.getTag().toString().equals(url)) {
            return;
        }
        if (resId >= 0) {
            if (imageView.getBackground() == null) {
                imageView.setBackgroundResource(resId);
            }
            imageView.setImageDrawable(null);
        }
        if (url == null || url.equals("")) {
            return;
        }
        // 添加url tag
        imageView.setTag(url);
        // 读取map缓存
        Bitmap bitmap = mMemoryCache.get(url);
        if (bitmap != null) {
            setImageBitmap(imageView, bitmap, false);
            return;
        }
        // 生成文件名
        String filePath = urlToFilePath(url);
        if (filePath == null) {
            return;
        }
        queueImage(new ImageRef(imageView, url, filePath, resId));
    }
    /**
     * 显示图片固定大小图片的缩略图,一般用于显示列表的图片,可以大大减小内存使用
     *
     * @param imageView 加载图片的控件
     * @param url 加载地址
     * @param resId 默认图片
     * @param width 指定宽度
     * @param height 指定高度
     */
    public void displayImage(ImageView imageView, String url, int resId,
            int width, int height) {
        if (imageView == null) {
            return;
        }
        if (resId >= 0) {
            if (imageView.getBackground() == null) {
                imageView.setBackgroundResource(resId);
            }
            imageView.setImageDrawable(null);
        }
        if (url == null || url.equals("")) {
            return;
        }
        // 添加url tag
        imageView.setTag(url);
        // 读取map缓存
        Bitmap bitmap = mMemoryCache.get(url + width + height);
        if (bitmap != null) {
            setImageBitmap(imageView, bitmap, false);
            return;
        }
        // 生成文件名
        String filePath = urlToFilePath(url);
        if (filePath == null) {
            return;
        }
        queueImage(new ImageRef(imageView, url, filePath, resId, width, height));
    }
    /**
     * 入队,后进先出
     *
     * @param imageRef
     */
    public void queueImage(ImageRef imageRef) {
        // 删除已有ImageView
        Iterator<ImageRef> iterator = mImageQueue.iterator();
        while (iterator.hasNext()) {
            if (iterator.next().imageView == imageRef.imageView) {
                iterator.remove();
            }
        }
        // 添加请求
        mImageQueue.push(imageRef);
        sendRequest();
    }
    /**
     * 发送请求
     */
    private void sendRequest() {
        // 开启图片加载线程
        if (mImageLoaderHandler == null) {
            HandlerThread imageLoader = new HandlerThread("image_loader");
            imageLoader.start();
            mImageLoaderHandler = new ImageLoaderHandler(
                    imageLoader.getLooper());
        }
        // 发送请求
        if (mImageLoaderIdle && mImageQueue.size() > 0) {
            ImageRef imageRef = mImageQueue.pop();
            Message message = mImageLoaderHandler.obtainMessage(MSG_REQUEST,
                    imageRef);
            mImageLoaderHandler.sendMessage(message);
            mImageLoaderIdle = false;
            mRequestQueue.add(imageRef);
        }
    }
    /**
     * 图片加载线程
     */
    class ImageLoaderHandler extends Handler {
        public ImageLoaderHandler(Looper looper) {
            super(looper);
        }
        public void handleMessage(Message msg) {
            if (msg == null)
                return;
            switch (msg.what) {
            case MSG_REQUEST: // 收到请求
                Bitmap bitmap = null;
                Bitmap tBitmap = null;
                if (msg.obj != null && msg.obj instanceof ImageRef) {
                    ImageRef imageRef = (ImageRef) msg.obj;
                    String url = imageRef.url;
                    if (url == null)
                        return;
                    // 如果本地url即读取sd相册图片,则直接读取,不用经过DiskCache
                    if (url.toLowerCase().contains("dcim")) {
                        tBitmap = null;
                        BitmapFactory.Options opt = new BitmapFactory.Options();
                        opt.inSampleSize = 1;
                        opt.inJustDecodeBounds = true;
                        BitmapFactory.decodeFile(url, opt);
                        int bitmapSize = opt.outHeight * opt.outWidth * 4;
                        opt.inSampleSize = bitmapSize / (1000 * 2000);
                        opt.inJustDecodeBounds = false;
                        tBitmap = BitmapFactory.decodeFile(url, opt);
                        if (imageRef.width != 0 && imageRef.height != 0) {
                            bitmap = ThumbnailUtils.extractThumbnail(tBitmap,
                                    imageRef.width, imageRef.height,
                                    ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
                            isFromNet = true;
                        } else {
                            bitmap = tBitmap;
                            tBitmap = null;
                        }
                    } else
                        bitmap = mDiskCache.get(url);
                    if (bitmap != null) {
                        // ToolUtil.log("从disk缓存读取");
                        // 写入map缓存
                        if (imageRef.width != 0 && imageRef.height != 0) {
                            if (mMemoryCache.get(url + imageRef.width
                                    + imageRef.height) == null)
                                mMemoryCache.put(url + imageRef.width
                                        + imageRef.height, bitmap);
                        } else {
                            if (mMemoryCache.get(url) == null)
                                mMemoryCache.put(url, bitmap);
                        }
                    } else {
                        try {
                            byte[] data = loadByteArrayFromNetwork(url);
                            if (data != null) {
                                BitmapFactory.Options opt = new BitmapFactory.Options();
                                opt.inSampleSize = 1;
                                opt.inJustDecodeBounds = true;
                                BitmapFactory.decodeByteArray(data, 0,
                                        data.length, opt);
                                int bitmapSize = opt.outHeight * opt.outWidth
                                        * 4;// pixels*3 if it's RGB and pixels*4
                                            // if it's ARGB
                                if (bitmapSize > 1000 * 1200)
                                    opt.inSampleSize = 2;
                                opt.inJustDecodeBounds = false;
                                tBitmap = BitmapFactory.decodeByteArray(data,
                                        0, data.length, opt);
                                if (imageRef.width != 0 && imageRef.height != 0) {
                                    bitmap = ThumbnailUtils
                                            .extractThumbnail(
                                                    tBitmap,
                                                    imageRef.width,
                                                    imageRef.height,
                                                    ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
                                } else {
                                    bitmap = tBitmap;
                                    tBitmap = null;
                                }
                                if (bitmap != null && url != null) {
                                    // 写入SD卡
                                    if (imageRef.width != 0
                                            && imageRef.height != 0) {
                                        mDiskCache.put(url + imageRef.width
                                                + imageRef.height, bitmap);
                                        mMemoryCache.put(url + imageRef.width
                                                + imageRef.height, bitmap);
                                    } else {
                                        mDiskCache.put(url, bitmap);
                                        mMemoryCache.put(url, bitmap);
                                    }
                                    isFromNet = true;
                                }
                            }
                        } catch (OutOfMemoryError e) {
                        }
                    }
                }
                if (mImageManagerHandler != null) {
                    Message message = mImageManagerHandler.obtainMessage(
                            MSG_REPLY, bitmap);
                    mImageManagerHandler.sendMessage(message);
                }
                break;
            case MSG_STOP: // 收到终止指令
                Looper.myLooper().quit();
                break;
            }
        }
    }
    /** UI线程消息处理器 */
    private Handler mImageManagerHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg != null) {
                switch (msg.what) {
                case MSG_REPLY: // 收到应答
                    do {
                        ImageRef imageRef = mRequestQueue.remove();
                        if (imageRef == null)
                            break;
                        if (imageRef.imageView == null
                                || imageRef.imageView.getTag() == null
                                || imageRef.url == null)
                            break;
                        if (!(msg.obj instanceof Bitmap) || msg.obj == null) {
                            break;
                        }
                        Bitmap bitmap = (Bitmap) msg.obj;
                        // 非同一ImageView
                        if (!(imageRef.url).equals((String) imageRef.imageView
                                .getTag())) {
                            break;
                        }
                        setImageBitmap(imageRef.imageView, bitmap, isFromNet);
                        isFromNet = false;
                    } while (false);
                    break;
                }
            }
            // 设置闲置标志
            mImageLoaderIdle = true;
            // 若服务未关闭,则发送下一个请求。
            if (mImageLoaderHandler != null) {
                sendRequest();
            }
        }
    };
    /**
     * 添加图片显示渐现动画
     *
     */
    private void setImageBitmap(ImageView imageView, Bitmap bitmap,
            boolean isTran) {
        if (isTran) {
            final TransitionDrawable td = new TransitionDrawable(
                    new Drawable[] {
                            new ColorDrawable(android.R.color.transparent),
                            new BitmapDrawable(bitmap) });
            td.setCrossFadeEnabled(true);
            imageView.setImageDrawable(td);
            td.startTransition(300);
        } else {
            imageView.setImageBitmap(bitmap);
        }
    }
    /**
     * 从网络获取图片字节数组
     *
     * @param url
     * @return
     */
    private byte[] loadByteArrayFromNetwork(String url) {
        try {
            HttpGet method = new HttpGet(url);
            HttpResponse response = myapp.getHttpClient().execute(method);
            HttpEntity entity = response.getEntity();
            return EntityUtils.toByteArray(entity);
        } catch (Exception e) {
            return null;
        }
    }
    /**
     * 根据url生成缓存文件完整路径名
     *
     * @param url
     * @return
     */
    public String urlToFilePath(String url) {
        // 扩展名位置
        int index = url.lastIndexOf('.');
        if (index == -1) {
            return null;
        }
        StringBuilder filePath = new StringBuilder();
        // 图片存取路径
        filePath.append(myapp.getCacheDir().toString()).append('/');
        // 图片文件名
        filePath.append(MD5.Md5(url)).append(url.substring(index));
        return filePath.toString();
    }
    /**
     * Activity#onStop后,ListView不会有残余请求。
     */
    public void stop() {
        // 清空请求队列
        mImageQueue.clear();
    }
}

举报

开源中国-程序员在线工具:Git代码托管 API文档大全(120+) JS在线编辑演示 二维码 更多»

发表评论 回到顶部 网友评论(44)

  • 41楼:木法沙王 发表于 2014-04-10 17:26 回复此评论
    //如果本地url即读取sd相册图片,则直接读取,不用经过DiskCache
    if (url.toLowerCase().contains("dcim")

    建议把dcim改为mnt, 这样别的路经下图片也能加载了

  • 42楼:迷途的小羔羊 发表于 2014-05-02 16:14 回复此评论
    我他妈的非常感谢你,真心的
  • 43楼:wgggfiy 发表于 2014-06-05 10:56 回复此评论
    请问这个缓存何时可以清空呢??我担心内存和磁盘会慢慢越积越大
  • 44楼:巴顿将军 发表于 2014-07-19 18:16 回复此评论
    什么东西呀,我跑不起来,看不到什么效果呢
  • <
  • 1
  • 2
  • 3

 
 
 

回到顶部   回到评论列表

开源从代码分享开始 分享代码
roc2013的其他代码

  • SlidingDrawer源码(0评/938阅,1年前)
  • 自定义Android滑动式菜单SlidingMenu(不依赖任何第三方库)(35评/18351阅,1年前)
  • Android自定义控件:DragSortListview(11评/4356阅,1年前)
  • android自定相册(7评/1391阅,1年前)
  • 仿大众点评下拉菜单实现(3评/2888阅,1年前)

全部(6)...

Android图片管理组件(双缓存+异步加载)相关推荐

  1. Android进阶:ListView性能优化异步加载图片 使滑动效果流畅

    ListView 是一种可以显示一系列项目并能进行滚动显示的 View,每一行的Item可能包含复杂的结构,可能会从网络上获取icon等的一些图标信息,就现在的网络速度要想保持ListView运行的很 ...

  2. Android 使用AsyncTask 后监听异步加载完毕的动作

    AsyncTask 的使用方法网上有很多例子,使用起来也非常的方便.这里就不详细说具体的使用方法了,同学可以Google 一下,很多. 场景模拟 当我们在加载一个列表的时候,比如GridView ,这 ...

  3. Android 使用AsyncTask 后监听异步加载完毕的动作-(by terry-龙)

    AsyncTask 的使用方法网上有很多例子,使用起来也非常的方便.这里就不详细说具体的使用方法了,同学可以Google 一下,很多. 场景模拟 当我们在加载一个列表的时候,比如GridView ,这 ...

  4. wemall app商城源码中基于JAVA的Android异步加载图片管理器代码

    wemall doraemon是Android客户端程序,服务端采用wemall微信商城,不对原商城做任何修改,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可随意定制修改.本文分享其中 ...

  5. FLutter入门:异步加载组件FutureBuilder

    FutureBuilder 在实际开发中,进入一个页面后执行网络请求加载数据并显示是非常普遍的,这时候我们一般会显示loading直到加载完成显示正常页面.在flutter中我们可以在initStat ...

  6. bootstrap 树形表格渲染慢_layUI之树状表格异步加载组件treetableAsync.js(基于treetable.js)...

    概述 后台框架中使用树状表格是非常常用的操作,layUI本身并没有这种组件. 第三方的treetable.js做到了完美的实现,但是不能实现在双击时异步加载数据,本文就是站在了巨人的肩膀上实现的异步加 ...

  7. element ui table组件 异步加载数据盒子位移

    使用element ui 中的 table组件时 ,异步加载数据,然后渲染上去时会出现td与th对不齐,但是重新重新进一次就有又好了,但是对于用户体验肯定是不行的. 这个时候只需要使用element ...

  8. android异步加载图片并缓存到内存和sd卡上,Android批量图片加载经典系列——采用二级缓存、异步加载网络图片...

    http://www.cnblogs.com/jerehedu/p/4560119.html 2015-06-08 09:20 by 杰瑞教育, 232 阅读, 1 评论, 收藏, 编辑一.问题描述 ...

  9. Android批量图片加载经典系列——使用LruCache、AsyncTask缓存并异步加载图片

    一.问题描述 使用LruCache.AsyncTask实现批量图片的加载并达到下列技术要求 1.从缓存中读取图片,若不在缓存中,则开启异步线程(AsyncTask)加载图片,并放入缓存中 2.及时移除 ...

最新文章

  1. hashmap value可以为空吗_美团面试题:Hashmap结构,1.7和1.8有哪些区别(最详细解析)...
  2. 从深度图到点云的构建方式
  3. 采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。
  4. flask html css文件更改后(谷歌)浏览器不及时更新样式文件怎么办?(ctrl+shift+delete清除缓存的图片和文件)
  5. 【uni-app】动态计算图片高度且保持宽高比
  6. linux查找特定类型的文件中是否包含特定字段
  7. 在 xml 视图里指定 SAP UI5 VizType 实例
  8. python运行出现SyntaxError: 'return' outside function的原因和解决办法
  9. 远程无法连接数据库的问题
  10. thinkphp v5.0.11漏洞_ThinkPHP 5.0.x-5.0.23、5.1.x、5.2.x 全版本远程代码执行漏洞分析
  11. C#4.0新特性:可选参数,命名参数,Dynamic
  12. InDesign教程,如何对齐和调整对象位置?
  13. 手机APP神器大全,这些被堪称神器的APP你用过哪些!
  14. 《Java修炼指南:高频源码解析》阅读笔记一Java数据结构的实现集合类
  15. python实例方法不可以用类调用_python中可以直接用类调用方法吗
  16. 【戴嘉乐】IPFS伴侣:一个对IPFS资源管理更加便捷的浏览器插件
  17. 微信小程序使用icon图标
  18. Proteus仿真51开发板
  19. 现有MyCAT上新增一个库及MyCAT报错1184问题解决
  20. SRT编码器传输获取公网IP地址3种解决方案

热门文章

  1. 视频教程-FFmpeg打造Android万能音频播放器-Android
  2. Solidworks插件
  3. php本地数据查询,PHP中如何实现首字母数据查询
  4. PhpSpreadsheet 导出Excel图表 生成多列柱状图
  5. 再见,区块链 Rio会议和以太坊Sao Paolo活动。 你好,闪光时刻
  6. 2023年转行做网络安全工程师还来得及吗? 内附详细解答
  7. django authentication_classes自定义权限验证类 authenticate
  8. 中国 GitHub 霸榜乱象! 别再给国内程序员丢脸了
  9. 夜光:计算机网络笔记(十二)
  10. Java面试技巧——每天一个Tip