Android自定义图片加载三级缓存

最近刚完成一个仿微信项目,刚闲下来,写篇文章,方便大家理解Android的图片加载模式。

有人会问:为什么要自定义,三方库不是很完善吗?

我说:是的,现在有Picasso,Glide等很多优秀的封装,可以完成。

但重点是:

1、通过自定义了解原理和本质才是最主要的。

2、三方库少则十多个类,多则几十个甚至上百个类,方法更是不计其数,要是完全掌握恐怕至少也要一个月时间,这样对我们的学习效率影响很大,试想我们是在最有限的时间掌握原理好呢,还是一个劲的钻进无限的回调,参数拼接... ..好呢。

3、也是上班原因,由于本人所在单位对外部访问严格的审核,所以开发中的很多技术点,很多都是自己封装完成,在自己编写的过程中不断完善和探索,以达到能公用的目标为己任。

好了,闲话不啰嗦,我们切入正题。

一、原理:图片加载的缓存机制,相信大家都知道,但还是唠叨几句,希望对初学的人也有个认知。

因为APP端用户是流量有限的,所以能最大限度的减下用户流量的消耗,是用户很看重的一点,所以要把一些图片呀,音频视频,集合呀等一些过大的数据,缓存起来,等到用户再次登录,就可以从本地获取,极大节约了资源。

三级缓存:

好了,以目了然,现在就开始具体的代码实现了

二:代码

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);
}
就这么简单


发布了19 篇原创文章 · 获赞 19 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/baidu_30084597/article/details/79089162