美文网首页
16.手写图片加载框架ImageLoader

16.手写图片加载框架ImageLoader

作者: 任振铭 | 来源:发表于2018-06-13 07:06 被阅读46次
    概述

    第三方开源的图片框架很多,这里自己去写一个的目的是通过这样一个写的过程,拓展自己对架构设计的理解,包括设计模式,线程,策略,缓存等等。另外大型的框架例如Glide,代码很完善,扩展性很高,但是阅读起来有难度,而实际上,这些框架底层实现原理都是类似的,所以通过构建一个简单框架的过程更加有助于对其原理的理解,算是为阅读复杂的第三方源码打下一个基础。

    github地址:https://github.com/renzhenming/ImageLoader.git

    今天的框架要实现一下的功能:

    1.根据用户需求可以灵活配置(建造者模式)
    2.支持高并发,图片加载的优先级
    3.支持可以选择不同的加载策略,对加载策略进行扩展
    4.二级缓存 加载图片时内存中已经加载了,则从内存中加载,不存在去外置卡中5.加载,外置还不存在则从网络下载
    6.并对缓存策略可以扩展
    7.支持从加载过程中显示默认加载图片
    8.支持加载失败时 显示默认错误图片
    9.图片显示自适应。从网络加载下来的图片经最佳比例压缩后显示不能失真变形
    10.支持请求转发,下载

    用到的模式:
    1.生产者 消费者模式
    2.建造者模式
    3.单例模式
    4.模板方法模式
    5.策略模式

    用到的知识点
    1.内存缓存 LruCache技术
    2.硬盘缓存技术DiskLruCache技术
    3.图片下载时请求转发

    ImageLoader类图.png
    框架构建流程

    如上图,首先使用Builder设计模式构建ImageLoaderConfig,这个类处理图片加载框架的全局配置信息,包括加载策略,缓存策略,线程数,以及加载中一些图片的配置,封装成了DisplayConfig对象;SimpleImageLoader是对外暴露的主类,它持有配置对象的引用,所以它可以调用所以图片加载中所涉及的配置,然后将这些配置封装成BitmapRequest对象,一个BitmapRequest对象对应一次网络请求,在SimpleImageLoader初始化的同时还会初始化全局的请求队列RequestQueue,BitmapRequest对象会被加入队列中,这时候分发器开始工作,将BitmapRequest按照一定协议分发给不同的加载器,加载器拿到请求后先从缓存BitmapCache中获取,有缓存则直接展示然后再去加载网络图片,加载网络图片的时候又涉及到加载策略的选择,根据不同策略进行不同的加载。

    关键代码
    1.配置管理

    ImageLoaderConfig ,配置管理类,我们把一些关键的配置信息单独封装起来,以Builder模式构建,用户可以自由的配置自己需要的,同时提高扩展性,这正是Builder设计模式的优点.ImageLoaderConfig 负责处理整个框架的配置信息,目前包括缓存策略,加载策略,加载中显示的占位图,加载失败展示的占位图等等

    package com.rzm.imageloader.config;
    
    import com.rzm.imageloader.cache.BitmapCache;
    import com.rzm.imageloader.policy.LoadPolicy;
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:21
     * Description:This is ImageLoaderConfig
     * 以建造者模式实现,管理ImageLoader配置信息
     */
    public class ImageLoaderConfig {
    
        /**
         * 图片显示配置 TODO 初始化
         */
        private DisplayConfig displayConfig;
    
        /**
         * 缓存策略
         */
        private BitmapCache bitmapCache;
    
        /**
         * 加载策略
         */
        private LoadPolicy loadPolicy;
    
        /**
         * 默认线程数
         */
        private int threadCount = Runtime.getRuntime().availableProcessors();
    
        private ImageLoaderConfig(){}
    
        /**
         * 建造者模式
         */
        public static class Builder{
    
            /**
             * Builder持有外部类的引用,在new的时候创建出来
             */
            private ImageLoaderConfig config;
    
            public Builder(){
                config = new ImageLoaderConfig();
            }
    
            /**
             * 设置缓存策略
             * @param bitmapCache
             * @return
             */
            public Builder setCachePolicy(BitmapCache bitmapCache){
                config.bitmapCache = bitmapCache;
                return this;
            }
    
            /**
             * 设置加载策略
             * @param loadPolicy
             * @return
             */
            public Builder setLoadPolicy(LoadPolicy loadPolicy){
                config.loadPolicy = loadPolicy;
                return this;
            }
    
            /**
             * 设置线程数
             * @param threadCount
             * @return
             */
            public Builder setThreadCount(int threadCount){
                config.threadCount = threadCount;
                return this;
            }
    
            /**
             * 设置加载过程中的图片
             * @param resId
             * @return
             */
            public Builder setLoadingImage(int resId){
                if (config.displayConfig == null){
                    throw new NullPointerException("you have not set DisplayConfig,DisplayConfig is null");
                }
                config.displayConfig.loadingImage = resId;
                return this;
            }
    
            /**
             * 设置加载失败显示的图片
             * @param resId
             * @return
             */
            public Builder setErrorImage(int resId){
                if (config.displayConfig == null){
                    throw new NullPointerException("you have not set DisplayConfig,DisplayConfig is null");
                }
                config.displayConfig.errorImage = resId;
                return this;
            }
    
    
    
            /**
             * 构建
             * @return
             */
            public ImageLoaderConfig build(){
                return config;
            }
        }
    
        public DisplayConfig getDisplayConfig() {
            return displayConfig;
        }
    
        public BitmapCache getBitmapCache() {
            return bitmapCache;
        }
    
        public LoadPolicy getLoadPolicy() {
            return loadPolicy;
        }
    
        public int getThreadCount() {
            return threadCount;
        }
    }
    
    
    package com.rzm.commonlibrary.general.imageloader.config;
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:24
     * Description:This is DisplayConfig
     * 图片显示配置类,单独拿出来作为一个单独类有利于扩展,仿Glide
     */
    public class DisplayConfig {
    
        /**
         * 加载过程中的占位图片
         */
        public int loadingImage = -1;
    
        /**
         * 加载失败显示的图片
         */
        public int errorImage = -1;
    }
    
    2.SimpleImageLoader

    以单例形式构建的交互类,持有ImageLoaderConfig 配置引用,负责将每一个网络请求封装成BitmapRequest对象,SimpleImageLoader初始化的时候会构建出一个全局的阻塞式队列,BitmapRequest会被加入这个队列中,此时分发器开始工作,分发器的构建启发于Android消息队列中的looper,负责将每个Request请求从队列中取出交给负责处理这个请求的加载器

    package com.rzm.commonlibrary.general.imageloader.loader;
    
    import android.graphics.Bitmap;
    import android.widget.ImageView;
    
    import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;
    import com.rzm.commonlibrary.general.imageloader.config.ImageLoaderConfig;
    import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
    import com.rzm.commonlibrary.general.imageloader.request.RequestQueue;
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:22
     * Description:This is SimpleImageLoader
     */
    public class SimpleImageLoader {
    
        /**
         * 持有配置信息对象的引用
         */
        private ImageLoaderConfig config;
    
        private static volatile SimpleImageLoader instance;
    
        /**
         * 请求队列
         */
        private RequestQueue requestQueue;
    
        private SimpleImageLoader(){}
    
        private SimpleImageLoader(ImageLoaderConfig config){
            this.config = config;
            //初始化请求队列
            requestQueue = new RequestQueue(config.getDispatcherCount());
            //开启请求队列
            requestQueue.start();
        }
    
        /**
         * 用于在Application中初始化ImageLoader 设置配置信息
         * 必须,否则调用空参getInstance()会抛异常
         * @param config
         * @return
         */
        public static SimpleImageLoader init(ImageLoaderConfig config){
            if (instance == null){
                synchronized (SimpleImageLoader.class){
                    if (instance == null){
                        instance = new SimpleImageLoader(config);
                    }
                }
            }
            return instance;
        }
    
        /**
         * 用于在代码中获取单例对象
         * @return
         */
        public static SimpleImageLoader getInstance(){
            if (instance == null){
                throw new UnsupportedOperationException("SimpleImageLoader haven't been init with ImageLoaderConfig,call init(ImageLoaderConfig config) in your application");
            }
            return instance;
        }
    
        /**
         * 显示图片
         * @param imageView
         * @param url
         */
        public void display(ImageView imageView,String url){
            display(imageView,url,null,null);
        }
        /**
         * 显示图片
         * @param imageView
         * @param url
         */
        public void display(ImageView imageView,String url,DisplayConfig displayConfig){
            display(imageView,url,displayConfig,null);
        }
    
        /**
         * 显示图片
         * @param imageView
         * @param url
         */
        public void display(ImageView imageView,String url,ImageListener listener){
            display(imageView,url,null,listener);
        }
        /**
         * 显示图片,用于针对特殊图片配置特殊的配置信息
         * @param imageView
         * @param url
         * @param displayConfig
         * @param listener
         */
        public void display(ImageView imageView,String url,DisplayConfig displayConfig,ImageListener listener){
            if (imageView  == null){
                throw new NullPointerException("ImageView cannot be null");
            }
            //封装成一个请求对象
            BitmapRequest request= new BitmapRequest(imageView,url,displayConfig,listener);
            //加入请求队列
            requestQueue.addRequest(request);
        }
    
        /**
         * 监听图片,设置后期处理,仿Glide
         */
        public static interface ImageListener{
            void onComplete(ImageView imageView, Bitmap bitmap,String url);
        }
    
        /**
         * 获取全局配置
         * @return
         */
        public ImageLoaderConfig getConfig() {
            return config;
        }
    }
    
    
    3.BitmapRequest请求对象

    一个BitmapRequest对象中封装了这次请求的所有相关信息,包括这个请求的图片显示逻辑的配置DisplayConfig,当前全局的加载策略LoadPolicy,请求图片的网络地址和这个地址需要展示的控件对象ImageView,另外我对BitmapRequest对象重写了hashCode和equals方法,实现了Comparable接口,以实现这个对象的比较逻辑,为加载策略服务

    package com.rzm.commonlibrary.general.imageloader.request;
    
    import android.widget.ImageView;
    
    import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;
    import com.rzm.commonlibrary.general.imageloader.loader.SimpleImageLoader;
    import com.rzm.commonlibrary.general.imageloader.policy.LoadPolicy;
    import com.rzm.commonlibrary.general.imageloader.utils.Md5Util;
    
    import java.lang.ref.SoftReference;
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:23
     * Description:This is BitmapRequest
     */
    public class BitmapRequest implements Comparable<BitmapRequest>{
    
        /**
         * 展示配置
         */
        private DisplayConfig disPlayConfig;
        /**
         * 加载策略
         */
        private LoadPolicy loadPolicy = SimpleImageLoader.getInstance().getConfig().getLoadPolicy();
    
        /**
         * 序列号,用于顺序比较
         */
        private int serialNum;
    
        /**
         * 持有ImageView的软引用
         */
        private SoftReference<ImageView> imageViewSoftReference;
    
        /**
         * 图片路径
         */
        private String imageUrl;
    
        /**
         * 图片路径的md5值
         */
        private String imageUrlMd5;
    
        /**
         * 下载完成的监听
         */
        private SimpleImageLoader.ImageListener imageListener;
    
        public BitmapRequest() {
    
        }
    
        public BitmapRequest(ImageView imageView, String imageUrl) {
            this(imageView,imageUrl,null,null);
        }
    
        public BitmapRequest(ImageView imageView,String imageUrl,DisplayConfig displayConfig) {
            this(imageView,imageUrl,displayConfig,null);
        }
    
        public BitmapRequest(ImageView imageView,String imageUrl,SimpleImageLoader.ImageListener imageListener) {
            this(imageView,imageUrl,null,imageListener);
        }
    
        public BitmapRequest(ImageView imageView, String imageUrl, DisplayConfig displayConfig,
                             SimpleImageLoader.ImageListener imageListener){
            this.imageViewSoftReference = new SoftReference<ImageView>(imageView);
    
            if (imageUrl != null) {
                imageView.setTag(imageUrl);
                imageUrlMd5 = Md5Util.toMD5(imageUrl);
            }
            this.imageUrl = imageUrl;
    
            if (displayConfig != null){
                this.disPlayConfig = displayConfig;
            }
            if (imageListener != null) {
                this.imageListener = imageListener;
            }
        }
    
        /**
         * 请求的先后顺序是根据加载的策略进行的,不同的策略比较的条件也不同,所以
         * 这里要把比较的逻辑交割具体的策略去做,策略有多种,所以通过接口调用,增强扩展性
         * @return
         */
        @Override
        public int compareTo(BitmapRequest o) {
            return loadPolicy.compareTo(o,this);
        }
    
        public int getSerialNum() {
            return serialNum;
        }
    
        public void setSerialNum(int serialNum) {
            this.serialNum = serialNum;
        }
    
        /**
         * 获取这个请求对应的ImageView
         * @return
         */
        public ImageView getImageView(){
            if (imageViewSoftReference == null)
                return null;
            return imageViewSoftReference.get();
        }
    
        public DisplayConfig getDisPlayConfig() {
            return disPlayConfig;
        }
    
        public LoadPolicy getLoadPolicy() {
            return loadPolicy;
        }
    
        public SoftReference<ImageView> getImageViewSoftReference() {
            return imageViewSoftReference;
        }
    
        public String getImageUrl() {
            return imageUrl;
        }
    
        public String getImageUrlMd5() {
            return imageUrlMd5;
        }
    
        public SimpleImageLoader.ImageListener getImageListener() {
            return imageListener;
        }
    
        /**
         * BitmapRequest会被加入请求队列中,在队列中有需要做判断当前请求是否存在
         * 那么就涉及到这个对象的比较,所以需要重写hashCode和equals方法
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            BitmapRequest request = (BitmapRequest) o;
            return serialNum == request.serialNum &&
                    loadPolicy.equals(request.loadPolicy);
        }
    
        @Override
        public int hashCode() {
            int result = loadPolicy != null ? loadPolicy.hashCode():0;
            result = 66*result+serialNum;
            return result;
        }
    
    }
    
    
    4.分发器实现原理

    RequestDispatcher是一个继承了Thread的线程对象,队列创建后会根据配置创建出指定数量的分发器,当队列中有请求对象后就从队列中取出对象交给加载器,根据一定的协议选择合适的加载器进行网络请求,当队列中没有对象时,会进入休眠状态

    package com.rzm.commonlibrary.general.imageloader.request;
    
    import android.text.TextUtils;
    import android.util.Log;
    
    import com.rzm.commonlibrary.general.imageloader.loader.Loader;
    import com.rzm.commonlibrary.general.imageloader.loader.LoaderManager;
    
    import java.util.concurrent.BlockingQueue;
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:24
     * Description:This is RequestDispatcher
     */
    public class RequestDispatcher extends Thread{
    
        /**
         * 从队列中转发请求需要持有队列的引用
         */
        private BlockingQueue<BitmapRequest> blockingQueue;
    
        public RequestDispatcher(BlockingQueue<BitmapRequest> blockingQueue) {
            this.blockingQueue = blockingQueue;
        }
    
        /**
         * 阻塞式队列,转发器开启,从队列中取请求队列,如果没有则会阻塞当前线程,所以这里
         * 是在子线程开启的
         */
        @Override
        public void run() {
            while(!isInterrupted()){
                try {
                    BitmapRequest request = blockingQueue.take();
                    //处理请求对象,交给loader
                    String schema = parseSchema(request.getImageUrl());
                    //获取加载器
                    Loader loader = LoaderManager.getInstance().getLoader(schema);
                    if (loader == null){
                        Log.d("TAG",request.getImageUrl() + "没有找到对应的加载器");
                        return;
                    }
                    loader.load(request);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 根据图片url判断加载类型
         * @param imageUrl
         * @return
         */
        private String parseSchema(String imageUrl) {
            if (TextUtils.isEmpty(imageUrl)){
                return null;
            }
            if (imageUrl.contains("://")){
                //形如 http://xxx 或者file://xxx,这样截取后
                //可以获得http file等前缀,根据这个前缀获取相应
                //的加载器
                return imageUrl.split("://")[0];
            }else{
                Log.d("TAG","不持支的图片类型");
            }
            return null;
        }
    }
    
    
    5.加载器实现原理

    目前该框架支持网络图片和本地图片加载,不同的加载器根据不同的url进行选择,为了提高扩展性,设置接口和抽象类的实现方式,下面是顶层接口

    package com.rzm.commonlibrary.general.imageloader.loader;
    
    
    import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:21
     * Description:This is Loader
     */
    public interface Loader {
    
        /**
         * 加载图片
         * @param request
         */
        void load(BitmapRequest request);
    }
    

    网络加载器和本地加载器实现逻辑有所不同,但是有一些公共的操作存在,比如加载前显示加载中占位图,加载失败显示失败图片等等,这些操作可以放在一个公共的基类中实现,所以这里创建了一个抽象类作为上层类处理公共逻辑

    package com.rzm.commonlibrary.general.imageloader.loader;
    
    import android.graphics.Bitmap;
    import android.widget.ImageView;
    
    
    import com.rzm.commonlibrary.general.imageloader.cache.BitmapCache;
    import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;
    import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:22
     * Description:This is AbstractLoader
     * 加载器策略不同,则不同的加载器实现方式不同,但是他们有相同的操作,比如显示
     * 占位图等,所以这些相同操作在抽象一层出来
     */
    public abstract class AbstractLoader implements Loader {
    
        private static final String TAG = "AbstractLoader";
    
        private AtomicInteger integer = new AtomicInteger(0);
        /**
         * 加载器加载图片的逻辑是先缓存后网络,所以需要持有缓存对象的引用
         */
        private BitmapCache bitmapCache = SimpleImageLoader.getInstance().getConfig().getBitmapCache();
    
        /**
         * 同样因为要处理显示时的逻辑,所以需要持有显示配置对象的引用
         */
        private DisplayConfig displayConfig = SimpleImageLoader.getInstance().getConfig().getDisplayConfig();
        @Override
        public void load(BitmapRequest request) {
            //从缓存中获取Bitmap
            Bitmap bitmap= null;
            if (bitmapCache != null) {
                bitmap = bitmapCache.get(request);
            }
            if (bitmap == null){
                //显示加载中图片
                showLoadingImg(request);
                //开始加载网络图,加载的逻辑不同加载器有所不同,所以交给各自
                //加载器实现,抽象
                bitmap = onLoad(request);
                if (bitmap == null){
                    //加载失败重试三次
                    while(integer.incrementAndGet() <=3){
                        bitmap = onLoad(request);
                        if (bitmap != null){
                            break;
                        }
                    }
                    integer.set(0);
                }
                if (bitmap == null){
                }
                //加入缓存
                if (bitmapCache != null && bitmap != null)
                    cacheBitmap(request,bitmap);
            }else{
                //有缓存
            }
            deliveryToUIThread(request,bitmap);
        }
    
        public abstract Bitmap onLoad(BitmapRequest request);
    
        protected void deliveryToUIThread(final BitmapRequest request, final Bitmap bitmap) {
            ImageView imageView = request.getImageView();
            if(imageView!=null) {
                imageView.post(new Runnable() {
                    @Override
                    public void run() {
                        updateImageView(request, bitmap);
                    }
    
                });
            }
    
        }
    
        private void updateImageView(final BitmapRequest request, final Bitmap bitmap) {
            ImageView imageView = request.getImageView();
            //加载正常  防止图片错位
            if(bitmap != null && imageView.getTag().equals(request.getImageUrl())){
                imageView.setImageBitmap(bitmap);
            }
            //有可能加载失败
            if(bitmap == null && displayConfig!=null&&displayConfig.errorImage!=-1){
                imageView.setImageResource(displayConfig.errorImage);
            }
            //监听
            //回调 给圆角图片  特殊图片进行扩展
            if(request.getImageListener() != null){
                request.getImageListener().onComplete(imageView, bitmap, request.getImageUrl());
            }
        }
        /**
         * 缓存图片
         * @param request
         * @param bitmap
         */
        private void cacheBitmap(BitmapRequest request, Bitmap bitmap) {
            if (request != null && bitmap != null){
                synchronized (AbstractLoader.class){
                    bitmapCache.put(request,bitmap);
                }
            }
        }
    
        /**
         * 显示加载中占位图,需要判断用户有没有配置
         * @param request
         */
        private void showLoadingImg(BitmapRequest request) {
            if (hasLoadingPlaceHolder()){
                final ImageView imageView = request.getImageView();
                if (imageView != null){
                    imageView.post(new Runnable() {
                        @Override
                        public void run() {
                            imageView.setImageResource(displayConfig.loadingImage);
                        }
                    });
                }
            }
        }
    
        /**
         * 是否设置了加载中图片
         * @return
         */
        private boolean hasLoadingPlaceHolder() {
            return displayConfig != null && displayConfig.loadingImage > 0;
        }
    }
    
    

    接下来是具体的加载器实现

    package com.rzm.commonlibrary.general.imageloader.loader;
    
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.text.TextUtils;
    
    import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
    import com.rzm.commonlibrary.general.imageloader.utils.BitmapDecoder;
    import com.rzm.commonlibrary.general.imageloader.utils.ImageViewHelper;
    
    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.MalformedURLException;
    import java.net.URL;
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:22
     * Description:This is UrlLoader
     * 网络图片加载器
     */
    public class UrlLoader extends AbstractLoader {
    
        @Override
        public Bitmap onLoad(BitmapRequest request) {
            try {
                String imageUrl = request.getImageUrl();
                if (TextUtils.isEmpty(imageUrl)){
                    return null;
                }
                URL url = new URL(imageUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                if (conn.getResponseCode() != 200){
                    return null;
                }
                InputStream inputStream = conn.getInputStream();
                //转化成BufferedInputStream
                final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                //标记一下,reset后会重置到这个位置
                bufferedInputStream.mark(inputStream.available());
                BitmapDecoder decoder = new BitmapDecoder() {
                    @Override
                    public Bitmap decodeBitmapWithOptions(BitmapFactory.Options options) {
                        //第一次读取,因为设置了inJustDecodeBounds为true,所以,这里decodeStream之后,会将宽高
                        //信息存储在options中;第二次读取,因为设置了inJustDecodeBounds为false.所以会将流全部读取
                        Bitmap bitmap = BitmapFactory.decodeStream(bufferedInputStream,null,options);
                       if (options.inJustDecodeBounds){
                           //表示时第一次执行,此时只是为了获取Bounds
                           try {
                               //第一次读取图片宽高信息,读完之后,要为第二次读取做准备,将流重置
                               bufferedInputStream.reset();
                           } catch (IOException e) {
                               e.printStackTrace();
                           }
                       }else{
                           try {
                               bufferedInputStream.close();
                           } catch (IOException e) {
                               e.printStackTrace();
                           }
                       }
                       return bitmap;
                    }
                };
                //传入控件的宽高,设置图片适应控件
                return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView()),
                        ImageViewHelper.getImageViewHeight(request.getImageView()));
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
            return null;
        }
    }
    
    
    package com.rzm.commonlibrary.general.imageloader.loader;
    
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.net.Uri;
    
    import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
    import com.rzm.commonlibrary.general.imageloader.utils.BitmapDecoder;
    import com.rzm.commonlibrary.general.imageloader.utils.ImageViewHelper;
    
    import java.io.File;
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:22
     * Description:This is LocalLoader
     * 本地图片加载器
     */
    public class LocalLoader extends AbstractLoader {
    
        @Override
        public Bitmap onLoad(BitmapRequest request) {
            //得到本地图片的路径
            final String path = Uri.parse(request.getImageUrl()).getPath();
            File file = new File(path);
            if (!file.exists() || !file.isFile()){
                return null;
            }
            BitmapDecoder decoder = new BitmapDecoder() {
                @Override
                public Bitmap decodeBitmapWithOptions(BitmapFactory.Options options) {
                    return BitmapFactory.decodeFile(path,options);
                }
            };
            return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView()),
                    ImageViewHelper.getImageViewHeight(request.getImageView()));
        }
    }
    
    
    package com.rzm.commonlibrary.general.imageloader.utils;
    
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    
    /**
     * 图片解码器
     */
    public abstract class BitmapDecoder {
    
        /**
         * 压缩图片
         * @param width imageView的宽度
         * @param height imageView的高度
         * @return
         */
        public Bitmap decodeBitmap(int width,int height){
            BitmapFactory.Options options = new BitmapFactory.Options();
            //设置为true 只读取图片的宽高,不需要将整个图片都加载到内存
            options.inJustDecodeBounds = true;
            decodeBitmapWithOptions(options);
            //经过上面一次操作,此时options中已经有了宽高信息
            calculateSampleSizeWithOptions(options,width,height);
            //第二次就可以得到缩放后的bitmap了
            return decodeBitmapWithOptions(options);
        }
    
        /**
         * 将图片宽高和控件宽高进行比较,得到缩放值,信息仍然存储在options中
         * @param options
         * @param viewWidth
         * @param viewHeight
         */
        private void calculateSampleSizeWithOptions(BitmapFactory.Options options,int viewWidth,int viewHeight) {
            //计算缩放比例
    
            //图片的原始宽高
            int width = options.outWidth;
            int height = options.outHeight;
    
            int inSampleSize = 1;
    
            //当图片的宽高大于控件的宽高时才需要压缩
            if (width > viewWidth || height > viewHeight){
                //计算出宽高的缩放比例
                int widthRatio = Math.round((float) width/(float)viewWidth);
                int heightRatio = Math.round((float)height/(float)viewHeight);
    
                //取宽高缩放比较大的值为图片的缩放比
                inSampleSize = Math.max(widthRatio,heightRatio);
            }
            //设置到options中,options保存的是配置信息
            //当inSampleSize为2,图片的宽高会缩放为原来的1/2
            options.inSampleSize = inSampleSize;
    
            //每个像素2个字节
            options.inPreferredConfig = Bitmap.Config.RGB_565;
    
            //宽高已经计算出来了,inJustDecodeBounds值可以复位了
            options.inJustDecodeBounds = false;
    
            //当系统内存不足时.可以回收bitmap
            options.inPurgeable = true;
            options.inInputShareable = true;
    
        }
    
        /**
         * 将流的处理通过抽象方法暴露出来,降低解码器和外部的耦合
         * @param options
         */
        public abstract Bitmap decodeBitmapWithOptions(BitmapFactory.Options options);
    }
    
    6.本地缓存
    package com.rzm.commonlibrary.general.imageloader.cache;
    
    import android.graphics.Bitmap;
    import android.util.LruCache;
    
    import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
    
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:20
     * Description:This is MemoryCache
     */
    public class MemoryCache implements BitmapCache{
    
        private LruCache<String,Bitmap> mLruCache;
    
        public MemoryCache(){
            int maxSize = (int) (Runtime.getRuntime().maxMemory()/1024/8);
            mLruCache = new LruCache<String,Bitmap>(maxSize){
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    return value.getRowBytes()*value.getHeight();
                }
            };
        }
        @Override
        public Bitmap get(BitmapRequest request) {
            if (mLruCache == null) return null;
            return mLruCache.get(request.getImageUrlMd5());
        }
    
        @Override
        public void put(BitmapRequest request, Bitmap bitmap) {
            if (mLruCache == null) return;
            mLruCache.put(request.getImageUrlMd5(),bitmap);
        }
    
        @Override
        public void remove(BitmapRequest request) {
            if (mLruCache == null) return;
            mLruCache.remove(request.getImageUrlMd5());
        }
    }
    
    
    7.硬盘缓存
    package com.rzm.commonlibrary.general.imageloader.disk;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    
    import com.rzm.commonlibrary.general.imageloader.cache.BitmapCache;
    import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
    import com.rzm.commonlibrary.general.imageloader.utils.IOUtil;
    
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    
    /**
     * Author:renzhenming
     * Time:2018/6/13 7:20
     * Description:This is DiskCache
     */
    public class DiskCache implements BitmapCache {
        private static volatile DiskCache mDiskCache;
        //缓存路径
        private String mCacheDir = "Image";
        //MB
        private static final int MB = 1024 * 1024;
        //jackwharton的杰作
        private DiskLruCache mDiskLruCache;
    
        private DiskCache(Context context)
        {
            iniDiskCache(context);
        }
        public static DiskCache getInstance(Context context) {
            if(mDiskCache==null)
            {
                synchronized (DiskCache.class)
                {
                    if(mDiskCache==null)
                    {
                        mDiskCache=new DiskCache(context);
                    }
                }
            }
            return mDiskCache;
        }
        private void iniDiskCache(Context context) {
            //得到缓存的目录  android/data/data/com.xxx/cache/Image
            File directory=getDiskCache(mCacheDir,context);
            if(!directory.exists())
            {
                directory.mkdirs();
            }
            try {
                //最后一个参数 指定缓存容量
                mDiskLruCache=DiskLruCache.open(directory,1,1,50*MB);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private File getDiskCache(String mCacheDir, Context context) {
            //默认缓存路径
            return new File(context.getCacheDir(),mCacheDir);
            //return new File(Environment.getExternalStorageDirectory(),mCacheDir);
        }
    
        @Override
        public void put(BitmapRequest request, Bitmap bitmap) {
            if (mDiskLruCache == null) return;
            DiskLruCache.Editor edtor=null;
            OutputStream os=null;
            try {
                //路径必须是合法字符
                edtor=mDiskLruCache.edit(request.getImageUrlMd5());
                os=edtor.newOutputStream(0);
                if(persistBitmap2Disk(bitmap,os))
                {
                    edtor.commit();
                }else {
                    edtor.abort();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private boolean persistBitmap2Disk(Bitmap bitmap, OutputStream os) {
            BufferedOutputStream bos=new BufferedOutputStream(os);
    
            bitmap.compress(Bitmap.CompressFormat.JPEG,100,bos);
            try {
                bos.flush();
            } catch (IOException e) {
                e.printStackTrace();
    
            }finally {
                IOUtil.closeQuietly(bos);
            }
            return true;
    
        }
    
        @Override
        public Bitmap get(BitmapRequest request) {
            if (mDiskLruCache == null) return null;
            try {
                DiskLruCache.Snapshot snapshot=mDiskLruCache.get(request.getImageUrlMd5());
                if(snapshot!=null)
                {
                    InputStream inputStream=snapshot.getInputStream(0);
                    return BitmapFactory.decodeStream(inputStream);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        @Override
        public void remove(BitmapRequest request) {
            if (mDiskLruCache == null) return;
            try {
                mDiskLruCache.remove(request.getImageUrlMd5());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    相关文章

      网友评论

          本文标题:16.手写图片加载框架ImageLoader

          本文链接:https://www.haomeiwen.com/subject/empdeftx.html