Android经典面试题之Glide的缓存大揭秘

简介: Glide缓存机制包括内存和硬盘缓存。内存缓存使用弱引用的ActiveResources和LRU策略,硬盘缓存利用DiskLruCache。Engine.load方法首先尝试从内存和弱引用池加载,然后从LRU缓存中加载图片,增加引用计数并移出LRU。若缓存未命中,启动新任务或加入现有任务。内存大小根据设备内存动态计算,限制在0.4以下。DiskLruCache使用自定义读写锁,保证并发安全,写操作通过锁池管理,确保高效。

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点

Glide缓存

关联类:Engine、LruResourceCache、LruCache、ActiveResources

ActiveResources:弱引用缓存池
@VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
LruCache:LinkedHashMap缓存池
private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);

入口:Engine.load方法

先从缓存中取

--> loadFromMemory

--> loadFromActiveResources(弱引用池) 

--> loadFromCache 
--> getEngineResourceFromCache(LRU缓存池,LinkedHashMap)

LRU缓存池中取到EngineResource后,会从LRU缓存中删除,然后对它引用计数+1,放入弱引用池

缓存中没有找到,就需要创建任务执行

-->  waitForExistingOrStartNewJob  
--> 如果当前图片任务已经有EngineJob了,就直接加个Callback

---> 没有的话就创建EngineJob和DecodeJob从本地加载或者是网络加载

EngineResource通过引用计数来判断是否需要释放资源,释放的资源会从弱引用池中删除,放入LRU缓存中

缓存大小设置

涉及的类:MemorySizeCalculator

  • 首先获取App可用内存大小,Glide的内存大小限制在0.4以下,如果是低内存的系统,则是在0.33
private static int getMaxSize(
    ActivityManager activityManager, float maxSizeMultiplier, float lowMemoryMaxSizeMultiplier) {
   
    final int memoryClassBytes = activityManager.getMemoryClass() * 1024 * 1024;
    final boolean isLowMemoryDevice = isLowMemoryDevice(activityManager);
    //lowMemoryMaxSizeMultiplier是0.33
    //maxSizeMultiplier是0.4
    return Math.round(
            memoryClassBytes * (isLowMemoryDevice ? lowMemoryMaxSizeMultiplier : maxSizeMultiplier));
}

@TargetApi(Build.VERSION_CODES.KITKAT)
@Synthetic
static boolean isLowMemoryDevice(ActivityManager activityManager) {
   
    if (Build.VERSION.SDK\_INT >= Build.VERSION\_CODES.KITKAT) {
   
        return activityManager.isLowRamDevice();
    } else {
   
        return true;
    }
}
  • 图片缓存大小,用几屏来表示,跟屏幕的分辨率有关
//一屏的图片大小 宽*高*4(ARG888图片的像素大小就是4字节)
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;

//BitmapPool最新的是API26以上是4,以下的是1
int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);

//memoryCache是2屏
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
  • LRU缓存动态限制图片缓存大小
 //在低内存回调,或是put新的图片后,都会进行缓存大小检查,如果超过就移除不太用的
protected synchronized void trimToSize(long size) {
   
    Map.Entry<T, Entry<Y>> last;
    Iterator<Map.Entry<T, Entry<Y>>> cacheIterator;
    while (currentSize > size) {
   
        cacheIterator = cache.entrySet().iterator();
        last = cacheIterator.next();
        final Entry\<Y> toRemove = last.getValue();
        currentSize -= toRemove.size;
        final T key = last.getKey();
        cacheIterator.remove();
        onItemEvicted(key, toRemove.value);
    }
}

DiskLruCache中的读写锁

写的时候会加锁,这个锁是自定义的,并且有一个锁的池子

 private static class WriteLock {
   
   final Lock lock = new ReentrantLock();
   int interestedThreads;

   @Synthetic
   WriteLock() {
   }
}

每次写的时候会加锁,并且会对这个WriteLock的interestedThreads分别在开始写和结束时进行加减操作

writeLocker.acquire(safeKey);
....
writeLocker.release(safeKey);

writeLocker从锁池子里取,key的话是用请求的key做哈希得到

 //SafeKeyGenerator.class
 private String calculateHexStringDigest(Key key) {
   
     PoolableDigestContainer container = Preconditions.checkNotNull(digestPool.acquire());
     try {
   
       key.updateDiskCacheKey(container.messageDigest);
       // calling digest() will automatically reset()
       return Util.sha256BytesToHex(container.messageDigest.digest());
     } finally {
   
       digestPool.release(container);
     }
}

锁的缓存做了2级,一级是通过上面的key和锁放在一个HashMap中;

//DiskCacheWriteLocker.class

private final Map<String, WriteLock> locks = new HashMap<>();
private final WriteLockPool writeLockPool = new WriteLockPool();

另一级是定义在内部类WriteLockPool的ArrayDeque里面,默认大小是10

 private static class WriteLockPool {
   
    private static final int MAX_POOL_SIZE = 10;
    private final Queue<WriteLock> pool = new ArrayDeque<>();
    ...
}

取的时候先从HashMap中取,取不到再从WriteLockPool中取

void acquire(String safeKey) {
   
    WriteLock writeLock;
    synchronized (this) {
   
      writeLock = locks.get(safeKey);
      if (writeLock == null) {
   
        writeLock = writeLockPool.obtain();
        locks.put(safeKey, writeLock);
      }
      writeLock.interestedThreads++;
    }

    writeLock.lock.lock();
}

释放锁的时候,会把writeLock的interestedThreads进行减一操作,如果为0了就释放锁,放入WriteLockPool中

void release(String safeKey) {
   
    WriteLock writeLock;
    synchronized (this) {
   
      writeLock = Preconditions.checkNotNull(locks.get(safeKey));
      if (writeLock.interestedThreads < 1) {
   
        ...
      }

      writeLock.interestedThreads--;
      if (writeLock.interestedThreads == 0) {
   
        WriteLock removed = locks.remove(safeKey);
        ...
        writeLockPool.offer(removed);
      }
    }

    writeLock.lock.unlock();
}

欢迎关注我的公众号AntDream查看更多精彩文章!

目录
相关文章
|
3天前
|
Android开发 Kotlin
Android面试题之kotlin中怎么限制一个函数参数的取值范围和取值类型等
在Kotlin中,限制函数参数可通过类型系统、泛型、条件检查、数据类、密封类和注解实现。例如,使用枚举限制参数为特定值,泛型约束确保参数为Number子类,条件检查如`require`确保参数在特定范围内,数据类封装可添加验证,密封类限制为一组预定义值,注解结合第三方库如Bean Validation进行校验。
15 6
|
3天前
|
Android开发
Android面试题之自定义View注意事项
在Android开发中,自定义View主要分为四类:直接继承View重写onDraw,继承ViewGroup创建布局,扩展特定View如TextView,以及继承特定ViewGroup。实现时需注意:支持wrap_content通过onMeasure处理,支持padding需在onDraw或onMeasure/onLayout中处理。避免在View中使用Handler,使用post系列方法代替。记得在onDetachedFromWindow时停止线程和动画以防止内存泄漏。处理滑动嵌套时解决滑动冲突,并避免在onDraw中大量创建临时对象。
13 4
|
1天前
|
Android开发
Android面试题之View的invalidate方法和postInvalidate方法有什么区别
本文探讨了Android自定义View中`invalidate()`和`postInvalidate()`的区别。`invalidate()`在UI线程中刷新View,而`postInvalidate()`用于非UI线程,通过消息机制切换到UI线程执行`invalidate()`。源码分析显示,`postInvalidate()`最终调用`ViewRootImpl`的`dispatchInvalidateDelayed`,通过Handler发送消息到UI线程执行刷新。
11 1
|
4天前
|
Android开发 Kotlin
Android面试题之 Kotlin中退出迭代器的方式有哪些
在Android和Kotlin中,遍历集合时可使用迭代器结合`break`提前终止循环。例如,使用`while`和迭代器,或用`forEach`配合`return@forEach`来中断遍历。若需退出外层函数,可定义自定义标签。在遍历并删除元素时,这些技巧尤其有用。
12 3
|
4天前
|
存储 缓存 算法
Java面试题:给出代码优化的常见策略,如减少对象创建、使用缓存等。
Java面试题:给出代码优化的常见策略,如减少对象创建、使用缓存等。
6 0
|
5天前
|
设计模式 存储 缓存
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
8 0
|
5天前
|
设计模式 存储 缓存
Java面试题:结合单例模式与Java内存模型,设计一个线程安全的单例类?使用内存屏障与Java并发工具类,实现一个高效的并发缓存系统?结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:结合单例模式与Java内存模型,设计一个线程安全的单例类?使用内存屏障与Java并发工具类,实现一个高效的并发缓存系统?结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
10 0
|
存储 缓存 编解码
Android Glide 的简单使用(一)(下)
Generated API 使用Generated API(高级用法) 定制请求 GlideExtension GlideOption GlideType submit同步获取图片资源 Application Options(选项) Memory cache(内存缓存) Disk Cache(磁盘缓存) Bitmap pool(位图池) 配置缓存 磁盘缓存策略(Disk Cache Strategy) 仅从缓存加载图片 跳过缓存 清理磁盘缓存 相关资源 Glide 缓存机制及源码(二) Glide git传送门 Glide中文文档 glide-transformations传送门
282 0
Android Glide 的简单使用(一)(下)
|
存储 缓存 监控
Android Glide 的简单使用(一)(上)
前言 Gilde简介 Android SDK 要求 所需权限 网络加载 本地存储 性能 Glide基本用法 简单使用
478 0
Android Glide 的简单使用(一)(上)
|
4天前
|
开发工具 Android开发 iOS开发
探索Android与iOS开发的差异与挑战
【7月更文挑战第11天】在移动应用开发的广阔天地中,Android和iOS两大平台如同双子星座般耀眼,各自拥有独特的开发生态和用户群体。本文将深入分析这两个平台的显著差异,从技术架构到开发工具,再到市场定位,揭示它们之间的异同。通过比较,我们不仅能够更好地理解各自的优势和局限,还能洞察未来移动应用开发的趋势。