教你写Android ImageLoader框架之图片加载与加载策略

                       

教你写Android ImageLoader框架之初始配置与请求调度中,我们已经讲述了ImageLoader的请求配置与调度相关的设计与实现。今天我们就来深入了解图片的具体加载过程以及加载的策略(包括按顺序加载和逆序加载) ,在这其中我会分享我的一些设计决策,也欢迎大家给我提建议。     

图片的加载

Loader与LoaderManager的实现

在上一篇文章教你写Android ImageLoader框架之初始配置与请求调度中,我们聊到了Loader与LoaderManager。    ImageLoader不断地从队列中获取请求,然后解析到图片uri的schema,从schema的格式就可以知道它是存储在哪里的图片。例如网络图片对象的schema是http或者https,sd卡存储的图片对应的schema为file,schemae与Loader有一个对应关系。根据schema我们从LoaderManager中获取对应的Loader来加载图片。这个设计保证了SimpleImageLoader可加载图片类型的可扩展性,这就是为什么会增加loader这个包的原因。用户只需要根据uri的格式来构造图片uri,并且实现自己的Loader类,然后将Loader对象注入到LoaderManager即可。RequestDispatcher中的run函数如下 :   

 @Override    public void run() {        try {            while (!this.isInterrupted()) {                final BitmapRequest request = mRequestQueue.take();                if (request.isCancel) {                    continue;                }                final String schema = parseSchema(request.imageUri);                // 根据schema获取loader                Loader imageLoader = LoaderManager.getInstance().getLoader(schema);                imageLoader.loadImage(request);            }        } catch (InterruptedException e) {            Log.i("", "### 请求分发器退出");        }    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Loader只定义了一个接口,只用一个加载图片的方法。 

public interface Loader {    public void loadImage(BitmapRequest result);}
  
  
  • 1
  • 2
  • 3

抽象是为了可扩展,定义这个接口,我们就可以注入自己的图片加载实现类。例如从资源、assets中加载。不管从网络还是本地加载图片,我们加载图片的过程有如下几个步骤:   

  1. 判断缓存中是否含有该图片;
  2. 如果有则将图片直接投递到UI线程,并且更新UI;
  3. 如果没有缓存,则从对应的地方获取到图片,并且将图片缓存起来,然后再将结果投递给UI线程,更新UI;

我们可以发现,不管从哪里加载图片,这些逻辑都是通用的,因此我抽象了一个AbsLoader类。它将这几个过程抽象起来,只将变化的部分交给子类处理,就相当于AbsLoader封装了一个逻辑框架( 可以思考用了什么设计模式),大致代码如下 :

/** * @author mrsimple */public abstract class AbsLoader implements Loader {    /**     * 图片缓存     */    private static BitmapCache mCache = SimpleImageLoader.getInstance().getConfig().bitmapCache;    @Override    public final void loadImage(BitmapRequest request) {        // 1、从缓存中获取        Bitmap resultBitmap = mCache.get(request);        Log.e("", "### 是否有缓存 : " + resultBitmap + ", uri = " + request.imageUri);        if (resultBitmap == null) {            showLoading(request);            // 2、没有缓存,调用onLoaderImage加载图片            resultBitmap = onLoadImage(request);            // 3、缓存图片            cacheBitmap(request, resultBitmap);        } else {            request.justCacheInMem = true;        }        // 4、将结果投递到UI线程        deliveryToUIThread(request, resultBitmap);    }    /** 加载图片的hook方法,留给子类处理     * @param request     * @return     */    protected abstract Bitmap onLoadImage(BitmapRequest request);    // 代码省略    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

代码逻辑如上所述实现了一个模板函数,变化的部分就是onLoadImage,子类在这里实现真正的加载图片的方法。比如从网络上加载图片。

/** * @author mrsimple */public class UrlLoader extends AbsLoader {    @Override    public Bitmap onLoadImage(BitmapRequest request) {        final String imageUrl = request.imageUri;        FileOutputStream fos = null;        InputStream is = null;        try {            URL url = new URL(imageUrl);            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();            is = new BufferedInputStream(conn.getInputStream());            is.mark(is.available());            final InputStream inputStream = is;            BitmapDecoder bitmapDecoder = new BitmapDecoder() {                @Override                public Bitmap decodeBitmapWithOption(Options options) {                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);                    //                    if (options.inJustDecodeBounds) {                        try {                            inputStream.reset();                        } catch (IOException e) {                            e.printStackTrace();                        }                    } else {                        // 关闭流                        conn.disconnect();                    }                    return bitmap;                }            };            return bitmapDecoder.decodeBitmap(request.getImageViewWidth(),                    request.getImageViewHeight());        } catch (Exception e) {        } finally {            IOUtil.closeQuietly(is);            IOUtil.closeQuietly(fos);        }        return null;    }}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

在初始化ImageLoader时我们会默认将几个Loader注入到LoaderManager中,然后在加载图片时ImageLoader会根据图片的schema来获取对应Loader来完成加载功能。

    /**     *      */    private LoaderManager() {        register(HTTP, new UrlLoader());        register(HTTPS, new UrlLoader());        register(FILE, new LocalLoader());    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

加载策略

加载策略就是你的图片加载请求提交以后ImageLoader按照一个什么规则来加载你的请求。默认就是SerialPolicy策略(FIFO),谁在队列前面就是谁优先被执行。但是事情往往没有那么简单,我们在ListView滚动时,我们希望最后添加到请求队列的图片优先得了加载,因此此时它们就在手机屏幕上,所以我们又添加了一个ReversePolicy策略。咦,对于这种存在各种可能性的部分,我们最不能具体化,还是要抽象!于是我定义了LoadPolicy接口,它的作用是compare两个请求,以此来规定排序原则。

public interface LoadPolicy {    public int compare(BitmapRequest request1, BitmapRequest request2);}
  
  
  • 1
  • 2
  • 3

因为我们的请求队列使用的是优先级队列PriorityBlockingQueue,因此我们的BitmapRequest都实现了 Comparable 接口,我们在BitmapRequest的函数中将compareTo委托给LoadPolicy对象的compare。

    @Override    public int compareTo(BitmapRequest another) {        return mLoadPolicy.compare(this, another);    }
  
  
  • 1
  • 2
  • 3
  • 4

我们看看默认的加载策略,即按顺序加载,先添加到队列的请求先被执行。

/** * 顺序加载策略 *  * @author mrsimple */public class SerialPolicy implements LoadPolicy {    @Override    public int compare(BitmapRequest request1, BitmapRequest request2) {        // 那么按照添加到队列的序列号顺序来执行        return request1.serialNum - request2.serialNum;    }}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

逆序加载则为 :

/** * 逆序加载策略,即从最后加入队列的请求进行加载 *  * @author mrsimple */public class ReversePolicy implements LoadPolicy {    @Override    public int compare(BitmapRequest request1, BitmapRequest request2) {        // 注意Bitmap请求要先执行最晚加入队列的请求,ImageLoader的策略        return request2.serialNum - request1.serialNum;    }}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

呵,想想这不是策略模式么!原来模式无处不在,当你习惯之后你就会发现模式在无形之中已经运用到你的代码了。如上所示,策略都是简单的实现,这个策略只需要在配置ImageLoader时指定就行了,用户也可以根据自己的需求来实现策略类,并且注入给ImageLoader。这样就保证了灵活性、可扩展性。

总结

通过Loader和LoaderManager保证了可加载图片来源的扩展性,即图片可以存储在网络上、sd卡中、res文件夹中等等,实现一个从特定位置加载图片的Loader,然后给这个Loader注册一个schema,在加载图片的时候根据图片的路径获取schema,再通过schema获取Loader,通过Loader加载图片。      

而图片的加载策略又通过LoadPolicy这个抽象来定制,用户可以自行实现加载策略。这样就保证了灵活性,当然还有后期的图片缓存也是需要同样的灵活性。和我在公共技术点之面向对象六大原则所说,面向对象的几大原则最终化为几个简单的关键字: : 抽象、单一职责、最小化。领悟到了这些思想,我想你的代码质量应该会有一个质的提升。    

ImageLoader库,图片缓存肯定必不可少。关于图片的缓存设计,还是那句老话,待我下回讲解~

Github地址

SimpleImageLoader

           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值