ImageLoader

来源:互联网 发布:软件测试管理体系 编辑:程序博客网 时间:2024/05/16 14:26

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

一.线程池

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

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

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典型配置

    /**     * 线程池,用来管理线程     */    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参数,即采样率
流程如下:

    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)

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);    }}

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

初始化:        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的创建

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

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

        // 初始化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表示缓存对象的编辑对象

        //将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;        }

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

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

3.DiskLruCache缓存的查找

        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。
同时,为了优化列表的卡顿现象,我们采用了“仅当列表静止时才加载图片”的策略

            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

0 0
原创粉丝点击