Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> ImageLoader

ImageLoader

編輯:關於Android編程

這次做一個圖片加載器,裡面涉及到線程池,bitmap的高效加載,LruCache,DiskLruCache。接下來我先介紹這四個知識點

一.線程池

優點:
(1)重用線程池中的線程,避免因為線程的創建和銷毀帶來性能上的開銷
(2)有效控制線程池的最大並發數,避免大量線程之間因互相搶占系統資源而阻塞
(3)對線程進行簡單管理,並提供定時執行和指定間隔循環執行等功能

1.ThreadPoolExecutor介紹
是線程池的真正實現,構造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue 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(), 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 {
    //map用來存儲外界的緩存對象
    private final LinkedHashMap 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(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 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 snapshot() {
        return new LinkedHashMap(map);
    }
}

研究完了源碼,使用起來就方便了

初始化:
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache(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注釋掉
這裡寫圖片描述

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved