[转]教你写Android ImageLoader框架之图片加载与加载策略(三)

本文转自Mr.Simple的博客,如侵删

前言

教你写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("", "### 请求分发器退出");
       }
   }

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

public interface Loader {
     public void loadImage(BitmapRequest result);
}

抽象是为了可扩展,定义这个接口,我们就可以注入自己的图片加载实现类。例如从资源、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);
    // 代码省略
}

代码逻辑如上所述实现了一个模板函数,变化的部分就是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;
    }

}

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

private LoaderManager() {
    register(HTTP, new UrlLoader());
    register(HTTPS, new UrlLoader());
    register(FILE, new LocalLoader());
}

加载策略

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

public interface LoadPolicy {
    public int compare(BitmapRequest request1, BitmapRequest request2);
}

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

@Override
 public int compareTo(BitmapRequest another) {
     return mLoadPolicy.compare(this, another);
 }

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

/**
 * 顺序加载策略
 * 
 * @author mrsimple
 */
public class SerialPolicy implements LoadPolicy {

    @Override
    public int compare(BitmapRequest request1, BitmapRequest request2) {
        // 那么按照添加到队列的序列号顺序来执行
        return request1.serialNum - request2.serialNum;
    }

}

逆序加载则为 :

/**
 * 逆序加载策略,即从最后加入队列的请求进行加载
 * 
 * @author mrsimple
 */
public class ReversePolicy implements LoadPolicy {

    @Override
    public int compare(BitmapRequest request1, BitmapRequest request2) {
        // 注意Bitmap请求要先执行最晚加入队列的请求,ImageLoader的策略
        return request2.serialNum - request1.serialNum;
    }
}

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


总结

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容