Glide相关

一、Gilde的使用方法:

Glide.with(content) .load(url) .into(imageView);

with绑定生命周期,load指定加载资源,into指明加载目标。

二、生命周期绑定

  • with()方法的重载种类非常多,既可以传入Activity,也可以传入Fragment或者是Context,实际上只有两种情况而已,即传入Application类型的参数,和传入非Application类型的参数。
  • Application:自动就是和应用程序的生命周期是同步的。
  • 非Application:不管你在Glide.with()方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的:在 Activity 上创建一个透明的 ReuqestManagerFragment 加入到 FragmentManager 中,通过添加的 Fragment 感知Activty\Fragment 的生命周期。因为添加到 Activity 中的 Fragment 会跟随Activity 的生命周期。在 RequestManagerFragment 中的相应生命周期方法中通过 liftcycle 传递给在 lifecycle 中注册的 LifecycleListener。因为Glide并没有办法知道Activity的生命周期,于是Glide就使用了添加隐藏Fragment的这种小技巧,因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止图片加载了。

三、缓存机制

1.缓存方式:

  1. DiskCacheStrategy.NONE 不缓存文件
  2. DiskCacheStrategy.SOURCE 只缓存原图
  3. DiskCacheStrategy.RESULT 只缓存最终加载的图(默认的缓存略)
  4. DiskCacheStrategy.ALL 同时缓存原图和结果图

2.图片加载策略:

 

Glide相关_第1张图片

  1. 首先从ActivateResource获取,是个值为弱引用的Map
  2. MemoryCache和DiskCache是LruCache
  3. 图片加载时会从MemoryCache移到ActivateResouce,生命周期结束后会缓存至MemoryCache,所以内存中至多有一份缓存。

3.Glide 使用的缓存

Glide的缓存机制,主要分为2种缓存,一种是内存缓存,一种是磁盘缓存。
之所以使用内存缓存的原因是:防止应用重复将图片读入到内存,造成内存资源浪费。
之所以使用磁盘缓存的原因是:防止应用重复的从网络或者其他地方下载和读取数据。
正式因为有着这两种缓存的结合,才构成了Glide极佳的缓存效果。

内存缓存: LruResourceCache(memory)+弱引用 activeResources
Map<Key, WeakReference<EngineResource>> activeResources正在使用的资源,当 acquired变量大于 0,说明图片正在使用,放到 activeResources 弱引用缓存中,经过 release()后,acquired=0,说明图片不再使用,会把它放进 LruResourceCache 中。

Glide 默认内存缓存用的也是LruCache,只不过并没有用Android SDK中的LruCache,不过内部同样是基于LinkHashMap,使用的为LruResourceCache,原理是一样的。LruCache 采用最近最少使用算法,设定一个缓存大小,当缓存达到这个大小之后,会将最老的数据移除,避免图片占用内存过大导致OOM。

磁盘缓存: DiskLruCache,这里分为 Source(原始图片)和 Result(转换后的图片)

第一次获取图片,肯定网络取,然后存 active\disk 中,再把图片显示出来,第二次读取相同的图片,并加载到相同大小的 imageview 中,会先从 memory 中取,没有再去 active 中获取。如果 activity 执行到 onStop 时,图片被回收,active 中的资源会被保存到memory 中,active中的资源被回收。当再次加载图片时,会从 memory 中取,再放入 active 中,并将 memory中对应的资源回收。

之所以需要 activeResources,它是一个随时可能被回收的资源,memory 的强引用频繁读写可能造成内存激增频繁 GC,而造成内存抖动。资源在使用过程中保存在 activeResources 中,而 activeResources 是弱引用,随时被系统回收,不会造成内存过多使用和泄漏。

4.Glide 控制内存缓存大小

Glide 内存缓存最大空间(maxSize)=每个进程可用最大内存0.4(低配手机是 每个进程可用最大内存0.33)

磁盘缓存大小是 250MB int DEFAULT_DISK_CACHE_SIZE = 250 1024 1024;

5.Glide的三级缓存原理

读取一张图片的时候,获取顺序:
Lru算法缓存--> 弱引用缓存--> 磁盘缓存(如果设置了的话)

当我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,并将该图片放入WeakReference中,如果LruCache中没有,则去WeakReference中寻找,如果WeakReference中有,则从WeakReference中取出图片使用,如果WeakReference中也没有图片,则从磁盘缓存/网络中加载图片。

注:图片正在使用时存在于 activeResources 弱引用map中。

将图片缓存的时候,写入顺序:
弱引用缓存 --> Lru算法缓存--> 磁盘缓存中

当图片不存在的时候,先从网络下载图片,然后将图片存入弱引用中,glide会采用一个acquired(int)变量用来记录图片被引用的次数,
当acquired变量大于0的时候,说明图片正在使用中,也就是将图片放到弱引用缓存当中;
如果acquired变量等于0了,说明图片已经不再被使用了,那么此时会调用方法来释放资源,首先会将缓存图片从弱引用中移除,然后再将它put到LruResourceCache当中。
这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能。

关于LruCache

最近最少使用算法,设定一个缓存大小,当缓存达到这个大小之后,会将最老的数据移除,避免图片占用内存过大导致OOM。
LruCache 内部用LinkHashMap存取数据,在双向链表保证数据新旧顺序的前提下,设置一个最大内存,往里面put数据的时候,当数据达到最大内存的时候,将最老的数据移除掉,保证内存不超过设定的最大值。

关于LinkedHashMap

LinkHashMap 继承HashMap,在 HashMap的基础上,新增了双向链表结构,每次访问数据的时候,会更新被访问的数据的链表指针,具体就是先在链表中删除该节点,然后添加到链表头header之前,这样就保证了链表头header节点之前的数据都是最近访问的(从链表中删除并不是真的删除数据,只是移动链表指针,数据本身在map中的位置是不变的)

四、Glide加载完一个100 * 100的图片,放到一个300 * 300的view上会怎样,800*800呢,图片会很模糊,怎么处理?

当我们调整imageview的大小时,Picasso会不管imageview大小是什么,总是直接缓存整张图片,而Glide就不一样了,它会为每个不同尺寸的Imageview缓存一张图片,也就是说不管你的这张图片有没有加载过,只要imageview的尺寸不一样,那么Glide就会重新加载一次,这时候,它会在加载的imageview之前从网络上重新下载,然后再缓存。

举个例子,如果一个页面的imageview是300 * 300像素,而另一个页面中的imageview是100 * 100像素,这时候想要让两个imageview像是同一张图片,那么Glide需要下载两次图片,并且缓存两张图片。

public  LoadStatus load() {
    // 根据请求参数得到缓存的键
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
}

看到了吧,缓存Key的生成条件之一就是控件的长宽。

五、如果在一个页面中使用Glide加载了一张图片,图片正在获取中,如果突然关闭页面,这个页面会造成内存泄漏吗?

因为Glide 在加载资源的时候,如果是在 Activity、Fragment 这一类有生命周期的组件上进行的话,会创建一个透明的 RequestManagerFragment 加入到FragmentManager 之中,感知生命周期,当 Activity、Fragment 等组件进入不可见,或者已经销毁的时候,Glide 会停止加载资源。但是如果,是在非生命周期的组件上进行时,会采用Application 的生命周期贯穿整个应用,所以 applicationManager 只有在应用程序关闭的时候终止加载。

六、如何设计一个大图加载框架?

概括来说,图片加载包含封装,解析,下载,解码,变换,缓存,显示等操作。

  1. 封装参数:从指定来源,到输出结果,中间可能经历很多流程,所以第一件事就是封装参数,这些参数会贯穿整个过程;
  2. 解析路径:图片的来源有多种,格式也不尽相同,需要规范化;
  3. 读取缓存:为了减少计算,通常都会做缓存;同样的请求,从缓存中取图片(Bitmap)即可;
  4. 查找文件/下载文件:如果是本地的文件,直接解码即可;如果是网络图片,需要先下载;
  5. 解码:这一步是整个过程中最复杂的步骤之一,有不少细节;
  6. 变换:解码出Bitmap之后,可能还需要做一些变换处理(圆角,滤镜等);
  7. 缓存:得到最终bitmap之后,可以缓存起来,以便下次请求时直接取结果;
  8. 显示:显示结果,可能需要做些动画(淡入动画,crossFade等)。

7、假如让你自己写个图片加载框架,你会考虑哪些问题?

首先,梳理一下必要的图片加载框架的需求:

  • 异步加载:线程池
  • 切换线程:Handler,没有争议吧
  • 缓存:LruCache、DiskLruCache
  • 防止OOM:软引用、LruCache、图片压缩、Bitmap像素存储位置
  • 内存泄露:注意ImageView的正确引用,生命周期管理
  • 列表滑动加载的问题:加载错乱、队满任务过多问题

当然,还有一些不是必要的需求,例如加载动画等。

异步加载需要线程池个数:

缓存一般有三级,内存缓存、硬盘、网络。

由于网络会阻塞,所以读内存和硬盘可以放在一个线程池,网络需要另外一个线程池,网络也可以采用Okhttp内置的线程池。

读硬盘和读网络需要放在不同的线程池中处理,所以用两个线程池比较合适。

Glide 必然也需要多个线程池,看下源码是不是这样

public final class GlideBuilder {
  ...
  private GlideExecutor sourceExecutor; //加载源文件的线程池,包括网络加载
  private GlideExecutor diskCacheExecutor; //加载硬盘缓存的线程池
  ...
  private GlideExecutor animationExecutor; //动画线程池

Glide使用了三个线程池,不考虑动画的话就是两个。

切换线程:

图片异步加载成功,需要在主线程去更新ImageView,

无论是RxJava、EventBus,还是Glide,只要是想从子线程切换到Android主线程,都离不开Handler。

看下Glide 相关源码:

    class EngineJob implements DecodeJob.Callback,Poolable {
      private static final EngineResourceFactory DEFAULT_FACTORY = new EngineResourceFactory();
      //创建Handler
      private static final Handler MAIN_THREAD_HANDLER =
          new Handler(Looper.getMainLooper(), new MainThreadCallback());

 

八、图片框架比较

Glide:

Glide相关_第2张图片

Glide设计及流程

以上为Glide的总体设计图。
整个库分为RequestManager(请求管理器)、Engine(数据获取引擎)、Fetcher(数据获取器)、MemoryCache(内存缓存)、DiskLRUCache(本地缓存)、Transformation(图片处理)、Encoder(编码处理)、Registry(图片类型以及解析器配置)、Target(目标)等模块。

简单流程Glider收到加载及显示资源任务,创建Request并将它交给RequestManager,Request启动Engine去数据源获取资源,得到资源后通过Transformation处理后交给Target.
Glide依赖DiskLRUCache、GifDecoder等开源库去完成本地缓存和Gif图片解密工作;

Glide核心

为Bitmap 维护一个BitmapPool对象池, 对象池的主要目的是通过减少大对象的分配以重用来提高性能!
优点:

  • 加载速度快,框架体积小,4、5百kb

  • 图片缓存->媒体缓存:Glide不仅是一个图片缓存,它支持Gif、WebP、缩略图。甚至是Video,所以更该当做一个媒体缓存。
  • 支持优先级处理
  • 支持Activity、Fragment生命周期一致,支持trimMenory:Glide对每个context都保存一个RequestManager,通过FragmentTransaction保存于Activity、Fragment生命周期一致,并且有对应的trimMemory接口实现可供调用;
  • 支持Okhttp、Volley:Glide默认通过UrlConnection获取数据,可以配合Okhttp或者Volley获取。
  • 内存友好:
    ①Glide内存缓存有一个active设计
    从内存缓存中获取数据时,不像一般实现用get,而是用remove,在将这个缓存数据放到一个value为软引用的activeResources map中,并计数引用数,在图片加载完后进行判断,如果引用数为空则回收掉;
    ②内存缓存更小图片
    Glide以url、view_width、view_height、屏幕分辨率等作为联合key,将处理后图片缓存在内存缓存中,而不是原始图片以节省大小
    ③图片默认使用RGB_565,而不是ARGB_888;
    其他:Glide可以通过signature或不使用本地缓存,支持url过期;

缺点

  • 图片质量低:因为机制不同,速度快,但是图片的质量降低了RGB565;
  • 多尺寸缓存导致内存和磁盘占用多:根据ImageView大小来缓存,可能会导致一张图片可能根据展示情况来缓存不同尺寸的几份;

Picasso

Glide相关_第3张图片

Picasso设计及流程

以上为Picasso的总体设计图。
整个库分为Dispatcher、RequestHandler以及Downloader、PicassoDrawable等模块。
简单流程:Picasso收到加载显示图片任务后,创建Request并将它交给Dispatcher,Dispatcher分发任务到具体RequestHandler,任务通过MemoryCache及Handler(数据获取接口)获取图片,图片获取成功后通过PicassoDrawable显示到Target中;

上面Data的File system部分,Picasso没有自定义本地缓存的接口,默认使用http的本地缓存,API19以上使用okhttp,一下使用UrlConnection,所以如果需要自定义本地缓存就需要自定义Downloader;
优点

  • 图片质量高ARGB_8888,体积小可以配合OKhttp使用
  • 自带统计监控功能:支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流程等。
  • 支持优先级处理:每次任务调度前会选择优先级高的任务,比如App页面中的Banner的优先级高于Icon时就很适用
  • 支持延时到图片尺寸计算完成加载
  • 支持飞行模式、并发线程数据根据网络类型而变
    手机切换到飞行模式或网络类型变换时,会自动调整线程池最大并发数,比如wifi最大并发为4,4G为3,3G为2。
    这里Picasso根据网络类型来决定最大并发数,而不是CPU核数。
  • “无”本地缓存
    无本地缓存并不是说没有本地缓存,而是Picasso自己没有实现,交给了Square的另外有一个网络库okhttp去实现,这样的好处是可以通过请求Response Header中的Cache-Control及Expired控制图片的过期时间。

缺点:加载速度没有其他框架快;
特点:只缓存一个全尺寸的图片,根据需求的大小在压缩转换;

Fresco

Glide相关_第4张图片

Fresco设计及流程

以上为Fresco的总体设计图
整个库分为UI:DraweeView(View控件)、Drawable(图片数据)、DraweeController(图片控制器)、DraweeHiierarchy(图片体系);Core:DataSource(数据源)、ImagePipeline(图像管道)、Producer(生产者)、ProducerFacotry(生产工厂)、Subcriber(订阅)、Supplier(供应者)、Consumer(消费者);IO/Data:MemoryCache(内存缓存)、Network、DiskCache(磁盘缓存)、Recourse(本地资源)

简单流程:从上面的结构可以看出,fresco主要采用了工厂+建造者的模式实现功能,逻辑划分比较清楚;Fresco框架整体是一个MVC模式,DrawableView--->View用来显示顶层视图、DrawableController--->Control控制加载图片的配置 事件的分发、DrawableHierarchy--->Model 用于存储和描述图片信息,同时也封装了一些图片的显示和视图层级的方法;ImagePipeline模块负责从网络、本地文件系统、本地资源加载图片

优点:

  • 图片的渐进式呈现
  • 良好的内存管理,低端机一样出色
  • 支持动图加载:加载Gif、WebP动图,每一帧都是一张很大的Bitmap,每个动画都有很多帧。Fresco能管理好每一帧并管理好你的内存
  • 丰富的图片处理:缩放、圆角、透明、高斯模糊等处理
  • 支持监听下载事件:
  • 系统底层C(匿名共享内存)缓存,避免OOM:
    在系统底层C开辟内存区域处理缓存(匿名共享内存),避免占用应用内存资源,不会因为图片加载而导致OOM,减少Bitmap频繁回收导致的卡顿,性能更高(内存管理方便:主要看两个内容,既java堆和native堆。Java对最大值受设备限制(进程资源限制),并且其内存回收是个麻烦事,其进行回收时会直接影响前端性能响应。Native堆是通过C++来写的,能获取更多的内存,内存管理也更加灵活,但也相当麻烦);

缺点:

  • 框架大,影响Apk体积;
  • 一定的学习成本,使用比较繁琐,需要使用内部提供的ImageView控件,使用起来比较复杂;

 

参考:https://www.jianshu.com/p/069d8400ebab

           https://www.cnblogs.com/billshen/p/13306285.html

          https://liuyangbajin.blog.csdn.net/article/details/103193470

          https://www.jianshu.com/p/1ab5597af607

 

你可能感兴趣的:(面试_Android)