Android图片加载问题分析

下图是一个客户端图片加载模块常见的处理流程。

imagepipeline.png

本文以UniversalImageLoader为例分析了这一流程,然后分析了Fresco的优势和问题,最终推荐大家使用Glide。

从UniversalImageLoader分析图片加载中需要处理的问题

网络

主要用于下载网络图片,在UIL中是将图片地址变为InputStream。UIL支持多种类型来源的图片显示,包括:

  • 网络
  • 文件
  • Uri资源(如果是视频,会找到缩略图显示)
  • assets
  • drawable

缓存

  • UIL使用两级缓存:磁盘缓存图片文件、内存缓存Bitmap
  • UIL已经实现了多种缓存策略,但一般都使用LRU缓存
  • UIL可以指定图片缓存的路径,和缓存文件名的生成规则
  • 需要自己确定缓存的大小,确定内存缓存的大小尤其重要
    • 通过ActivityManager#getMemoryClassRuntime.getRuntime().maxMemory()获得单个应用的最大内存(前者单位为MB,后者单位为B),一般最多划分1/4的最大内存,否则容易导致OOM。
    • 图片较多,大图较多的应用,需要使用较大的缓存,提高缓存的命中率
    • 内存也不宜太小,最少应该能缓存2~3个屏幕大小的Bitmap

解码

  • 要按需解码,否则会造成内存的浪费,主要通过options.inJustDecodeBounds进行预解析
  • ImageAware可以帮助控制解码图片的大小,ImageViewAware就是对ImageView的一个封装
  • 注意照片方向Exif,避免图片错误旋转

显示

实现接口BitmapDisplayer可以自定义显示效果,已实现的包括:带描边的圆形、渐入、圆角矩形(不能四个角分别指定)等。

某些设计可能会出现两个角圆角、另外两个直角的特殊裁剪模式。自己实现这类Displayer时,不要生成一个新的Bitmap,定义一个Drawable会更高效。因为生成新的Bitmap会引起内存分配和回收,从而使GC更加频繁,而Drawable只是在绘制时会使用很少的计算资源。可以参考源码中RoundedBitmapDisplayer。

多线程

图片的下载和解码都需要再后台线程中处理,而且为了提高效率,一般都使用多个线程分别进行解码和网络请求。

共有三个Executor,分别用于

  • 分发任务
  • 处理已缓存图片
  • 处理未缓存图片

已缓存的图片主要占用计算资源,未缓存的图片则主要占用网络资源,所以不应该在一个Executor中竞争。可以分别指定Executor的线程数量,UIL默认为3个。

监听

UIL向外提供了两类监听:ImageLoadingListener和ImageLoadingProgressListener

PauseOnScrollListener主要用于,在列表滚动时暂停图片加载,但在现在的RecyclerView中无法使用。需要自己使用ImageLoader#pauseImageLoader#resume

了解了UIL是如何处理图片加载的问题之后,其他的第三方库也都是大同小异,下面再介绍下Fresco和Glide。

Fresco的优势和问题

Fresco在解决图片加载问题上的思路和其他框架有很大的不同。它最大的问题有两个:

  • 不能直接使用ImageView:这个问题对于老项目的重构几乎是致命的。
  • 需要指定宽高:指定宽高对于图片加载其实是一件好事,但在加载网络图片的时候需要服务端告知原图的尺寸,才能帮助客户端实现更好的体验。

相比UIL,它的优点主要包括:

  • 5.0以下的系统上,使用ASHMEM,不会占用Java堆内容
  • 多一级未解码图片的内存缓存,减少文件IO(很多Android机器使用1年之后变慢,很大的原因就是IO变慢了,所以这一优化的效果还是很显著的)
  • 支持多图请求,可以在大图显示之前展现缩略图
  • 支持Gif动画和渐进式JPEG(图片还未下载完成的时候就可以先显示一部分)

更多关于Fresco的特点可以参考Android图片加载开源库深度推荐,安利Fresco

缓存和网络:Image Pipeline

在5.0系统以下,Image Pipeline 使用 pinned purgeables 将Bitmap数据避开Java堆内存,存在ASHMEM中。这要求图片不使用时,要显式地释放内存。SimpleDraweeView自动处理了这个释放过程,所以没有特殊情况,尽量使用SimpleDraweeView。

显示

修改图片的显示效果需要使用DraweeHolder,它不仅能控制图片的显示,还可以处理View的Touch事件。默认支持圆角和圆形。

另一个控制图片显示的方法是在Postprocessor中修改Bitmap,但效率很低。

监听

  • ControllerListener:监听图片显示的过程
  • RequestListener:监听图片获取的过程

Glide是个不错的选择

优点

  • 支持本地Video
  • 分别控制每次请求的优先级
  • 支持缩略图
  • 可直接更新AppWidget和Notification中的图片
  • 自定义图片转换效果,还可使用GPU转换(使用GPU处理图片变换,并保持到缓存文件中,在Demo中可以看到GPU变换的10种特效)
  • 定义加载动画
  • 很方便的使用图片裁剪服务
  • 使用Bitmap回收池,减少系统gc

可参考Glide相关文章了解怎么通过自定义的GlideModule优化加载的图片。本文也提供给了参考代码,在代码中引入七牛裁图服务,同时也可体验10种GPU变化的特效。

关于Transformation和BitmapImageViewTarget的使用

  • Transformation#transform:在缓存前对图片进行处理,处理之后的图片才会进行缓存。处理图片的过程中会产生额外的内存消耗,处理后的图片会占据独立的缓存空间。但第二次使用的时候,不再需要处理,直接从缓存中读取。
  • BitmapImageViewTarget#setResource:控制图片的显示逻辑,每次显示的时候都会处理。因此在setResource中应该定义特殊的Drawable来控制显示效果,而不应该对Bitmap进行处理。(Bitmap的频繁生成和回收会导致gc;Drawable是在绘制的时候,通过Paint设置特殊的绘制效果,不会产生新的Bitmap)

关于Bitmap的回收机制

系统的Bitmap内存管理机制随着Android系统的演进可以分为三个阶段,关于这部分可以参考官网文章Managing Bitmap Memory

  • 2.3.3及以下:Bitmap的像素数据存储在native内存中,但依旧会计算在一个进程的内存上限之中。
  • 3.0~4.4:Bitmap的像素数据存储在Java堆内存中,解码Bitmap时可以通过Options#inBitmap复用不再使用的Bitmap,从而减少系统gc。但要求被复用的Bitmap和新Bitmap的像素数据一样大。
  • 5.0及以上:对于复用Bitmap的限制不再严格要求一样大,只要别复用的Bitmap的像素数据不小于新Bitmap即可。

如有兴趣,可参考笔者的另一篇文章了解《Glide如何通过引用计数复用Bitmap》

Android系统使用的内存除了native heapJava heap以外,还有ASHMEM。在5.0之前可以通过Options#inPurgeable将Bitmap的数据存储在ASHMEM中,从而不占据一个进程的内存上限。

BitmapFactory.Options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);

关于Android系统中三种内存的和Bitmap的关系可以参考Introducing Fresco: A new image library for Android

参考文章

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

推荐阅读更多精彩内容