天天看點

Android自定義圖檔加載三級緩存

最近剛完成一個仿微信項目,剛閑下來,寫篇文章,友善大家了解Android的圖檔加載模式。

有人會問:為什麼要自定義,三方庫不是很完善嗎?

我說:是的,現在有Picasso,Glide等很多優秀的封裝,可以完成。

但重點是:

1、通過自定義了解原理和本質才是最主要的。

2、三方庫少則十多個類,多則幾十個甚至上百個類,方法更是不計其數,要是完全掌握恐怕至少也要一個月時間,這樣對我們的學習效率影響很大,試想我們是在最有限的時間掌握原理好呢,還是一個勁的鑽進無限的回調,參數拼接... ..好呢。

3、也是上班原因,由于本人所在機關對外部通路嚴格的稽核,是以開發中的很多技術點,很多都是自己封裝完成,在自己編寫的過程中不斷完善和探索,以達到能公用的目标為己任。

好了,閑話不啰嗦,我們切入正題。

一、原理:圖檔加載的緩存機制,相信大家都知道,但還是唠叨幾句,希望對初學的人也有個認知。

因為APP端使用者是流量有限的,是以能最大限度的減下使用者流量的消耗,是使用者很看重的一點,是以要把一些圖檔呀,音頻視訊,集合呀等一些過大的資料,緩存起來,等到使用者再次登入,就可以從本地擷取,極大節約了資源。

三級緩存:

Android自定義圖檔加載三級緩存

好了,以目了然,現在就開始具體的代碼實作了

二:代碼

ImageLoader.java(圖檔加載類)

ImageLoadListener.java(圖檔加載完成回調接口)

/**
 * Created by jambestwick on 2018/1/10.
 * <p>
 * 三級緩存
 */

public class ImageLoader {

    private static final String TAG = "ImageLoader";

    // LRUCahce 池子
    private static LruCache<String, Bitmap> mCache;
    private static Handler mHandler;
    private static Map<ImageView, Future<?>> mTaskTags = new LinkedHashMap<ImageView, Future<?>>();
    private Context mContext;

    private static ImageLoader imageLoader;

    public ImageLoader(Context context) {
        mContext = context;
        if (mCache == null) {
            // 防止OOM,mCache存放
            int maxSize = (int) (Runtime.getRuntime().freeMemory() / 2);
            mCache = new LruCache<String, Bitmap>(maxSize) {
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getRowBytes() * value.getHeight();
                }
            };
        }
        if (mHandler == null) {
            mHandler = new Handler();
        }

    }

    public static ImageLoader getImageLoader() {
        return null == imageLoader ? new ImageLoader(BaseApplication.getBaseApplication()) : imageLoader;
    }

    public LruCache<String, Bitmap> getmCache() {
        return mCache;
    }

    public void display(ImageView iv, String url, ImageLoadListener loadListener) {
        // 1.去記憶體中取
        Bitmap bitmap;
        bitmap = mCache.get(url);
        if (bitmap != null) {
            Log.e("1記憶體讀取:", url);
            iv.setImageBitmap(bitmap);
            if (null != loadListener) {//單個朋友圈圖檔重置layoutParams
                invokeRelayoutBitmap(loadListener, bitmap, iv, url);
            }
            return;
        }

        // 2.去硬碟上取
        bitmap = loadBitmapFromLocal(url);
        if (bitmap != null) {
            Log.e("2檔案讀取:", url);
            iv.setImageBitmap(bitmap);
            if (null != loadListener) {//單個朋友圈圖檔重置layoutParams
                invokeRelayoutBitmap(loadListener, bitmap, iv, url);

            }
            return;
        }

        // 3. 去網絡擷取圖檔
        loadBitmapFromNet(iv, url, loadListener);


    }

    private void invokeRelayoutBitmap(ImageLoadListener loadListener, Bitmap bitmap, ImageView iv, String url) {
        mHandler.post(new RefreshBitmap(loadListener, bitmap, url, iv));

    }

    private void loadBitmapFromNet(ImageView iv, String url, ImageLoadListener loadListener) {
        // 判斷是否有線程在為 imageView加載資料
        Future<?> futrue = mTaskTags.get(iv);
        if (futrue != null && !futrue.isCancelled() && !futrue.isDone()) {
            Log.d(TAG, "取消任務");
            // 線程正在執行
            futrue.cancel(true);
        }
        futrue = ThreadPool.getCachedInstance().submit(new ImageLoadTask(iv, url, loadListener));
        mTaskTags.put(iv, futrue);
    }

    class ImageLoadTask implements Runnable {

        private String mUrl;
        private ImageView iv;
        private ImageLoadListener loadListener;

        public ImageLoadTask(ImageView iv, String url, ImageLoadListener loadListener) {
            this.mUrl = url;
            this.iv = iv;
            this.loadListener = loadListener;
        }

        @Override
        public void run() {
            try {

                Log.e("網絡異步", "圖檔" + mUrl);
                // 擷取連接配接

                //HttpsURLConnection conn = HttpsFormUtil.getInstance().getHttpURLConnection(new URL(mUrl));
                HttpURLConnection conn = (HttpURLConnection) new URL(mUrl).openConnection();

                conn.setConnectTimeout(30 * 1000);// 設定連接配接伺服器逾時時間
                conn.setReadTimeout(30 * 1000);// 設定讀取響應逾時時間

                // 連接配接網絡
                conn.connect();

                // 擷取響應碼
                int code = conn.getResponseCode();

                if (200 == code) {
                    InputStream is = conn.getInputStream();

                    // 将流轉換為bitmap
                    final Bitmap bitmap = BitmapFactory.decodeStream(is);

                    // 存儲到本地
                    write2Local(mUrl, bitmap);

                    // 存儲到記憶體
                    mCache.put(mUrl, bitmap);

                    mHandler.post(new Runnable() {

                        @Override
                        public void run() {
                            if (null != loadListener) {
                                loadListener.onLoadingComplete(mUrl, iv, bitmap);
                            }
                            display(iv, mUrl, loadListener);
                        }
                    });
                }
            } catch (Exception e) {
                Log.e(TAG, "download failed:" + e.toString());
            }
        }
    }


    /**
     * 本地找圖檔
     *
     * @param url
     */
    private Bitmap loadBitmapFromLocal(String url) {
        // 去找檔案,将檔案轉換為bitmap
        String name;
        try {
            name = MD5Encode.encode(url);

            File file = new File(getCacheDir(), name);
            if (file.exists()) {

                Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());

                // 存儲到記憶體
                mCache.put(url, bitmap);
                return bitmap;
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            Log.e(TAG, "load LocalImage Failed:" + e.toString());
        }

        return null;
    }

    private void write2Local(String url, Bitmap bitmap) {
        String name;
        FileOutputStream fos = null;
        try {
            name = MD5Encode.encode(url);
            File file = new File(getCacheDir(), name);
            fos = new FileOutputStream(file);

            // 将圖像寫到流中,放縮略,減下存儲
            bitmap.compress(Bitmap.CompressFormat.JPEG, 50, fos);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    Log.e(TAG, "io exception:" + e.toString());
                }
            }
        }
    }

    private String getCacheDir() {

        String state = Environment.getExternalStorageState();//外置sd卡路徑
        File dir;
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            // 有sd卡
            dir = new File(Environment.getExternalStorageDirectory(), "/zhanglong/data/" + mContext.getPackageName()
                    + "/icon");
        } else {
            // 沒有sd卡
            dir = new File(mContext.getCacheDir(), "/icon");

        }
        if (!dir.exists()) {
            dir.mkdirs();
        }

        return dir.getAbsolutePath();
    }

    static class RefreshBitmap implements Runnable {
        private ImageLoadListener loadListener;
        private Bitmap bitmap;
        private String url;
        private ImageView iv;

        public RefreshBitmap(ImageLoadListener loadListener, Bitmap bitmap, String url, ImageView iv) {
            this.loadListener = loadListener;
            this.bitmap = bitmap;
            this.url = url;
            this.iv = iv;
        }

        @Override
        public void run() {
            loadListener.onLoadingComplete(url, iv, bitmap);
        }
    }

}
           

核心方法就是一個display,

/**
 * Created by jambestwick on 2018/1/10.
 * 下載下傳完成監聽
 */

public interface ImageLoadListener {
   void onLoadingComplete(String imageUri, View view, Bitmap bitmap);
}
           

就這麼簡單

繼續閱讀