开发者社区> 黑夜路口> 正文

16.手写图片加载框架ImageLoader

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

第三方开源的图片框架很多,这里自己去写一个的目的是通过这样一个写的过程,拓展自己对架构设计的理解,包括设计模式,线程,策略,缓存等等。另外大型的框架例如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.图片下载时请求转发

img_f1fc6c053236a29dbfbe643a3dc00c74.png
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();
        }
    }
}

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
C# 使用FileUpload控件上传图片,将文件转换成二进制进行存储与读取
状况描述:   需要上传文件,但是不想要保存到实体路径下,便可以用该功能来实现。   效果图:      点击【Upload】按钮,上传文件到数据库;   点击【Preview】,预览文件;   具体实现:   前台: 1 2 3 4 : 5 ...
1769 0
Vue回炉重造之图片加载性能优化
图片加载优化对于一个网站性能好坏起着至关重要的作用。所以我们使用Vue来操作一波。
15 0
CV:基于Keras利用cv2+自定义(加载人脸识别xml文件)+keras的load_model(加载表情hdf5、性别hdf5)实现标注脸部表情和性别label
CV:基于Keras利用cv2+自定义(加载人脸识别xml文件)+keras的load_model(加载表情hdf5、性别hdf5)实现标注脸部表情和性别label
117 0
Chrome 图片解码与 Image Decoding Hint
我在之前的一篇文章[Chrome 图片解码与 Image.decode API][1],说明了为什么图片解码可能会导致非合成器动画的阻塞和如何使用 Image.decode API 来避免动画的阻塞。不过虽然 Image.decode API 给页端提供了更灵活的控制图片解码时机的能力,但是使用起来较为复杂,也容易误用,而 Image Decoding Hint 属性则更简单,容易使用,同时也满足
2914 0
使用代理模式改善SAP UI5应用的图片加载体验
使用代理模式改善SAP UI5应用的图片加载体验
38 0
使用webpack loader加载器
了解webpack请移步webpack初识! 什么是loader loaders 用于转换应用程序的资源文件,他们是运行在nodejs下的函数 使用参数来获取一个资源的来源并且返回一个新的来源(资源的位置),例如:你可以使用loader来告诉webpack去加载一个coffeescript或者j...
807 0
[Android]ListView的Adapter.getView()方法中延迟加载图片的优化
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4139998.html   举个例子吧,以好友列表为例 ListView中每个Item表示一个好友,每个好友中都有一个头像,需要从服务端加载到本地,然后显示在item中。
777 0
+关注
黑夜路口
安卓高级工程师,目前任职于Wifi万能钥匙
152
文章
1
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载