面试官:关于Glide常问的几个问题你掌握多少?答对了直接绿卡!

2021-05-10 10:39:27 浏览数 (1)

面试官1:Glide的三级缓存有了解过么?
  • 先来了解一下我们常说的图片三级缓存:

一般是强引用,软引用和文件系统,Android系统中提供了LruCache,通过维护一个LinkedHashMap来保存我们需要的各种类型数据,例如我们这里需要的Bitmap。LruCache一般我们会设置为系统最大存储空间的八分之一,而它的机制就是我们常说的最近最少使用原则,如果Lru中的图片大小超过了默认大小,则会把最久使用的图片移除。 当图片被Lru移除时,我们需要手动将图片添加到软引用(SoftRefrence)中。需要维护一个软应用的集合在我们的项目中。

  • 简单概括一下常用的三级缓存的流程:

先去Lru中找,有则直接取。 没有,则去SoftRefrence中找,有则取,同时将图片放回Lru中。 没有的话去文件系统找,有则取,同时将图片添加到Lru中。 没有就走下载图片逻辑,保存到文件系统中,并放到Lru中。

下面介绍一下Glide的缓存结构:

Glide缓存严格意义上说只有内存缓存和磁盘缓存,内存缓存中又分为Lru和弱引用缓存。

所以Glide的三级缓存可以分为:Lru缓存,弱引用缓存,磁盘缓存。

下面我们看一下Glide的读取顺序,这里有一点不同,我用的是Glide4.8版本,跟之前版本的写入顺序稍有不同。

截取部分源码:

代码语言:javascript复制
 @NonNull
  Glide build(@NonNull Context context) {

    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }

    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor(),
              isActiveResourceRetentionAllowed);
    }
  • memoryCache就是Glide使用的内存缓存,LruResourceCache类继承了LruCache,这部分可以自行查看一下源码。

通过上面可以看到,GLide#build()方法中实例化memoryCache作为Glide的内存缓存,并将其传给Engine作为构造器的入参。

  • Engine.class 截取部分源码
代码语言:javascript复制
{
    //生成缓存key
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
    //从弱应用中读取缓存
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }
    //从LruCache中读取缓存
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);
            jobs.put(key, engineJob);

    engineJob.addCallback(cb);
    //开启线程池,加载图片
    engineJob.start(decodeJob);
  }

从上可知,Glide加载过程中使用loadFromActiveResources方法和loadFromCache方法来获取内存缓存的。

大致总结一下:

首先从弱引用读取缓存,没有的话通过Lru读取,有则取,并且加到弱引用中,如果没有会开启EngineJob进行后面的图片加载逻辑。

下面直接看之后的缓存部分代码:

  • Engine#onEngineJobComplete()
代码语言:javascript复制
public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    Util.assertMainThread();
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null) {
      resource.setResourceListener(key, this);

      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }
    }

    jobs.removeIfCurrent(key, engineJob);
  }
void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key,
            resource,
            getReferenceQueue(),
            isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }

这里可以看到activeResources.activate(key, resource)把EngineResource放到了弱引用中,至于lru的放置逻辑如下:

  • EngineResource#release()
代码语言:javascript复制
void release() {
    if (acquired <= 0) {
      throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
    }
    if (!Looper.getMainLooper().equals(Looper.myLooper())) {
      throw new IllegalThreadStateException("Must call release on the main thread");
    }
    if (--acquired == 0) {
      listener.onResourceReleased(key, this);
    }
  }

当acquired变量大于0的时候,说明图片正在使用中,也就应该放到activeResources弱引用缓存当中。而经过release()之后,如果acquired变量等于0了,说明图片已经不再被使用了,那么此时会调用listener的onResourceReleased()方法来释放资源。

  • Engine#onResourceReleased()
代码语言:javascript复制
@Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

这里首先会将缓存图片从activeResources中移除,然后再将它put到LruResourceCache当中。这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能。

接下来就是Glide的磁盘缓存,磁盘缓存简单来说就是根据Key去DiskCache中取缓存,有兴趣可以自行看一下源码。

面试官2:为什么选择Glide不选择其他的图片加载框架?
  • Glide和Picasso

前者要更加省内存,可以按需加载图片,默认为ARGB_565,后者为ARGB_8888。

前者支持Gif,后者并不支持。

  • Glide和Fresco

Fresco低版本有优势,占用部分native内存,但是高版本一样是java内存。

Fresco加载对图片大小有限制,Glide基本没有。

Fresco推荐使用SimpleDraweeView,涉及到布局文件,这就不得不考虑迁移的成本。

Fresco有很多native的实现,想改源码成本要大的多。

Glide提供对中TransFormation帮助处理图片,Fresco并没有。

Glide版本迭代相对较快。

Glide的几个显著的优点:
  • 生命周期的管理

GLide#with

代码语言:javascript复制
  @NonNull
  public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }

  @NonNull
  public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
  }

  @NonNull
  public static RequestManager with(@NonNull FragmentActivity activity) {
    return getRetriever(activity).get(activity);
  }

  
  @NonNull
  public static RequestManager with(@NonNull Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
  }

 
  @Deprecated
  @NonNull
  public static RequestManager with(@NonNull android.app.Fragment fragment) {
    return getRetriever(fragment.getActivity()).get(fragment);
  }

可以看到有多个重载方法,主要对两类不同的Context进行不同的处理

  • Application Context 图片加载的生命周期和应用程序一样,肯定是我们不推荐的写法。
  • 其余Context,会像当前Activity创建一个隐藏的Fragment,绑定生命周期。

以Activity为例:

代码语言:javascript复制
 @NonNull
  public RequestManager get(@NonNull Activity activity) {
    if (Util.isOnBackgroundThread()) {
      return get(activity.getApplicationContext());
    } else {
      //判断是否是销毁状态
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      //绑定生命周期
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

具体看#fragmentGet()

代码语言:javascript复制
@NonNull
  private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
      //这就是绑定的Fragment,RequestManagerFragment
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    
    return requestManager;
  }

接着看RequestManagerFragment

代码语言:javascript复制
public class RequestManagerFragment extends Fragment {
    @Override
  public void onStart() {
    super.onStart();
    lifecycle.onStart();
  }
  @Override
  public void onStop() {
    super.onStop();
    lifecycle.onStop();
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    lifecycle.onDestroy();
    unregisterFragmentWithRoot();
  }
    
}

关联lifeCycle相应的方法。 简单来说就是通过#with()方法根据穿过来的不同的Context绑定生命周期。

  • Bitmap对象池

Glide提供了一个BitmapPool来保存Bitmap。

简单来说就是当需要加载一个bitmap的时候,会根据图片的参数去池子里找到一个合适的bitmap,如果没有就重新创建。BitMapPool同样是根据Lru算法来工作的。从而提高性能。

  • 高效缓存

缓存相关可以看上文描述,内存和磁盘,磁盘缓存也提供了几种缓存策略。

1.NONE,表示不缓存任何内容 2.SOURCE,表示只缓存原始图片 3.RESULT,表示只缓存转换过后的图片(默认选项) 4.ALL, 表示既缓存原始图片,也缓存转换过后的图片

大厂面试前的复习准备

接下来分享的系统学习资源以详解各大互联网公司的 Android 常见面试题为主线,从面试的角度带你介绍必备知识点,以及该知识点在项目中的实际应用

帮你在现在的基础上,重新梳理和建立 Android 开发的知识体系。无论是你短期内想提升 Android 内功实力,突破自己工作中的能力瓶颈,还是准备参加 Android 面试,都会在这份资料中有所一些收获。

从架构基础开始,分了8个模块来逐步从基础进阶到架构师的环节:

多余的话就不讲了,接下来将分享面试的一个复习路线,如果你也在准备面试但是不知道怎么高效复习,可以参考一下我的复习路线,有任何问题也欢迎一起互相交流,加油吧!

首先是超级详细得不能再详细的Android开发学习思维导图,因为图片实在是太大了,所以我就只把二级目录的内容放出来。

接下来就需要梳理知识,提升储备了!(Android移动架构师七大专题学习资源)

  • 架构师筑基必备技能:深入Java泛型 注解深入浅出 并发编程 数据传输与序列化 Java虚拟机原理 反射与类加载 动态代理 高效IO
  • Android高级UI与FrameWork源码:高级UI晋升 Framework内核解析 Android组件内核 数据持久化
  • 360°全方面性能调优:设计思想与代码质量优化 程序性能优化 开发效率优化
  • 解读开源框架设计思想:热修复设计 插件化框架解读 组件化框架设计 图片加载框架 网络访问框架设计 RXJava响应式编程框架设计 IOC架构设计 Android架构组件Jetpack
  • NDK模块开发:NDK基础知识体系 底层图片处理 音视频开发
  • 微信小程序:小程序介绍 UI开发 API操作 微信对接
  • Hybrid 开发与Flutter:Html5项目实战 Flutter进阶

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

然后再是通过源码来系统性地学习

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

刷大厂面试题备战,增加大厂通过率

历时半年,整理了这份市面上最全面的安卓面试题解析大全。

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。 2.五角星数表示面试问到的频率,代表重要推荐指数

以上这些内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。或者点击 【这里】 查看获取方式。

0 人点赞