[转]教你写Android ImageLoader框架之基本架构(一)

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

前言

在Android开发中,ImageLoader应该算得上是最重要的开源库之一,由于项目原因(不能使用开源库),前段时间自己也是需要实现一个简单的ImageLoader,因此诞生了这个库,我们暂且叫它为SimpleImageLoader。就目前而言,你上网查ImageLoader资料的时候,基本上能够找到很简单的实现,基本上一个类就把所有的工作给做了,这就显得很不专业了嘛,很多时候我们不只是需要实现功能,而是希望能够在实现功能的同时在设计层面有所提升。

SimpleImageLoader分享出来的主要目的并不是说替代那些著名开源库,而是提供一个简单的、又有一定参考价值的ImageLoader实现让一些需要帮助的人学习,在深入了解实现的同时学到知识,也能够体会到在设计一个开源库时应该要做哪些考虑、做哪些取舍、有什么模式,当然在了解了ImageLoader的实现之后再去使用专业的开源库也会更加的得心应手,出现问题的时候自己也能够不太费力地去究其原因。在提升自己的同时也能够了解一些开源库的设计基本原则,这也是我的博客中一直主张的观点。

当然限于本人水平有限,有bug在所难免,但是我们这里是以学习为目的,了解ImageLoader的设计与实现才是我们最重要的目的,一些细节不必在意,可以在你深入的学习过程中修改你认为不合理或者错误的地方。如果你有好的实现方法或者有好的建议,也都请指教一二;如果你认为我写的东西烂得不值一提,那你就深深地埋藏在心里吧。


基本架构

一般来说,ImageLoader的实现都是基于线程池,在第一版的我也是使用线程池来加载图片,但是后面的版本却换成了跟SimpleNet类似的架构模式,原因是觉得线程池刚启动的时候会稍微慢一些,我感觉不太爽就换了线程模型。当然我也会把线程池的版本在另外的分支上给出,这样给一些需要的朋友参考。
SimpleImageLoader的基本结构如图1所示。

图1

看到这幅图看过SimpleNet网络框架的朋友应该是会熟悉一些,基本结构与SimpleNet很相似,其实主要也是我们比较喜欢把架构图画成这种分层样式,感觉比较好理解,

SimpleImageLoader类是用户的入口,用户在通过配置类初始化SimpleImageLoader之后就可以向SimpleImageLoader提交加载图片的请求了。SimpleImageLoader内部维护了一个请求队列,用户提交的加载图片的请求会在内部被封装成BitmapRequest对象,然后将这些对象放到请求队列中。在创建队列时会创建用户指定数量(默认为CPU个数 + 1)的线程来加载图片,这些线程在内部命名为RequestDispatcher,它们在run函数中不断地获取队列中的加载请求,然后交给对应的Loader加载图片。

为了方便用户的扩展,我们引入了Loader这个抽象,因为在SimpleImageLoader中只支持两种图片uri的加载,即网络图片uri和本地文件的uri。网络图片一般以"http:// "或者"https:// "开头,而本地图片的uri格式却是"file://"开头,SimpleImageLoader内部通过图片uri的格式的不同使用不同的Loader来加载图片,这样后续用户就可以注册Loader来实现其他格式的加载,例如"drawable:// + 图片名"来加载res/drawable中的图片等。这样保证了SimpleImageLoader可加载图片uri格式的可扩展性。Loader会通过LoaderManager来进行管理,如果需要注册自己的Loader实现,则调用LoaderManager的register函数即可。如果你传递进去的图片uri是无效,例如格式错误,那么LoaderManager会返回一个默认的Loader,这个默认的Loader名为NullLoader,它其实什么也不做,只是为了防止在外部进行判空而已,这种模式成为Null Object设计模式。当然,在加载图片之前我们会从缓存中读取,如果有缓存我们则不加载。

Loader加载完图片之后会先更新UI,即将图片显示到对应的ImageView上,在构造BitmapRequest时内部已经将图片的uri设置为ImageView的tag了。图片加载完成后判断ImageView的tag和uri是否相等,如果相等则将图片显示到ImageView上,否则不更新ImageView。这一步很重要,很多朋友在使用ImageLoader时出现问题基本上就是由于没有设置ImageView的tag。

加载图片的先后顺序是由加载策略决定的,策略相关的内容没有在架构图中给出。加载策略决定了请求在队列中的排序,在将请求添加到队列中时会给每个请求设置一个序号,队列将根据这个序号对请求进行排序。这样我们就可以知道哪个请求是先添加进来的,也可以很方便的实现自己的策略类来定制自己的加载策略,比如最后加载到队列中的请求最先加载。比如我们在ListView滚动时,最后添加到队列中的图片请求应该是我们最急需显示的,我们它们就在手机的当前屏幕,而前面的请求对应的ImageView已经被复用,即使它们加载完成它们也不会被显示,因为ImageView的tag已经变化了。因此,策略的灵活性依然很重要。

在加载完图片并且更新UI之后,我们会将图片缓存起来。内置的缓存类型有四种,无缓存、内存缓存、sd卡缓存、内存和sd卡的双缓存,这四种缓存都实现了Cache接口,如果你这四种缓存类型还不能满足你的需求,那么你可以实现Cache接口,然后实现自己的缓存逻辑,然后在配置ImageLoader时设置需要的缓存类型(具体配置后续说明),如果不配置则默认使用的是内存缓存。这里我们又看到了一个面向接口编程的例子,即SimpleImageLoader只依赖于Cache接口的抽象,而不是说依赖于某个具体的缓存类,这样用户就可以很方面的实现自己的缓存逻辑,并且将缓存实现注入到sdk中。当然,上述的Loader、加载策略实现也是基于同样的理论基础,就是说过很多遍的“面向接口编程”。

恩,是时候捋一捋这个执行流程了。

用户调用displayImage请求加载图片,SimpleImageLoader将这个加载图片请求封装成一个Request,然后加入到队列中。几个色眯眯的调度子线程不断地从队列中获取请求,然后根据uri的格式获取到对应的Loader来加载图片。在加载图片之前首先会查看缓存中是否含有目标图片(具体细节在后续的博客再细说),如果有缓存则使用缓存,否则加载目标图片。获取到图片之后,我们会将图片投递给ImageView进行更新,如果该ImageView的tag与图片的uri是一样的,那么则更新ImageView,否则不处理。
使用ImageView的tag与图片的uri进行对比是为了防止图片错位显示的问题,这在ImageLoader中是很重要的一步。
如果目标图片没有缓存,第一次从uri中加载后会加入缓存中,当然从sdcard中加载的图片我们只会缓存到内存中,而不会再缓存一份到sd卡的另一个目录中。这样,整个加载过程也就完成了。


SimpleImageLoader工程结构图


效果图

上述效果中有四张图片是显示不出来的,因为在图片uri列表中有三张是我手机中的图片,模拟器中没有,因此显示加载失败。还有一张是无效的uri,也是加载失败。
GitHub

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

推荐阅读更多精彩内容