【安卓中的緩存策略系列】安卓緩存策略之綜合應用ImageLoader實現照片牆的效果

在前面的【安卓緩存策略系列】安卓緩存之內存緩存LruCache【安卓緩存策略系列】安卓緩存策略之磁盤緩存DiskLruCache這兩篇博客中已經將安卓中的緩存策略的理論知識進行過詳細講解,還沒看過這兩篇博客的看官建議先去看一下,本博客將依據這些理論知識打造一個ImageLoader,實現照片牆的效果,關於照片牆的知識網上相關博客也很多,首先解釋一下照片牆的概念:用一個GridView控件當作“牆”,然後隨着GridView的滾動將一張張照片貼在“牆”上,很顯然因爲圖片一般比較佔內存,所以得考慮使用壓縮與緩存策略來防止程序OOM,而完成這些核心功能的自然就是我們的ImageLoader類,當然也可以使用類似於ImageLoader的開源框架來完成這個功能,如Universal_Image_Loader。不過本博客將根據前面講解的理論知識自己來實現一個ImageLoader類。


首先總的來說一個合格的ImageLoader應該包含如下功能:

1..圖片的同步加載

2.圖片的異步加載

3.圖片的壓縮處理

4.圖片的內存緩存處理

5.圖片的磁盤緩存處理

6.圖片的網絡拉取處理

首先圖片的異步加載肯定是必須具備的,因爲爲了在GridView上能流暢的顯示圖片肯定不能用同步加載,然後爲了防止程序OOM圖片的壓縮處理也是必須的,而網絡拉取,內存緩存,磁盤緩存這時緩存策略常用的三級緩存策略,即當要使用某一張圖片時,首先嚐試從內存中獲取,如果內存中不存在則從磁盤中緩存,如果磁盤中也不存在,則從網絡上獲取,當從網絡上獲取到該圖片後先將其添加到內存緩存中,然後添加到磁盤緩存中,供下次訪問時直接使用。


當然除了上述ImageLoader本身的硬性功能需求外,我們還需要考慮它與GridView交互過程中可能出現的問題,如我們在複用View時,假設某個Item X正在從網絡上獲取,它對應的ImageView爲X,這個時候如果用戶快速的向下活動GridView,則很肯能Item Y複用了ImageView X,然後等之前的圖片下載完畢後,如果直接給ImageView X設置圖片,則因爲這個時候ImageView X被item Y複用,但是很顯然item Y要顯示的圖片顯然不是item X剛剛下載完的圖片,這個時候就會出現item Y顯示了item X的圖片,即出現列表錯位的現象,ImageLoader應該能夠正確的處理這些問題。


上述的功能大綱就是我們的整個ImageLoader的功能框架,接下來我們就按照上述介紹的功能及前面學習的理論知識親手打造一個ImageLoader,最後還會對針對GridView中列表的卡頓現象進行一些優化處理。

一圖片的同步與異步加載:

所謂圖片的同步加載即在該方法中不單獨開啓一個線程去加載圖片,而是在調用該同步方法時將該同步方法放到一個子線程中去執行,而異步加載是在該方法中開啓一個子線程去加載圖片,這樣調用該方法時不需將其放到子線程中去執行,即該方法可以直接運行在主線程中,顯然從網絡上加載圖片可能比較耗時,所以一般我們使用異步加載的方式。首先我們來看一下同步加載,代碼如下:

 /**
     * load bitmap from memory cache or disk cache or network.
     * @param uri http url
     * @param reqWidth the width ImageView desired
     * @param reqHeight the height ImageView desired
     * @return bitmap, maybe null.
     */
    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
            return bitmap;
        }

        try {
            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
            if (bitmap != null) {
                Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
                return bitmap;
            }
            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
            Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (bitmap == null && !mIsDiskLruCacheCreated) {
            Log.w(TAG, "encounter error, DiskLruCache is not created.");
            bitmap = downloadBitmapFromUrl(uri);
        }

        return bitmap;
    }
可以看到,在該loadBitmap()的中首先嚐試通過loadBitmapFromMemCache(uri)方法從內存緩存中獲取圖片,如果內存緩存中存在該圖片對象直接返回,如果不存在,則嘗試通過loadBitmapFromDiskCache(uri, reqWidth, reqHeight)從磁盤緩存中獲取,如果不存在則通過loadBitmapFromHttp(uri, reqWidth, reqHeight)來從網絡上獲取圖片,這就是安卓緩存策略中的三步緩存策略,即內存-磁盤-網絡。注意該方法不能在主線程中調用,因爲圖片的加載屬於耗時操作,可能會導致ANR異常。


接下來看一下異步加載的代碼:

  public void bindBitmap(final String uri, final ImageView imageView,
            final int reqWidth, final int reqHeight) {
        imageView.setTag(TAG_KEY_URI, uri);
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        Runnable loadBitmapTask = new Runnable() {

            @Override
            public void run() {
                Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
                if (bitmap != null) {
                    LoaderResult result = new LoaderResult(imageView, uri, bitmap);
                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
                }
            }
        };
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }
可以看到在異步加載中首先嚐試從內存緩存中獲取,如果獲取到相應的圖片就直接返回結果,否則會創建一個Runnable對象,然後調用線程池的execute()方法去執行該runnable對象,在Runnable的run方法中調用了同步加載圖片的loadBitmap(uri, reqWidth, reqHeight)方法,在圖片加載成功後將圖片及圖片地址Uri和需要綁定的ImageView控件包裝成一個LoaderResult對象,然後通過mMainHandler向主線程發送一個消息,因爲在子線程中是不能更新UI的。之所以在異步加載圖片的bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight)方法中添加一個ImageView參數(注意:該參數在同步加載圖片的loadBitmap(String uri, int reqWidth, int reqHeight)方法中不存在),這是因爲異步加載圖片纔是ImageLoader整個加載圖片對外提供的接口,而同步加載是ImageLoader內部調用的接口,爲了讓ImageLoader與外部其它類最大程度的解耦,所以直接提供一個根據Uri與ImageView這兩個核心參數來設置圖片的功能,即將網絡地址爲Uri的圖片設置到ImageView這個控件上去。如果不提供該參數,則只能像同步加載那樣獲取到一個Bitmap對象,然後通過ImageView的setImageBitmap()方法來爲ImageView控件設置圖片,而很顯然前面一種方式較好。

另外可以看到在異步加載圖片時採用了線程池,首先肯定不能採用普通的線程去完成該功能,因爲照片牆的原理是當用戶滑動GridView列表時會從網絡上去獲取圖片資源,即調用該異步加載的bindBitmap方法,如果採用普通的線程去完成該功能,則在列表滑動的過程中會產生大量的線程,這顯然是不利於整體效率的提高,而線程池可以複用線程,使線程的數量始終維持在一個合理的範圍之內。也沒采用AsyncTask類是因爲從安卓3.0開始默認情況下AsyncTask是串行執行的,這顯然是不符合要求的。


二圖片的壓縮處理:

關於圖片的壓縮處理的理論知識,請參看我的博客:安卓圖片緩存技術這裏我們將圖片的壓縮處理功能單獨的抽象爲一個類ImageResizer,其代碼如下:

public class ImageResizer {
    private static final String TAG = "ImageResizer";

    public ImageResizer() {
    }

    public Bitmap decodeSampledBitmapFromResource(Resources res,
            int resId, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

    public int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        if (reqWidth == 0 || reqHeight == 0) {
            return 1;
        }

        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        Log.d(TAG, "origin, w= " + width + " h=" + height);
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and
            // keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        Log.d(TAG, "sampleSize:" + inSampleSize);
        return inSampleSize;
    }
}
圖片壓縮功能的代碼的理論講解請參考我的博客:安卓圖片緩存技術,在此不再贅述。

三內存緩存與磁盤緩存功能及網絡拉取功能:(磁盤緩存的添加是在網絡拉取功能中完成的)

正如前面理論知識介紹的使用LruCach與DiskLlruCache來完成內存緩存與磁盤緩存的功能,在ImageLoader初始化的時候,會創建LruCache與DiskLruCache,代碼如下:

 private Context mContext;
    private ImageResizer mImageResizer = new ImageResizer();
    private LruCache<String, Bitmap> mMemoryCache;
    private DiskLruCache mDiskLruCache;

    private ImageLoader(Context context) {
        mContext = context.getApplicationContext();
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
        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);
                mIsDiskLruCacheCreated = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
在創建磁盤緩存時首先作了一個判斷,即可用磁盤空間是否大於磁盤緩存所需的空間大小,如果小於則表示用戶手機上的內存空間不足以創建該大小的磁盤緩存,一般情況下我們將內存緩存的容量定義爲當前進程可用內存的1/8,磁盤緩存容量定爲50MB就足夠。


在創建完內存緩存與磁盤緩存後還需要提供一下方法來完成緩存的添加與獲取等功能,首先看內存緩存,代碼如下:

  private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    private Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }
這個在前面博客的理論知識部分已經進行過詳細講解,在此不再贅述。


再來看一下磁盤緩存:首先看一下磁盤緩存的添加功能:代碼如下:

 private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
            throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network from UI Thread.");
        }
        if (mDiskLruCache == null) {
            return null;
        }
        
        String key = hashKeyFormUrl(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

注意磁盤緩存的添加是在從網絡上獲取到圖片時添加的,而內存緩存的添加是在磁盤緩存添加完成時添加的,可以看到磁盤緩存的添加需要通過 DiskLruCache.Editor這個類來完成,Editor提供了commit()與abort()方法來提交和撤銷對磁盤文件系統的寫操作,具體理論知識請參看我的博客:【安卓緩存策略系列】安卓緩存策略之磁盤緩存DiskLruCache

在來看一下磁盤緩存的獲取,代碼如下:

  private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
            int reqHeight) throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }

        return bitmap;
    }
可以看到磁盤緩存的讀取是通過DiskLruCache.Snapshot這個類來完成的,通過Snapshot可以得到磁盤緩存對象對應的FileInputStream,但是FileInputStream不能很好的進行圖片的壓縮處理,因此通過FileDescriptor來加載壓縮後的圖片,最後將加載後的圖片添加到內存緩存中,注意內存緩存的添加是在磁盤緩存添加完成時添加的。即三級緩存策略的獲取與添加過程爲網絡-磁盤-內存。



四滑動GridView時列表圖片顯示錯位的解決方案:

private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.imageView;
            String uri = (String) imageView.getTag(TAG_KEY_URI);
            if (uri.equals(result.uri)) {
                imageView.setImageBitmap(result.bitmap);
            } else {
                Log.w(TAG, "set image bitmap,but url has changed, ignored!");
            }
        };
    };
前面我們在bindBitmap中的Runnable對象中通過 mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget()發送一個消息,該消息是一個包含圖片Uri,bitmap圖片,顯示控件ImageView的LoaderResult對象,該消息交給mMainHandler的handleMessage()方法來處理,在該方法中主要邏輯是獲取到傳遞過來的LoaderResult對象,然後通過LoaderResult對象獲取到bitmap圖片將其顯示在對應的ImageView控件上,正如我們在前面所說的,當用戶快速滑動GridView列表時可能導致列表錯位現象,因此在設置bitmap圖片時首先需要做個判斷,即在給ImageView設置圖片之前先判斷接受到的uri與該圖片的Uri是否相同,如果相同則將其設置爲該ImagView控件的圖片,否則不處理。

另外我們可以看到mMainHandler的創建是直接通過主線程的Looper來構造的Handler對象,這樣我們在子線程中就可以直接構造ImageLoader對象。而不需要在子線程中創建Looper對象。


五針對GridView中列表卡頓現象的優化處理:
首先,不要再getView中執行耗時操作,即在getView中必須通過異步的方式加載圖片。

其次,控制異步任務的執行頻率,對於列表而言,僅僅在getView中採用異步操作是不夠的,以照片牆來說,在getView方法中會通過ImageLoader的binBitmap方法來異步加載圖片,但是如果用戶刻意的頻繁上下滑動,這會在瞬時產生上百個異步任務,這些異步任務會造成線程池的擁堵和大量的UI更新操作,這是沒有意義的,但因爲瞬時產生大量的UI更新操作,這些UI操作是運行在主線程中的,會造成一定程度上的卡頓,那如何解決呢?可以考慮在列表滑動的時候停止加載圖片,等列表停止滑動後再加載圖片,這樣仍可以獲得良好的用戶體驗,具體實現時,可以給ListView或GridView設置setOnScrollListener,,在 OnScrollListener的onScrollStateChanged方法中判斷列表是否處於滑動狀態,如果是則停止加載圖片,代碼如下:

public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
            mIsGridViewIdle = true;
            mImageAdapter.notifyDataSetChanged();
        } else {
            mIsGridViewIdle = false;
        }
    }

在getView方法中,僅當列表靜止時才能加載圖片,代碼如下:

 if (mIsGridViewIdle && mCanGetBitmapFromNetWork) {
                imageView.setTag(uri);
                mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth);


最後給出整個ImageLoader的實現代碼:

public class ImageLoader {

    private static final String TAG = "ImageLoader";

    public static final int MESSAGE_POST_RESULT = 1;

    private static final int CPU_COUNT = Runtime.getRuntime()
            .availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final long KEEP_ALIVE = 10L;

    private static final int TAG_KEY_URI = R.id.imageloader_uri;
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
    private static final int IO_BUFFER_SIZE = 8 * 1024;
    private static final int DISK_CACHE_INDEX = 0;
    private boolean mIsDiskLruCacheCreated = false;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

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

    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
            KEEP_ALIVE, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(), sThreadFactory);
    
    private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.imageView;
            String uri = (String) imageView.getTag(TAG_KEY_URI);
            if (uri.equals(result.uri)) {
                imageView.setImageBitmap(result.bitmap);
            } else {
                Log.w(TAG, "set image bitmap,but url has changed, ignored!");
            }
        };
    };

    private Context mContext;
    private ImageResizer mImageResizer = new ImageResizer();
    private LruCache<String, Bitmap> mMemoryCache;
    private DiskLruCache mDiskLruCache;

    private ImageLoader(Context context) {
        mContext = context.getApplicationContext();
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
        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);
                mIsDiskLruCacheCreated = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * build a new instance of ImageLoader
     * @param context
     * @return a new instance of ImageLoader
     */
    public static ImageLoader build(Context context) {
        return new ImageLoader(context);
    }

    private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    private Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }

    /**
     * load bitmap from memory cache or disk cache or network async, then bind imageView and bitmap.
     * NOTE THAT: should run in UI Thread
     * @param uri http url
     * @param imageView bitmap's bind object
     */
    public void bindBitmap(final String uri, final ImageView imageView) {
        bindBitmap(uri, imageView, 0, 0);
    }

    public void bindBitmap(final String uri, final ImageView imageView,
            final int reqWidth, final int reqHeight) {
        imageView.setTag(TAG_KEY_URI, uri);
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        Runnable loadBitmapTask = new Runnable() {

            @Override
            public void run() {
                Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
                if (bitmap != null) {
                    LoaderResult result = new LoaderResult(imageView, uri, bitmap);
                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
                }
            }
        };
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }

    /**
     * load bitmap from memory cache or disk cache or network.
     * @param uri http url
     * @param reqWidth the width ImageView desired
     * @param reqHeight the height ImageView desired
     * @return bitmap, maybe null.
     */
    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
        Bitmap bitmap = loadBitmapFromMemCache(uri);
        if (bitmap != null) {
            Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
            return bitmap;
        }

        try {
            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
            if (bitmap != null) {
                Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
                return bitmap;
            }
            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
            Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (bitmap == null && !mIsDiskLruCacheCreated) {
            Log.w(TAG, "encounter error, DiskLruCache is not created.");
            bitmap = downloadBitmapFromUrl(uri);
        }

        return bitmap;
    }

    private Bitmap loadBitmapFromMemCache(String url) {
        final String key = hashKeyFormUrl(url);
        Bitmap bitmap = getBitmapFromMemCache(key);
        return bitmap;
    }

    private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
            throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network from UI Thread.");
        }
        if (mDiskLruCache == null) {
            return null;
        }
        
        String key = hashKeyFormUrl(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

    private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
            int reqHeight) throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }

        return bitmap;
    }

    public boolean downloadUrlToStream(String urlString,
            OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),
                    IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);

            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (IOException e) {
            Log.e(TAG, "downloadBitmap failed." + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            MyUtils.close(out);
            MyUtils.close(in);
        }
        return false;
    }

    private Bitmap downloadBitmapFromUrl(String urlString) {
        Bitmap bitmap = null;
        HttpURLConnection urlConnection = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),
                    IO_BUFFER_SIZE);
            bitmap = BitmapFactory.decodeStream(in);
        } catch (final IOException e) {
            Log.e(TAG, "Error in downloadBitmap: " + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            MyUtils.close(in);
        }
        return bitmap;
    }

    private String hashKeyFormUrl(String url) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    public File getDiskCacheDir(Context context, String uniqueName) {
        boolean externalStorageAvailable = Environment
                .getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        final String cachePath;
        if (externalStorageAvailable) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }

        return new File(cachePath + File.separator + uniqueName);
    }

    @TargetApi(VERSION_CODES.GINGERBREAD)
    private long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
            return path.getUsableSpace();
        }
        final StatFs stats = new StatFs(path.getPath());
        return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
    }

    private static class LoaderResult {
        public ImageView imageView;
        public String uri;
        public Bitmap bitmap;

        public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {
            this.imageView = imageView;
            this.uri = uri;
            this.bitmap = bitmap;
        }
    }
}

注:本博文爲本人閱讀安卓開發藝術探索這本書的讀書記錄,絕大部分內容來自該書,中間融合了本人自己的思考過程,如果看官覺得不錯記得頂個贊哦微笑






發佈了98 篇原創文章 · 獲贊 330 · 訪問量 118萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章