Android图片加载(一)——框架的对比分析

铺垫

我们为什么需要解决图片的异步加载问题?

我们在使用列表控件(如ListView RecyclerView)异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位、重复、闪烁等问题,其实这些问题,根源上是由图片异步加载以及View对象被复用造成的。

比如说ListView有100个item,一个屏幕只显示10个item,一个item对应一个view对象,当列表中的item数量很多的时候,我们不可能给每个item都创建一个view对象,因此我们需要复用,我们在复用View对象的时候,View对象之中的ImageView对象也被复用了。比如说,第11个item的view复用了第1个item view对象,那么imageview同时就被复用了,所以当图片没下载出来的时候,第11个item显示的就是复用的数据,也就是第1个item的图片数据。

目前的解决方法:

在给imageView设置图片之前,先将url设置为Tag,使用Async Task将图片下载的操作放在子线程中,返回结果之后,首先判断当前imageView要加载的图片的url是否等于刚才下载出来的图片的url,如果是的话,则为imageView设置图片,否则说明imageView已经被重用到其他item,不做处理。
核心代码如下:

holder.userImage.setTag(url);
        AsyncTask<String,Void,Bitmap> asyncTask = new AsyncTask<String,Void,Bitmap>(){
          ....
            @Override
            protected Bitmap doInBackground(String ... params) {
                ....
                    /*download image*/
                    return bitmap;
            }
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                super.onPostExecute(bitmap);
                if(url.equals(holder.userImage.getTag())) {
                    holder.userImage.setImageBitmap(bitmap);
                }
            }
        }.execute(url);
    }
上述方法存在的问题:

如果加载高分辨率的大图的话,这种方案很有可能会崩溃。原因在于,imageView本身有个尺寸,图片也有个尺寸,最好是图片尺寸和imageView匹配。如果图片太大,就需要缩放以节省内存,代码中的方式缺乏这一步,所以加载大图的话,大概率会挂。同时由于bitmap很大,所以还要做好回收。

铺垫

一张图片占用的内存取决于什么

公式计算:分辨率*像素点大小,像素点大小取决于像素点的数据格式,glide的bitmap默认的格式是RGB_565,但是picasso用的是ARGB_8888,RGB_565每个像素点占据2B,ARGB_8888每个像素点占4B,因此采用不同的颜色通道,像素点的大小也是不同的。为了保证图片质量,官方默认使用ARGB_8888格式,导致图片的每个像素会占用4个Byte大小。

实践

内置到res目录下一张高清图片,然后用imageView渲染出来

如下:一张4k大图


图片信息

存放到drawable-nodpi文件夹下面,并修改代码,将res文件夹下的这张图片的位置代替之前的url。

并运行代码。

Log:

log.png

以上的log证明发生了相对频繁的GC,由于导致GC的原因是Alloc,说明大量的bitmap给内存造成了负担。
由于android 8.0之后的内存分配是在native heap上,所以8.0之后的Bitmap消耗内存可以无限增长,直到耗尽系统内存,也不会提示Java OOM。
但是上述实践仍然表明,代码存在很多缺点,包括在加载到ImageView之前没有修改图片尺寸,没有定期回收bitmap等等。因此,我们引入图片加载框架。

图片加载框架
1. Android Universal ImageLoader

https://github.com/nostra13/Android-Universal-Image-Loader

使用过程

  1. 添加dependency

implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'

  1. 代码中:

    ImageLoader.getInstance().displayImage(url, holder.userImage);
    
  2. 运行报错

console.png
  1. 查看官方文档
官方文档描述.png

在第一次使用ImageLoader之前需要对它进行配置,因此在代码中添加配置信息。

添加配置信息.png

运行,列表界面成功加载。

  1. 继续看官方文档的configuration

    File cacheDir = StorageUtils.getCacheDirectory(context);
    ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
         .memoryCacheExtraOptions(480, 800) // default = device screen dimensions
         .diskCacheExtraOptions(480, 800, null)
         .taskExecutor(...)
         .taskExecutorForCachedImages(...)
         .threadPoolSize(3) // default
         .threadPriority(Thread.NORM_PRIORITY - 2) // default
         .tasksProcessingOrder(QueueProcessingType.FIFO) // default
         .denyCacheImageMultipleSizesInMemory()
         .memoryCache(new LruMemoryCache(2 * 1024 * 1024))
         .memoryCacheSize(2 * 1024 * 1024)
         .memoryCacheSizePercentage(13) // default
         .diskCache(new UnlimitedDiskCache(cacheDir)) // default
         .diskCacheSize(50 * 1024 * 1024)
         .diskCacheFileCount(100)
         .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
         .imageDownloader(new BaseImageDownloader(context)) // default
         .imageDecoder(new BaseImageDecoder()) // default
         .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default
         .writeDebugLogs()
         .build();
    

例如其中threadPoolSize可以用来设置线程池的大小。

  1. 还可以使用一些options来设置一些图片加载时的条件,例如:cacheInMemory设置让加载出来的图片存储在内存中,具体的option内容请查看官方文档。

工作流程

ImageLoader收到加载及显示图片的任务,并将它交给ImageLoaderEngine,ImageLoaderEngine分发任务带具体线程池去执行,任务通过cache(内存和磁盘)以及ImageDownloader获取图片,中间可能会经过BitmapProcessor和ImageDecoder处理,最终转换成Bitmap交给BitmapDisplayer在Imageware中显示出来

优点

  1. 可以在view滚动中暂停图片加载(通过PauseOnScrollListener接口)。
  2. 可以对多个模块进行单独配置,并且在配置上也很方便灵活。例如线程池,内存缓存策略,硬盘缓存策略,图片显示选项等
  3. 支持多级缓存以及多种缓存算法,可以根据使用场景选择合适的缓存策略,这几个框架都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。
  4. 可以根据ImageView的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存。
    其中它的缓存设计也是我们最常见的一种三级缓存设计,后面的三种图片加载框架都采用了类似的设计,即内存缓存,磁盘缓存和网络缓存。

三级缓存设计不仅在性能上可以降低Cpu的处理开销,在内存占用上也可以达到较好的控制避免OOM的发生,从而保证良好的图片加载体验。

缺点

在某些场景,比如在图片较多的应用,尤其是长列表视图中,当用户快速滚动视图时,由于图片较大较多,内存缓存大小的限制会导致频繁的Bitmap回收,进而导致内存的缓存命中率严重下降最终影响到图片的加载效率。

2. Picasso

Picasso是Square公司出品的一个强大的图片下载和缓存图片库。

优点:

  1. 图片的质量高
  2. picasso没有自定义本地缓存的接口,而是默认使用http的本地缓存,api9以上的使用Okhttp,可以灵活的控制本地缓存(通过Response Header的cache-control和expired控制图片的过期时间)---判断完内存缓存有没有命中之后,会先判断http缓存有没有命中,如果有的话,再缓存图片到内存,如果http缓存也没有命中,才会从图片加载网络,然后http缓存图片到本地,再缓存到内存,显示图片。
  3. 自带统计监控,比如说缓存命中率,内存使用大小,节省的流量等。
  4. 支持优先级处理
  5. 支持飞行模式,并发线程数根据网络类型变化
3. Glide

优点:

  1. 支持Gif

  2. activity生命周期绑定:你可以通过传递Activity或者Fragment的context给Glide.with(),然后glide就会非常智能的同Activitity的生命周期集成,比如OnResume或者onPause()。

  3. 缓存上相比picasso进行了很多优化,比如,依据Activity的生命周期按使用场景对内存缓存进行了进一步的划分,做到了及时释放不必要的内存的同时也避免了当前或者即将要使用的缓存不会被回收而导致重新加载的情况。同时,利用了android4.4以上系统支持Bitmap复用的特性,进一步降低了Bitmap的创建开销。

4. Glide和Picasso的对比
  • 图片的缓存机制 :

    • Picasso是下载图片然后缓存完整的大小到本地,比如说图片的大小是1080p的,之后如果我需要同一张图片,就会返回这张full size的,如果我需要resize,也是对这种full size的做resize。

    • glide会先下载图片,然后改变图片的大小,以适应imageView的要求,然后缓存到本地 (缓存的是和ImageView尺寸相同的图片)。所以如果你是下载同一张图片,但是设定两个不一样大小的imageView, 那么glide实际上是会缓存两份。

      这种缓存机制的优点就是glide的加载显示非常快,但是picasso会有一些延迟。

  • 加载图片的时间:

    • 第一次下载图片时(缓存中没有)

    和之前一样,picasso是直接把图缓存,但是glide需要改变图片大小再缓存,这会耗费一定的时间,因此在实际试验中,picasso会比glide快。

    • 缓存中已经有下载好的图片时

      glide比picasso快,原因还是因为缓存机制的区别。因为Picasso从缓存中拿到的图片,还要先去resize之后,再设定给imageView,但是glide不需要这样。

    其他功能对比:

    glide支持gif ; picasso不支持gif。

    灵活性:相对来讲,glide比较灵活。可以根据需求来客制化,从而缩减glide库的大小。

    图片的质量:glide的bitmap默认的格式是RGB_565,但是picasso用的是ARGB_8888,所以glide图片质量上不如picassso,但是glide的内存消耗只占picasso一半。

5. Fresco

优点:

  1. 支持gif,图片加载的效率更高
  2. 支持图片从模糊到清晰的渐进式加载
  3. 图片可以以任意的中心点显示在ImageView上
  4. 图片的解码环节比其他三个框架好,因此图片的加载效率更高
  5. 采用了类似GC的引用计数机制,使不再使用的图片对象可以更早的被回收,降低内存的开销。

缺点:

  1. 包很大,用法复杂
  2. API不够简洁
6. SDK大小

从各个框架的SDK大小来说,他们的体积分别对应如下:

  • UIL:133k
  • Picasso: 118k
  • Glide:430k
  • Fresco:>=500k

可以看出,UIL 和 Picasso 包体积较小,对安装包影响不大;Glide 包体积略大,但是其功能十分强大,内部实现及其复杂,其方法数较多。Fresco 包体积很大,但是用户可以根据自己需求有选择的添加动画依赖库和 WebP 支持包,这种模块化机制使得基础 Fresco 库很轻,增加一个库大约包体积会增加 100 KB。

总结

Universal Image Loader是早期比较有代表性的图片加载库,虽然目前已经停止维护,不再推荐使用,但是其架构设计和实现依然值得借鉴。Picasso的设计充分体现了Square公司在架构设计上一贯的简洁易用风格(链式调用)。Glide充分吸收了Picasso的优点,并在此基础上做了大量的优化和改进。Fresco 可以说是综合了之前图片加载库的优点并将性能优化到极致,但它的包很大,用法比较复杂,API不够简洁。

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

推荐阅读更多精彩内容