半栈工程师Android开发Android开发经验谈

Glide ‘优’ 与 ‘愁’

2018-06-20  本文已影响229人  IMSk

随着业务的增长,加载图片是不可避免的需求。从一开始的自己写一个 ImageLoader 到井喷似的第三方图片加载库,当然中间还时不时穿插着 asynctask ,三级缓存,LRU Cache等。那个时候想必大家都用过 nostra13/Android-Universal-Image-Loader , 大家都纷纷拥抱了它,确实时势造英雄吧,所以我很钦佩作者。但三年前作者大概可能觉得实在是太累了的,宣布不再维护了的。在此期间也有一些优秀的开源库比如 square 出来的 Picasso ,优雅的链式调用想必很多人选择了拥抱他。后来Google在2014年的google I/O大会上发布的官方app中使用的 bumptech/glide 闯入大家的视野, Google 推荐大家图片加载使用 Glide. 当然 Glide 的使用方式也是仿照 Picasso 。所以几乎没有任何迁移成本,很多人也开始拥抱了 Glide. 当然在此期间 Facebook 也不甘寂寞横空出来开源了 fresco 。

为什么会选择 Glide

为什么选择 Glide ,前言中也提到了 毕竟是 Google 推荐的最佳选择。除此之外也可以做一下简单的对比

Glide VS Picasso

双胞胎兄弟之间的对比,使用方式相同,但 Glide 之所以胜出,不仅仅是 Google的推荐,更多应该归功于 GIF 的支持。 在没有 Glide 之前,常用的做法就是写了个自定义 view 然后 用一个 media 去播放。有了 Glide 之后几乎对于 GIF 无感知了的, 内部已经支持了的。可以像普通图片那样去加载并且显示出来动图。

Glide VS Android-Universal-Image-Loader

虽然有再多的不舍,一个已经不再维护的开源库,Android碎片化那么严重,我们自己维护起来还是要考虑成本的。所以 Glide 胜出。

Glide VS fresco

两个都支持 GIF。所以 GIF 这一关pass掉。说到这里不得不提到一个头疼的OOM问题,fresco 之所以很快闯入大家的视线,大概就是因为 Facebook 说他们使用了 native 内存规避掉了 OutOfMemoryError 问题。而且官方还专门写了个demo,把几大流行的开源库都集成进去,为了说明自己的图片加载库加载同样的图片速度更快,内存占用更低。所以 fresco 相比较于 Glide 的(官方)优势就是这两点: 内存以及加载速度。但是我为什么依旧坚持抛弃了 fresco ?

  1. “ In Android 4.x and lower, Fresco puts images in a special region of Android memory. This lets your application run faster - and suffer the dreaded OutOfMemoryError much less often.” 官方的原话是这么说的,所以在高版本上面依旧使用的Java 内存,所以不可避免依旧会占用内存。

  2. 提到内存,不得不说到另外一个笑话,fresco 最大只支持图片文件大小为 2M 。记得有一次帮其他团队跟踪问题,看到了 fresco 源码中有一个 最大 size 2M 常量 。于是当场找了一个10M的图片作为测试。 Glide 正常显示, fresco显示黑屏。。。

  3. 使用方式上,fresco 推荐的是用他提供的 SimpleDraweeView . 这个方式意味着我们的迁移成本会非常的高,要改布局文件,其次还必须给定大小(或者比例)。 当然他也支持代码来加载图片,比如 DraweeHierarchy,但是写起来还是真心很费劲的,很不友好,改动成本居高。

  4. fresco 更多是native实现。所以需要对NDK有所了解,但个人对NDK不太了解,相比较于 Glide, 同样遇到问题之后,修改源码的成本,Glide 成本更可控。前者可能就不太好下手了的。

  5. Glide 各种 BitmapTransformation,比如圆形,圆角等,更让人喜欢。

  6. 这一点就当随意吐槽一下,当然也可以说心疼一下 Facebook。因为在没有 Android studio (gradle构建)的情况下,想必大家都用的是 eclipse 吧。那么就意味着 fresco 得提供 Jar 包. 这一点当时也是把很多人拒之门外了的,可笑的是当 Facebook 费了老大劲的搞出来 jar 包之后,大家早就纷纷转战 gradle 构建工程, 直接 maven 依赖啦。大写的尴尬。

综上所述,Glide 依旧胜出。

Glide 是如何解决图片加载生命周期的?(精髓之一)(也是bug高发地带)

当一个界面离开之后,我们更希望当前的图片取消加载,那么 Glide 是怎么做到的呢?

Glide 的使用方式上,一定需要传入一个 context 给它。它为什么需要拿上下文呢?原因就是可以根据不同的上下文进行处理,拿到 context (除了application context)之后,Glide做了一件很巧妙的事情,就是在这个界面上追加一个 fragment,由于 fragment 添加到了 activity 上,是可以捕获到生命周期的,因此可以在 destroy 的时候取消掉当前context下的 glide对象中的加载任务。

为什么标题后面说是 ‘也是bug高发地带’ 呢? 因为从实现方式上,它是巧妙的利用了fragment的生命周期来实现的‘销毁’动作,那么就类似于另外一个高发bug,延时的匿名内部类(网络请求callback回来),界面已经销毁,所以当前activity依附的glide也就销毁了的,此时再尝试加载图片的话,就会crash。具体源码中可以看到这里:

https://github.com/bumptech/glide/blob/master/library/src/main/java/com/bumptech/glide/Glide.java

 @NonNull
 private static RequestManagerRetriever getRetriever(@Nullable Context context) {
   // Context could be null for other reasons (ie the user passes in null), but in practice it will
   // only occur due to errors with the Fragment lifecycle.
   Preconditions.checkNotNull(
       context,
       "You cannot start a load on a not yet attached View or a Fragment where getActivity() "
           + "returns null (which usually occurs when getActivity() is called before the Fragment "
           + "is attached or after the Fragment is destroyed).");
   return Glide.get(context).getRequestManagerRetriever();
 }

Glide 是如何做到链式的优雅调用?(精髓之二)

链式调用其实只不过是一种语法招数。它能让你通过重用一个初始操作来达到用少量代码表达复杂操作的目的。其实就是类似builder一样,把this return 出去,一直可以调用自己的方法。所以也称之为方法链。

 // Some time in the future:
Glide.with(fragment)
.load(newUrl)
.into(target);

链式调用的好与不好:

  1. 编程性强
  2. 可读性强
  3. 代码简洁
  4. 对程序员的业务能力要求高
  5. 不太利于代码调试

Glide 坑爹的 wrap_content 不支持的问题

官方说了的,不支持并且不建议imageview设置wrap_content。因为这样 glide 不知道要加载多大的图片给我们才好,在他的接口(Sizes and dimensions)中也有体现。普通的imageview其实也还好,如果放在列表(RecyclerView)中, 由于我们并不知道目标图片大小是多大的,所以我们选择了wrap_content,那么在上下来回滚动过程中,就会导致图片一会大一会小的bug.

官方 issue 作者回答如下:

 Don't use wrap_content.

Even if you don't use Glide, wrap_content necessarily means that the size of your views in RecyclerView are going to change from item to item. That's going to cause all sorts of UI weirdness.

One option is to try to obtain the image dimensions in whatever metadata you're using to populate the RecyclerView. Then you can set a fixed View size in onBindViewHolder so the view size at least doesn't change when the image is loaded. You're still likely to see weird scroll bar behavior though.

If nothing else, you can always pick a uniform size that's large enough for all items and use the same consistent size for every item.
For the image file size, you can downscale or upscale by setting the ImageView size manually to 150dp x 150dp. Ultimately either you need uniform view sizes or predetermined view sizes. There's nothing else that will prevent content from expanding or shrinking in your RecyclerView.

For the placeholder bit, I think that will be fixed by 648c58e, you can check by trying the 4.2.0-SNAPSHOT version: http://bumptech.github.io/glide/dev/snapshots.html.

so...还是建议我们指定图片的大小。

Glide 坑爹的 support包 版本问题

为什么会有这个问题呢?其实刚才已经提到了的 ,由于它用到了 fragmen t,那么自然就有版本冲突问题。support 包大家都懂的,不同的版本,差异可能巨大,有个段子就是说 Google 的 support 包 大概是招了个实习生写的。不同的版本冲突可能会编译不过,可能会有 ‘nosuchmethod’ 等等问题。
比如我们产线现在的用的是 Glide 版本是 4.3.1,之所以迟迟没有升级到最新版本,就是因为后面的版本 Glide采用了 27编译。。而我们项目才25 。。。 中间这个编译升级的风险。有点不可控。所以一直没有升级上去。
所以建议,在升级 Glide 版本的时候 看一下对应版本源码中依赖的 support 版本是多少。

写在最后

之所以今天简单的跟大家聊一聊 Glide。其实也只是找了一个项目中用到的开源库作为例子,想跟大家聊聊,当项目中需要技术选型的时候,不能给的答案是:因为大家都在用啊?

而我更想知道的是,大家为什么会选择它,不仅仅是人群中多看了你一眼, 而是从外表 API的“美”,再到内在框架设计的 “美”。只有知其所以然,那么当遇到坑的时候,才知道如何去解决它。而不是简单的“跟风似的” “一见钟情”。。。

上一篇下一篇

猜你喜欢

热点阅读