Android框架设计03-图片加载框架

本片文章介绍一个图片加载的框架。

框架优点:
支持高并发,支持不同的加载策略(加载图片的优先级),显示图片自适应(老生常谈的图片压缩),支持缓存策略扩展。

设计模式:
建造者模式,生产者消费者模式,单例模式,策略模式,模板方法模式

其他知识点
内存缓存LruCache,磁盘缓存DiskLruCache,下载时将请求任务转发

类图 (看不清可以右键,在新标签中打开)
这里写图片描述

流程分析
由于整个框架的代码比较多,所以没有把所有的代码都贴出来。分析框架主要看框架的思想,所以这里不会太拘泥于细节。对框架感兴趣的可以下载源码。由于整个类图看上去我从下手。所以我按照在UML里面的序号进行分析。这样不至于摸不着头绪。

1 客户端调用框架
客户端首先需要构建SimpleImageLoader对象

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageLoaderConfig.Builder builder = new ImageLoaderConfig.Builder();

        builder.setThreadCount(3)//线程数量
                .setLoadPolicy(new ReversePolicy())//加载策略
                .setCachePolicy(new DoubleCache())//缓存策略
                .setLoadingImage(R.drawable.loading) //加载中的图片
                .setFaildImage(R.drawable.not_found);//加载失败显示的图片

        ImageLoaderConfig config = builder.build();
        imageLoader = SimpleImageLoader.getInstance(config);


        inflater = LayoutInflater.from(this);
        GridView gridView = (GridView) findViewById(R.id.gv);
        gridView.setAdapter(new MyAdapter());

    }

通过上面的代码可以看出,首先要构建一个ImageLoaderConfig对象。然后用此对象来构建SimpleImageLoader对象。
然后通过imageLoader对象的disPlayImage()方法来加载图片。这就是客户端(MainActivity)的调用代码。

imageLoader.disPlayImage(viewHolder.imageView, imagebUrl);

可以看出此方法加载图片的这种方式和Glide框架类似。只是传一个ImageView对象和Url.需要说明的是,上面在构建ImageLoaderConfig对象时有关的缓存策略,加载策略线程数都是有默认值的。所以也可以不做设置。如在ImageLoaderConfig中:

    //缓存策略
    private BitmapCache bitmapCache = new MemoryCache();

    //加载策略
    private LoadPolicy loadPolicy = new ReversePolicy();

    //默认线程数
    private int threadCount = Runtime.getRuntime().availableProcessors();

2 通过ImageLoaderConfig来构建SImpleImageLoader对象
简单来说,SimpleImageLoader是负责图片加载的代理。而ImageLoaderConfig是封装有关图片请求需要的参数。所以构建SimpleImageLoader要先构建一个ImageLoaderConfig对象。
在1中也有体现。

ImageLoaderConfig.Builder builder = new ImageLoaderConfig.Builder();

        builder.setThreadCount(3)//线程数量
                .setLoadPolicy(new ReversePolicy())//加载策略
                .setCachePolicy(new DoubleCache())//缓存策略
                .setLoadingImage(R.drawable.loading) //加载中的图片
                .setFaildImage(R.drawable.not_found);//加载失败显示的图片

        //构建 ImageLoaderConfig 
        ImageLoaderConfig config = builder.build();
        //构建 SimpleImageLoader
        imageLoader = SimpleImageLoader.getInstance(config);

3,4,5构建ImageLoaderConfig所需的三个参数
3 DisPlayConfig 封装了加载中和加载失败的图片id。
4 BitmapCache 封装了图片缓存的策略。如内存缓存和SD卡缓存
5 loadPolicy加载策略。加载时是可以选择顺序加载和倒叙加载。但是此功能没有实现。可以在扩展时添加。

6 维护阻塞队列的RequestQueue

这个类有什么作用?
在RequestQueue中维护了一个线程阻塞队列。

     /**
     * 阻塞式线程
     * 多线程共同轮训此队列
     * 优先级高的队列先被消费
     * 都一个产品都有编号
     */
    private BlockingQueue<BitmapRequest> requestQueue = new PriorityBlockingQueue<>();

通过在requestQueue中添加BitmapRequest和去取出BitmapRequest来实现任务的生产和消费。这里用了生产者消费者模式。
什么时候添加任务?
在MainActivity中

imageLoader.disPlayImage(viewHolder.imageView, imageThumbUrls[position]);

在SimpleImageLoader的disPlayImage()方法中

 requestQueue.addRequest(bitmapRequest);   

在RequestQueue中

public void addRequest(BitmapRequest bitmapRequest){
        if(!requestQueue.contains(bitmapRequest)){
            bitmapRequest.setSeriaNo(atomicInteger.incrementAndGet());
            requestQueue.add(bitmapRequest);
        }else{
            Log.i("","请求已经存在 编号:"+bitmapRequest.getSeriaNo());
        }

    }

此时一个任务已经添加到阻塞队列中
什么时候轮询任务队列里的任务?
在SimpleImageLoader中的构造方法时,将调用RequestQueue的start()方法。

private SimpleImageLoader(ImageLoaderConfig config) {
        this.imageLoaderConfig = config;
        this.requestQueue = new RequestQueue(imageLoaderConfig.getThreadCount());
        requestQueue.start();
    }

在RequestQueue中

public void start(){
        stop();
        startDisPatcher();
    }
 private void startDisPatcher() {
        dispatchers = new RequestDispatcher[threadCount];
        for(int i=0;i<dispatchers.length;i++){
            //1
            RequestDispatcher requestDispatcher = new RequestDispatcher(requestQueue);
            dispatchers[i] = requestDispatcher;
            //2
            dispatchers[i].start();
        }
    }

1 通过startDisPatcher()方法,把RequestQueue中维护的队列传到RequestDispatcher 中

RequestDispatcher requestDispatcher = new RequestDispatcher(requestQueue);

2然后通过RequestDispatcher 中的start()方法去轮询传来的队列。
由于RequestDispatcher 继承自Thread,所以run()方法将会执行

@Override
    public void run() {
        while (!isInterrupted()) {
            try {
                //阻塞式队列  取出任务
                BitmapRequest bitmapRequest = requestBlockingQueue.take();
                String schema = parseSchema(bitmapRequest.getImageUrl());
                Loader loader = LoaderManager.getInstance().getLoader(schema);
                loader.loadImage(bitmapRequest);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

至此RequestQueue维护的队列的任务已经被取出。
用图来表示
这里写图片描述
可以看出有多个RequestDispatcher 共同来轮询取出RequestQueue维护的队列中的任务。RequestDispatcher 的个数等于在ImageLoaderConfig中设置的个数。

 dispatchers = new RequestDispatcher[threadCount];

由于任务队列是线程阻塞的,所以当任务队列中的BitmapRequest对象个数为0时,线程将会阻塞。直到新任务添加进队列。

7 RequestDispatcher 轮询队列
这一过程在6中已经说明了。简单来说就是RequestDispatcher 做为一个线程对象。在RequestQueue中的startDispatcer()中开启这些线程。然后这些线程开始共同轮询这个队列。不断的在队列中取出任务并执行。

8 通过LooperManager()来判断使用哪种加载 方式
在RequestDispatcher 的run()方法中

 @Override
    public void run() {
        while (!isInterrupted()) {
            try {
                //1 取出任务
                BitmapRequest bitmapRequest = requestBlockingQueue.take();
                //2 判断开头是http开头还是file开头
                String schema = parseSchema(bitmapRequest.getImageUrl());
                //3 根据 schema 来获得具体的请实例
                Loader loader = LoaderManager.getInstance().getLoader(schema);
                //4 加载器开始执行加载任务
                loader.loadImage(bitmapRequest);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

做了四件事。1 先是取出任务。2 然后根据开头来判断该用哪种请求方式。

  //http://www.baidu.com------->http
    private String parseSchema(String imageUrl) {
        if (imageUrl.contains("://")) {
            return imageUrl.split("://")[0];
        } else {
            Log.i("RequestDispatcher", "不支持此类型");
        }
        return null;
    }

3获得LooperManager的实例,并根据schema来获得Loader的请求实例。
在LooperManager的构造方法中初始化了这样一个map

private LoaderManager() {
        regist("http", new UrlLoader());
        regist("https", new UrlLoader());
        regist("file", new LocalLoader());
    }

用map来维护把网络请求和本地请求对应的加载器。然后通过getLoader()来获得具体的请求方式。

public Loader getLoader(String schema) {
        if (mLoaderMap.containsKey(schema)) {
            return mLoaderMap.get(schema);
        }
        return new NullLoader();
    }

4 执行加载,调用loader的loadImage()方法。

loader.loadImage(bitmapRequest);

9,10通过网络请求和本地读取来加载Bitmap
UrlLoader和LocalLoader都是继承自AbstractLoader的,而AbstractLoader实现了Loader的接口。所以在8中的4步骤中调用loader的 loadImage()方法时,将会执行AbstractLoader的loadImage()方法。

@Override
    public void loadImage(BitmapRequest bitmapRequest) {
        //1 从缓存中读取
        Bitmap bitmap = bitmapCache.get(bitmapRequest);
        if (bitmap == null) {
            //2 显示加载中的图片
            showLoadingImage(bitmapRequest);

            //3 真正开始加载
            bitmap = onLoad(bitmapRequest);

            //4 缓存图片
            cacheBitmap(bitmapRequest, bitmap);
        }
        //5 切换到主线程显示图片
        deliveryToUIThread(bitmapRequest,bitmap);
    }

无论是网络加载UrlLoader还是本地加载LocalLoader都要执行这五个步骤。不同的是加载方式不同如步骤3调用了onLoad()方法。

 //抽象加载策略 交给本地加载和网络加载去处理
    protected abstract Bitmap onLoad(BitmapRequest bitmapRequest);

本地加载
在本地加载的onLoad()方法中

@Override
    protected Bitmap onLoad(BitmapRequest bitmapRequest) {
        //1 获得本地图片的路径
        final String path = Uri.parse(bitmapRequest.getImageUrl()).getPath();
        File file = new File(path);
        if (!file.exists()) {
            return null;
        }

        BitmapDecoder bitmapDecoder = new BitmapDecoder() {
            @Override
            public Bitmap decodeBitmapWithOptions(BitmapFactory.Options options) {
                //2 根据文件文件解码出bitmap
                return BitmapFactory.decodeFile(path, options);
            }
        };
        //3 通过ImageView的宽和高和之前的图片路径来解码出bitmap对象。
        return bitmapDecoder.decodeBitmap(ImageViewHelper.getImageViewWidth(bitmapRequest.getImageView()),
                ImageViewHelper.getImageViewHeight(bitmapRequest.getImageView()));
    }

做了三步。
1 不用多说,只是获得本地图片的路径
2 根据图片文件解码处bitmap。在此步骤中直接new了一个匿名抽象类。并实现了其抽象方法。此方法是为了在步骤3中调用的。所以还是要先看3.
3 压缩图片 解码bitmap对象

public Bitmap decodeBitmap(int reqWidth, int reqHeight) {
        //a 初始化options
        BitmapFactory.Options options = new BitmapFactory.Options();

        //b 仅加载边界属性
        options.inJustDecodeBounds = true;

        //c 解码出bitmap
        decodeBitmapWithOptions(options);

        //d 计算压缩比例
        caculateSampleSizeWithOptions(options, reqWidth, reqHeight);
        return decodeBitmapWithOptions(options);
    }

在步骤3中的ImageLoaderHelper是一个用来获得ImageView的宽高的辅助类。当ImageView没有明确具体宽高时。做了相应的处理。获取到ImageView的宽高后。在根据宽高来来计算出options的inSampleSize。再通过inSampleSize来压缩图片。最后获得bitmap对象。

网络加载
和网络加载和UrlLoader加载图片的过程不同。但是功能相同。因为本文主要是说明架构的思想。所以不再分析网络加载部分。至此整个流程已经结束。

下载源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值