面试官:简历上如果写Glide,请注意以下几点...

这次来面试的是一个有着5年工作经验的小伙,截取了一段对话如下:

面试官:我看你写到Glide,为什么用Glide,而不选择其它图片加载框架?
小伙:Glide 使用简单,链式调用,很方便,一直用这个。
面试官:有看过它的源码吗?跟其它图片框架相比有哪些优势?
小伙:没有,只是在项目中使用而已~
面试官:假如现在不让你用开源库,需要你自己写一个图片加载框架,你会考虑哪些方面的问题,说说大概的思路。
小伙:额~,压缩吧。
面试官:还有吗?
小伙:额~,这个没写过。

说到图片加载框架,大家最熟悉的莫过于Glide了,但我却不推荐简历上写熟悉Glide,除非你熟读它的源码,或者参与Glide的开发和维护。

在一般面试中,遇到图片加载问题的频率一般不会太低,只是问法会有一些差异,例如:

  • 简历上写Glide,那么会问一下Glide的设计,以及跟其它同类框架的对比 ;
  • 假如让你写一个图片加载框架,说说思路;
  • 给一个图片加载的场景,比如网络加载一张或多张大图,你会怎么做;

带着问题进入正文~

一、谈谈Glide

1.1 Glide 使用有多简单?

Glide由于其口碑好,很多开发者直接在项目中使用,使用方法相当简单

github.com/bumptech/gl…

1、添加依赖:

implementation 'com.github.bumptech.glide:glide:4.10.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'

2、添加网络权限

<uses-permission android:name="android.permission.INTERNET" />

3、一句代码加载图片到ImageView

Glide.with(this).load(imgUrl).into(mIv1);

进阶一点的用法,参数设置

RequestOptions options = new RequestOptions()
            .placeholder(R.drawable.ic_launcher_background)
            .error(R.mipmap.ic_launcher)
            .diskCacheStrategy(DiskCacheStrategy.NONE)
    		.override(200, 100);

Glide.with(this)
            .load(imgUrl)
            .apply(options)
            .into(mIv2);

使用Glide加载图片如此简单,这让很多开发者省下自己处理图片的时间,图片加载工作全部交给Glide来就完事,同时,很容易就把图片处理的相关知识点忘掉。

1.2 为什么用Glide?

从前段时间面试的情况,我发现了这个现象:简历上写熟悉Glide的,基本都是熟悉使用方法,很多3年-6年工作经验,除了说Glide使用方便,不清楚Glide跟其他图片框架如Fresco的对比有哪些优缺点。

首先,当下流行的图片加载框架有那么几个,可以拿 Glide 跟Fresco对比,例如这些:

Glide:

  • 多种图片格式的缓存,适用于更多的内容表现形式(如Gif、WebP、缩略图、Video)
  • 生命周期集成(根据Activity或者Fragment的生命周期管理图片加载请求)
  • 高效处理Bitmap(bitmap的复用和主动回收,减少系统回收压力)
  • 高效的缓存策略,灵活(Picasso只会缓存原始尺寸的图片,Glide缓存的是多种规格),加载速度快且内存开销小(默认Bitmap格式的不同,使得内存开销是Picasso的一半)

Fresco:

  • 最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)
  • 大大减少OOM(在更底层的Native层对OOM进行处理,图片将不再占用App的内存)
  • 适用于需要高性能加载大量图片的场景

对于一般App来说,Glide完全够用,而对于图片需求比较大的App,为了防止加载大量图片导致OOM,Fresco 会更合适一些。并不是说用Glide会导致OOM,Glide默认用的内存缓存是LruCache,内存不会一直往上涨。

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

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

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

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

2.1 异步加载:

线程池,多少个?

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

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

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

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

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

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

2.2 切换线程:

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

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

看下Glide 相关源码:

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

问RxJava是完全用Java语言写的,那怎么实现从子线程切换到Android主线程的? 依然有很多3-6年的开发答不上来这个很基础的问题,而且只要是这个问题回答不出来的,接下来有关于原理的问题,基本都答不上来。

有不少工作了很多年的Android开发不知道鸿洋、郭霖、玉刚说,不知道掘金是个啥玩意,内心估计会想是不是还有叫掘银掘铁的(我不知道有没有)。

我想表达的是,干这一行,真的是需要有对技术的热情,不断学习,不怕别人比你优秀,就怕比你优秀的人比你还努力,而你却不知道

2.3 缓存

我们常说的图片三级缓存:内存缓存、硬盘缓存、网络。

2.3.1 内存缓存

一般都是用LruCache

Glide 默认内存缓存用的也是LruCache,只不过并没有用Android SDK中的LruCache,不过内部同样是基于LinkHashMap,所以原理是一样的。

// -> GlideBuilder#build
if (memoryCache == null) {
  memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}

既然说到LruCache ,必须要了解一下LruCache的特点和源码:

为什么用LruCache?

LruCache 采用最近最少使用算法,设定一个缓存大小,当缓存达到这个大小之后,会将最老的数据移除,避免图片占用内存过大导致OOM。

LruCache 源码分析
    public class LruCache<K, V> {
	// 数据最终存在 LinkedHashMap 中
    private final LinkedHashMap<K, V> map;
	...
	public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
		// 创建一个LinkedHashMap,accessOrder 传true
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
    ...

LruCache 构造方法里创建一个LinkedHashMap,accessOrder 参数传true,表示按照访问顺序排序,数据存储基于LinkedHashMap。

先看看LinkedHashMap 的原理吧

LinkedHashMap 继承 HashMap,在 HashMap 的基础上进行扩展,put 方法并没有重写,说明LinkedHashMap遵循HashMap的数组加链表的结构

LinkedHashMap重写了 createEntry 方法。

看下HashMap 的 createEntry 方法

void createEntry(int hash, K key, V value, int bucketIndex) {
    HashMapEntry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
    size++;
}

HashMap的数组里面放的是HashMapEntry 对象

看下LinkedHashMap 的 createEntry方法

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值