Android图片加载库Glide核心功能手写实现

Glide代码量非常庞大,直接阅读源码非常不易理解,很容易导致进入源码就出不来了,不能够掌握Glide的设计思想(源码都是根据Glide设计者的设计思想写的)。直接看源码,你能理清楚Glide的整体原理与设计思想吗?为什么经常看了网上各种各样的大量的源码分析,还是看了没过多久就忘?因为没搞懂框架的设计思想,没理解框架的整体原理…

换一种思路,跟着大牛或者有丰富经验的人(他们对Glide的原理已经很熟悉了)进行手写实现Glide的整体核心功能(当然是整体核心功能,若实现Glide的全部功能可能需要两个月甚至更多),从而理解Glide的整体原理与设计思想。后续看Glide的源码时也能更容易理解。

Glide手写实现之资源封装

资源封装就是对图片资源进行封装,图片可以来自本地,可以来自网络等,定义相关的类对其进行封装。
在这里插入图片描述
Key :对Value的唯一性描述
Value : Bitmap的封装(有一个计数器,使用一次则+1,使用完毕则-1,没有任何使用则释放Bitmap)

key是图片的url,当然是要进行hash过的url,不能是原始的url,因为原始的url有很多不符合规范的字符(:、/、_ 等等),具体用什么hash算法不重要,只要能生成符合规范的字符串就行,磁盘缓存DiskLruCache需要这种格式的key。比如可以使用SHA256哈希后的url作为key。
举个例子:
直接使用:https://cn.bing.com/sa/simg/hpb/LaDigue_EN-CA1115245085_1920x1080.jpg
这种url是不可以的,可以对这url进行SHA256哈希,得到的结果:
900d489a4c4c31235dc856b6a982c4e0ae1f53f14dbf04733107b51553562c0a
这个可以作为key。

/**
 * key  -----> Value(即Bitmap的封装)
 */
public class Key {

    /**
     *  key是合格的字符串:是唯一的, 是加密后的的
     *  符合这种格式的字符串即可:ac037ea49e34257dc5577d1796bb137dbaddc0e42a9dff051beee8ea457a4668
     *  而不能是普通的http格式的url
     */
    private String key;


    public Key(String path) {
        this.key = Tool.getSHA256StrJava(path);
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}

/**
 * Bitmap的封装
 */
public class Value {

    private final static String TAG = Value.class.getSimpleName();

    // 单例模式
    private static Value value;

    public static Value getInstance()  {
        if (null == value) {
            synchronized (Value.class) {
                if (null == value) {
                    value = new Value();
                }
            }
        }
        return value;
    }

    private Bitmap mBitmap; // 位图
    private int count; // 使用计数
    private ValueCallback callback; // 监听回调
    private String key; // key标记 唯一的

    // TODO 下面是 Get Set 系列
    public static Value getValue() {
        return value;
    }

    public static void setValue(Value value) {
        Value.value = value;
    }

    public Bitmap getmBitmap() {
        return mBitmap;
    }

    public void setmBitmap(Bitmap mBitmap) {
        this.mBitmap = mBitmap;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public ValueCallback getCallback() {
        return callback;
    }

    public void setCallback(ValueCallback callback) {
        this.callback = callback;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }


    /**
     * TODO 若Bitmap使用一次,则 计数一次 + 1
     *  因为可能有很多视图控件使用同一张图片
     */
    public void useAction() {
        Tool.checkNotEmpty(mBitmap); // 如果为null,就抛出异常

        if (mBitmap.isRecycled()) { // 已经被回收了
            Log.d(TAG, "useAction: 已经被回收了");
            return;
        }
        Log.d(TAG, "useAction: 加一 count:" + count);
        count ++ ;
    }

    /**
     * TODO  不使用一次(使用完成), 则 计数一次 - 1
     */
    public void nonUseAction() {
        // + 1 = 1
        // - 1 = 0

        count--;
        if (count <= 0 && callback != null) {
            // 证明我们的Value没有使用(管理回收)
            // 告诉外界,回调接口
            callback.valueNonUseListener(key, this); // 活动缓存管理监听
        }
        Log.d(TAG, "nonUseAction: 减一 count:" + count);
    }

    /**
     * TODO 释放
     */
    public void recycleBitmap() {
        if (count > 0) { // 正在使用中...
            Log.d(TAG, "recycleBitmap: 引用计数大于0,正在使用中...,不能释放");
            return;
        }

        if (mBitmap.isRecycled()) {
            Log.d(TAG, "recycleBitmap: 都已经被回收了,不能释放");
            return;
        }

        // 同学们自己增加
        // ...

        mBitmap.recycle();

        value = null;

        System.gc();
    }

}

Glide手写实现之活动缓存

在这里插入图片描述

活动缓存的几个关键因素:
1.回收机制:GC扫描的时候回收, 采用弱引用队列进行监听回收。如果没有View使用Bitmap,则只有弱引用持有Bitmap,这时候GC会回收Bitmap。(一旦弱引用实例所引用的对象被GC回收,JVM会把该弱引用实例放入引用队列,此时移除缓存容器中对应的弱引用实例。弱引用对象的存在不会阻止它所指向的对象被垃圾回收器回收) 【被动移除】
2.手动移除缓存项
3.专门的关闭线程方法
4.添加缓存项
5.Value 和 Callback 进行了关联

活动缓存与内存缓存的区别

活动缓存(ActiveCache):正在使用的图片(当前页面展示的所有图片),都放在活动缓存 (采用弱引用管理 ) 【资源封装 Key Value】

内存缓存:临时存放活动缓存不使用的Value(采用LRU最少使用算法管理) 【资源封装 Key Value】

活动缓存没有找到时去内存缓存中找。

为什么要活动缓存?

内存缓存:采用LRU算法管理,有maxsize限制,当超过maxsize时,最少使用的bitmap就会被回收。(不安全,不稳定)
活动缓存:采用弱引用管理,正在使用的图片肯定不会被回收(即只要有View持有这个bitmap,就不会回收这个bitmap),图片如果不用了才会在GC扫描时被回收。[存入 移除 非常快]

Glide手写实现之内存缓存

在这里插入图片描述

图片在活动缓存中被回收时会放入内存缓存。
被内存缓存回收时会放入磁盘缓存。

活动缓存 和 内存缓存:

在这里插入图片描述
活动缓存:正在使用, 内存缓存:最近使用,他们两者互斥的(二选一)。
活动缓存中被回收的图片会被移动到内存缓存,内存缓存中被命中的图片会被移动到活动缓存,同一张图片不可能在活动缓存和内存缓存中同时存在,所以说他们两者互斥的。

如果没有活动缓存,只有内存缓存,由于是LRU算法,有maxsize容量限制的问题,可能会有被移除的风险。如果只有内存缓存,试想一种场景:一个页面加载了很多图片,可能后面网络请求回来的图片放入内存缓存时发现内存缓存满了,所以会把最前面的图片移出内存缓存,而这个图片是当前页面的某个View正在使用的,却被移出了内存缓存,这样后续如果有其他View要使用这张图片,那么只能去磁盘缓存中读取,速度慢,这不符合我们的需求。

活动缓存是使用弱引用来持有图片,回收时使用的是JVM的GC机制,只有图片没有被View使用时,下次 GC 全盘扫描的时候才会被回收;如果有View正在使用这张图片,这张图片就不会从活动缓存中移除。

LRU算法:

在这里插入图片描述

  1. 假设maxSize为4,依次放入元素1,2,3,4,继续放入元素5,则当放入元素5时会将最先放入的元素1从缓存中删除。

  2. 假设maxSize为4,依次放入元素1,2,3,4,然后调用get(“2元素”)获取元素2时,元素2会被调整到队尾(此时队列中元素排列:1,3,4,2),然后继续放入元素5,此时出队的是元素1,继续放入元素6,此时出队的是元素3。

Bitmap 的值在内存复用的体现:

在这里插入图片描述
黑色区域表示原bitmap总共可以复用的内存,
红色区域表示新bitmap需要使用的内存(新bitmap比较小)。
如果使用bitmap.getByteCount();则获取到内存空间8作为复用的内存。
如果使用bitmap.getAllocationByteCount();;则获取到内存空间20作为复用的内存,即获取到原bitmap的所有可以复用的内存。

Bitmap有两个方法:getByteCount()和getAllocationByteCount()
bitmap.getByteCount();
bitmap.getAllocationByteCount();

如果不使用Bitmap的复用机制,这两个方法可以随意使用,用哪个都一样。
如果使用Bitmap的复用机制,这两个方法就有区分了。如上分析所示。

Glide手写实现之磁盘缓存

使用了DiskLruCache: https://github.com/JakeWharton/DiskLruCache

在这里插入图片描述

磁盘缓存中的数据

在这里插入图片描述

disk_lru_cache_dir是我们在SD卡中创建的缓存目录。图中的文件名称ac037ea…就是一张图片的key(经过哈希后的),文件里面的内容就是缓存图片的数据。

磁盘缓存的存放位置

以下目录均可:

  • SD卡
  • app的私有cache目录
  • app的私有data目录

Glide手写实现之生命周期

Glide.with(Context) 与当前的上下文绑定;通过上下文绑定一个Fragment实现管理生命周期

Context分为:

  1. Application 无法绑定生命周期

  2. FragmentActivity 可绑定生命周期

  3. Activity 可绑定生命周期

在这里插入图片描述
Glide在当前Activity的上面新建了个空的Fragment,从而实现对Activity的生命周期方法的监听管理,Activity如果销毁了一定会调用Activity上面的Fragment的onDestroy()方法,在这个方法里,Glide会释放Glide用到所有资源。所以我们就不用手动在Activity的onDestroy()方法里写注销Glide的相关代码了,Glide已经自动帮我们实现。

Glide手写实现之加载图片

在这里插入图片描述

缓存流程图:

加载图片简单版:
在这里插入图片描述
获取图片时会先去缓存中获取,如果缓存中没有读取到图片,则会去请求网络,网络返回图片时会存储到缓存中。

完整的流程图:
在这里插入图片描述

在这里插入图片描述
活动缓存用弱引用管理图片,GC扫描时,如果这个图片没有视图控件View在使用,则会回收这张图片。

Glide总结

磁盘缓存读取缓存成功后保存到活动缓存。

外置资源(非缓存中的资源,如网络资源、SD卡等)加载成功,保存到磁盘缓存。

退出当前页面时,会清空活动缓存,当再次进入该页面时第一次加载从内存缓存中加载,第二次加载从活动缓存中加载

1、缓存获取机制

首次冷启动的情况:

第一次的时候,去网络下载图片,保存到磁盘缓存中(/sd/disk_lru_cache_dir/key)
第二次的时候,直接在活动缓存中,找到了资源
第三次的时候,直接在活动缓存中,找到了资源
第N次的时候,直接在活动缓存中,找到了资源

退出当前Activity的情况:

把Activity给返回回去的时候,进行释放,活动缓存的释放
重新进入Activity加载的时候,从内存缓存中获取(并且把图片从内存缓存移动到活动缓存,下一次加载的时候,就是从活动缓存获取了)
第二次的时候,直接在活动缓存中,找到了资源
第三次的时候,直接在活动缓存中,找到了资源
第N次的时候,直接在活动缓存中,找到了资源

把App给杀掉的情况:

整个活动缓存,整个内存缓存,都没有了 (即整个运行内存缓存都没有了)
首次是冷启动, 所以从磁盘缓存中获取(这时图片会从磁盘缓存中复制一份到活动缓存中,注意这里是复制,不是移动)
第二次的时候,直接在活动缓存中,找到了资源
第三次的时候,直接在活动缓存中,找到了资源
第N次的时候,直接在活动缓存中,找到了资源

2、LRU算法 – DiskLruCache,LruCache

3、为什么有了 内存缓存 还需要 活动缓存 ?

活动:正在使用的图片,都放在活动缓存 (弱引用 GC 没有使用了 已回收 被动回收) 【资源封装 Key Value】

内存:LRU管理的,临时存放 活动缓存 不使用的Value(LRU最少使用算法) 【资源封装 Key Value】

为什么要活动缓存?
内存:LRU管理的,maxsize,如果最少使用,内部算法会回收(不安全,不稳定)

你正在使用的图片—【活动缓存】如果不用了 才会扫描时回收,[存入 移除 非常快]

4、Glide源码中提交Fragment之后为什么要调用Handler发送一个消息?

@1 ActivityThread
@2 构造
@3 事务 提交 实际上 源码 Handler 添加 到队列的

添加fragment就是利用Handler机制进行添加的,消息队列里面可能有很多的消息在排队,为了让添加fragment操作立即执行,所以调用Handler发送一次Message

5、活动缓存,用到了弱引用的监听

6、Glide 生命周期管理

Glide 生命周期管理 很独到,其他框架都需要在Activity的onDestroy()方法里写注销框架的代码,Glide自动管理。

7、Glide资源封装的引用计数

NDK里用引用计数做功能的场景很多,特别是游戏引擎。

8、Glide缓存流程

在这里插入图片描述

架构师学习–手写Glide之活动缓存
架构师学习–手写Glide之内存缓存
架构师学习–手写Glide之磁盘缓存
架构师学习–手写Glide之复用池

Glide手写实现之资源封装

Glide框架设计<一>-------Glide基本使用、缓存原理、内存缓存和活动资源手写实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值