前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ImageLoader

ImageLoader

作者头像
提莫队长
发布2019-02-21 11:15:35
4310
发布2019-02-21 11:15:35
举报
文章被收录于专栏:刘晓杰刘晓杰

这次做一个图片加载器,里面涉及到线程池,bitmap的高效加载,LruCache,DiskLruCache。接下来我先介绍这四个知识点

一.线程池

优点: (1)重用线程池中的线程,避免因为线程的创建和销毁带来性能上的开销 (2)有效控制线程池的最大并发数,避免大量线程之间因互相抢占系统资源而阻塞 (3)对线程进行简单管理,并提供定时执行和指定间隔循环执行等功能

1.ThreadPoolExecutor介绍 是线程池的真正实现,构造方法

代码语言:javascript
复制
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory)

corePoolSize:核心线程数 maximumPoolSize:最大线程数。超过将阻塞 keepAliveTime:非核心线程超时时长,超过将会被回收 unit:指定keepAliveTime的时间单位。常用TimeUnit.SECONDS(秒),TimeUnit.MINUTES(分钟)等 workQueue:存储线程的队列。通过ThreadPoolExecutor.execute方法提交的Runnable对象会存储在这个线程中 threadFactory:是一个接口,提供创建新线程的功能。只有一个方法:public Thread newThread(Runnable r)

2.ThreadPoolExecutor典型配置

代码语言:javascript
复制
    /**
     * 线程池,用来管理线程
     */
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        // AtomicInteger,一个提供原子操作的Integer的类。
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
        }
    };

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAX_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final long KEEP_ALIVE = 10L;
    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
            new LinkedBlockingDeque<Runnable>(), sThreadFactory);

二.bitmap高效加载

通过BitmapFactory.Options来加载所需尺寸的图片,主要是用到了inSampleSize参数,即采样率 流程如下:

代码语言:javascript
复制
    public Bitmap decodeSampleBitmapFromResource(Resources resources,
            int resId, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        // 获取options
        BitmapFactory.decodeResource(resources, resId, options);
        // 结合目标view所需大小计算采样率
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(resources, resId, options);
    }
    /**
     * 指定输出图片的缩放比例
     * 
     * @param options
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        // 获得原始图片的宽高
        int imageHeight = options.outHeight;
        int imageWidth = options.outWidth;
        int inSimpleSize = 1;
        if (imageHeight > reqHeight || imageWidth > reqWidth) {
            int halfHeight = imageHeight / 2;
            int halfWidth = imageWidth / 2;
            while ((halfHeight / inSimpleSize) >= reqHeight
                    && (halfWidth / inSimpleSize) >= reqWidth) {
                inSimpleSize *= 2;
                Log.e("SimpleSize", inSimpleSize + "");
            }
        }
        Log.e("inSimpleSize", inSimpleSize + "");
        return inSimpleSize;
    }

三.LruCache(内存缓存)

Lru就是Least Recently Used近期最少使用算法。核心思想:当缓存满时,优先淘汰近期最少使用的缓存对象 先看源码:(推荐使用supprt-v4包中的LruCache,地址在E:\adt\sdk\sources\android-19\android\support\v4\util)

代码语言:javascript
复制
public class LruCache<K, V> {
    //map用来存储外界的缓存对象
    private final LinkedHashMap<K, V> map;

    /**
     * @param maxSize for caches that do not override {@link #sizeOf}, this is
     *     the maximum number of entries in the cache. For all other caches,
     *     this is the maximum sum of the sizes of the entries in this cache.
     */
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

    //获取一个缓存对象
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */

        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

    //添加一个缓存对象
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize);
        return previous;
    }

    /**
     * Remove the eldest entries until the total of remaining entries is at or
     * below the requested size.
     *
     * @param maxSize the maximum size of the cache before returning. May be -1
     *            to evict even 0-sized elements.
     */
    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize || map.isEmpty()) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

    //移除一个缓存对象,并且减少该对象对应的size
    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, null);
        }

        return previous;
    }

    //生成一个null对象
    protected V create(K key) {
        return null;
    }

    //返回一个缓存对象的副本
    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }
}

研究完了源码,使用起来就方便了

代码语言:javascript
复制
初始化:
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount() / 1024;
            }
        };
获取缓存:mMemoryCache.get(key)
添加缓存:mMemoryCache.put(key, bitmap)

四.DiskLruCache(磁盘缓存)

书上说地址在:https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java 但是下载下来好像要改好多东西,所以我就在Universal-ImageLoader里面找了相同的文件

1.DiskLruCache的创建

代码语言:javascript
复制
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

directory:存储路径 appVersion:通常为1 valueCount:单个节点对应数据个数,通常为1 maxSize:缓存总大小,比如50MB

代码语言:javascript
复制
        // 初始化DiskLruCache
        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }

        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
                        DISK_CACHE_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

getDiskCacheDir:获取磁盘缓存目录 getUsableSpace:获取sd卡的大小和剩余空间 这两个函数的实现方法在代码包里面有,就不细说

2.DiskLruCache缓存的添加 缓存的添加是通过Editor来完成的。Editor表示缓存对象的编辑对象

代码语言:javascript
复制
        //将uri转化为key
        String key = hashKeyForURI(uri);
        try {
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);
            if (editor != null) {
                OutputStream outputStream = editor
                        .newOutputStream(DISK_CACHE_INDEX);
                if (downloadURIToStream(uri, outputStream)) {
                    editor.commit();
                } else {
                    editor.abort();
                }
                mDiskLruCache.flush();
            }
        } catch (IOException e) {
            return null;
        }

上面的代码就是将图片写入文件系统,接下来就可以从文件系统中获取图片 解释几点

代码语言:javascript
复制
1.为什么要将uri转化为hashKey?如果uri中含有特殊字符会影响uri的使用
2.downloadURIToStream实现了“把图片写入到文件系统”的功能,确切的来说,还要配合editor.commit

3.DiskLruCache缓存的查找

代码语言:javascript
复制
        String key = hashKeyForURI(uri);
        try {
            DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
            if (snapshot != null) {
                FileInputStream fileInputStream = (FileInputStream) snapshot
                        .getInputStream(DISK_CACHE_INDEX);
                FileDescriptor fileDescriptor = fileInputStream.getFD();
                 bitmap = mImageResizer.decodeSampleBitmapFromFileDescriptor(
                 fileDescriptor, width, height);
                if (bitmap != null) {
                    addBitmapToMemoryCache(key, bitmap);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

注意,这里的Snapshot和LruCache的Snapshot不一样。LruCache的Snapshot表示内存缓存的副本,这里的Snapshot仅仅指保存了三个参数的一个对象

至此,ImageLoader已经大体实现。 代码包里面SquareImageView.java是为了得到一个宽高相同的ImageView。 同时,为了优化列表的卡顿现象,我们采用了“仅当列表静止时才加载图片”的策略

代码语言:javascript
复制
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
                    mIsGridViewIdle = true;
                    adapter.notifyDataSetChanged();
                } else {
                    mIsGridViewIdle = false;
                }
            }

            在getView里面添加如下代码
            if (mIsGridViewIdle) {
                imageLoader.bindBitmap(uri, imageView);
            }

运行截图 loadBitmapFromHttp和downloadBitmapFromURI都可以实现网络加载。前者是先放到disk中,然后获取,后者是先获取,然后放到memorycache中 我先把downloadBitmapFromURI注释掉

这里写图片描述
这里写图片描述

然后把loadBitmapFromHttp注释掉

这里写图片描述
这里写图片描述

代码地址:http://download.csdn.net/detail/lxj1137800599/9628778

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年09月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.线程池
  • 二.bitmap高效加载
  • 三.LruCache(内存缓存)
  • 四.DiskLruCache(磁盘缓存)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档