zoukankan      html  css  js  c++  java
  • Glide图片加载过程(简)

    96 
    iceIC 
    2018.10.25 20:53* 字数 906 阅读 529评论 0

    调研版本为4.7.1
    为了更加简单的理解,会将函数代码简化,详细代码请自行照源码对比

    Glide用法

    Glide.with(...).load(...).into(...)
    我们从into方法开始探寻之旅


    开启加载线程

    RequestBuilder.class
    
    public Target into() {
        Request request = buildRequest(...);
        requestTracker.runRequest(request); 
    }
    

    requestTracker是RequestTracker对象

    RequestTracker.class
    
    public void runRequest(Request request) {
        request.begin();
    }
    

    request是SingleRequest对象

    SingleRequest.class
    
    public void begin() {
        onSizeReady(...);
    }
    
    public void onSizeReady(...) {
        engine.load(...)
    }
    

    engine是Engine类的对象

    Engine.class
    
    void load(...) {
        engineJob.start(...)
    }
    

    engineJob是EngineJob类的对象

    EngineJob.class
    
    public void start(DecodeJob job) {
        GlideExecutor executor = ... //创建线程池
        executor.execute(job); //job是一个runnable对象,此语句开启线程
    }
    

    获取资源过程

    上面我们知道了开启线程的过程,这个线程主要就是获取二进制流并且编码成资源文件,如Bitmap、Drawable等用来在Android中显示。

    DecodeJob.class
    
    //线程从run方法开始
    public void run() {
        runWrapped();
    }
    
    private void runWrapped() {
        runGenerators();
    }
    
    private void runGenerators() {
        currentGenerator = getNextGenerator();
        //依次调用generator的startNext方法
        //startNext返回值表示是否可以获得图片的二进制流
        //如果generator能获取图片数据,那么就不需要下级的generator去尝试获取图片数据
        while (!currentGenerator.startNext()) {
            ...
            currentGenerator = getNextGenerator();
        }
    }
    
    //第一次调用该方法返回ResourceCacheGenerator
    //第二次返回DataCacheGenerator
    //第三次返回SourceGenerator(本地没有缓存时使用,下载数据并放入缓存)
    private DataFetcherGenerator getNextGenerator() {
        switch (stage) {
            case RESOURCE_CACHE:
                return new ResourceCacheGenerator(decodeHelper, this);
            case DATA_CACHE:
                return new DataCacheGenerator(decodeHelper, this);
            case SOURCE:
                return new SourceGenerator(decodeHelper, this);
        }
    }
    

    本例是一个imageView加载网络图,ResourceCacheGenerator的startNext为false,这样会调用DataCacheGenerator的startNext方法。

    DataCacheGenerator.class
    
        //startNext方法会通过循环所有的modelLoader分别调用buildLoadData方法
        //如果所有modelLoader获取的loadData都为空那么就返回false
        //这就意味着Glide将调用下一个Generator的startNext方法
        //也就是SourceGenerator的startNext方法
        public boolean startNext() {
            Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
            File cacheFile = helper.getDiskCache().get(originalKey);
    
            boolean started = false;
            while (!started && hasNextModelLoader()) {
                //我们自定义的ModelLoader就可以在这派上用场了
                ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++);
                ModelLoader.LoadData loadData = modelLoader.buildLoadData(...);
                if (loadData != null) {
                    started = true;
                    //本例中ByteBufferFileLoader获取的loadData不为空
                    //本例中loadData.fetcher是ByteBufferFetcher的对象
                    loadData.fetcher.loadData(this);
                }
            }
            return started;
        }
    
    ByteBufferFetcher.class
    
    public void loadData(DataFetcher.DataCallback<? super ByteBuffer> callback) {
        ByteBuffer result;
        try {
            result = ByteBufferUtil.fromFile(file);
        } catch (IOException e) {
            callback.onLoadFailed(e);
            return;
        }
        callback.onDataReady(result);
    }
    

    这里的callback是DataCacheGenerator对象,该类实现了DataFetcher.DataCallback接口,所以此处回调到DataCacheGenerator类中的onDataReady方法

    DataCacheGenerator.class
    
    public void onDataReady(Object data) {
        cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
    }
    

    cb是DecodeJob类对象

    DecodeJob.class
    
    public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
                                   DataSource dataSource, Key attemptedKey) {
                //将二进制流解码成资源文件
                decodeFromRetrievedData();
        }
    }
    
    private void decodeFromRetrievedData() {
        Resource<R> resource = null;
            resource = decodeFromData(currentFetcher, currentData, currentDataSource);
        if (resource != null) {
            notifyEncodeAndRelease(resource, currentDataSource);
        }
    }
    

    notifyEncodeAndRelease方法里主要是使用Handler回到主线程调用各种回调以及释放资源。至此,加载过程就分析完毕啦。


    在上面我们已经完整的走过了一个加载流程,但是Glide缓存的文件是什么形式的呢?如果一个图片的url被Glide加载缓存过,我们是不是可以通过该url获取缓存文件呢?获取的缓存文件是不是还是.jpg、.png等图片形式的可以直接从手机提取到电脑呢?如果你有这些疑问,那就继续往下看吧~

    Glide缓存机制

    Glide默认缓存在 data/data/包名/cache/image_manager_disk_cache 文件夹下,并且缓存文件的文件名根本不能直观上和请求的图片URL所匹配:
    类似这种021db417987c5c446d2042037c78e9d9c127432d5a8cdd2b58cd2a702fc917b3.0,后缀还是 .0 。是不是有点头大,让我们去看看文件名的生成规则吧。

     
    缓存文件夹

    Glide使用DiskLruCacheWrapper类进行缓存文件操作,我们主要看put和get方法,一个用于取缓存,一个用于存缓存。

    DiskLruCacheWrapper.class
    
    public File get(Key key) {
        String safeKey = safeKeyGenerator.getSafeKey(key);
        File result = null;
        final DiskLruCache.Value value = getDiskCache().get(safeKey);
        result = value.getFile(0);
        return result;
    }
    
    public void put(Key key, DiskCache.Writer writer) {
        String safeKey = safeKeyGenerator.getSafeKey(key);
        DiskLruCache diskCache = getDiskCache();
        DiskLruCache.Editor editor = diskCache.edit(safeKey);
        File file = editor.getFile(0);
        if (writer.write(file)) {
            editor.commit();
        }
    }
    
    

    safeKeyGenerator是SafeKeyGenerator类的对象,其getSafeKey方法主要通过图片url获取缓存文件名,getSafeKey需要传入一个Key对象,Key对象就包括了要加载的url。

    SafeKeyGenerator.class
    
    public String getSafeKey(Key key) {
        return calculateHexStringDigest(key);
    }
    
    private String calculateHexStringDigest(Key key) {
        return Util.sha256BytesToHex(container.messageDigest.digest());
    }
    

    这里打个断点让大家看看Key里都有什么成员变量


     
    Key.class

    calculateHexStringDigest方法也很简单,就是对该URL使用SHA256加密算法加密

    为了让大家理解,这里贴两张图,第一张图是将图片地址字符串进行加密计算结果图,第二张图是手机缓存文件夹图片


     

     

    那么后缀 .0 是怎么生成的呢?

    我们可以看到在调用DiskLruCacheWrapper的put函数保存缓存文件时有一句代码
    DiskLruCache.Editor editor = diskCache.edit(safeKey);

    其中diskCache是DiskLruCache对象

    DiskLruCache.class
    
    private DiskLruCache.Editor edit(String key) {
        DiskLruCache.Entry entry = lruEntries.get(key);
        if (entry == null) {
            entry = new DiskLruCache.Entry(key);
            lruEntries.put(key, entry);
        }
    }
    

    注意了,这里的lruEntries是一个LinkedHashMap,里面存的就是图片url和DiskLruCache.Entry,其中DiskLruCache.Entry就存有缓存文件名!

    可以看到Glide会优先从该Map中取,如果没有找到则自己创建DiskLruCache.Entry然后再放入Map

    我们可以直接看DiskLruCache.Entry的构造函数

    private Entry(String key) {
        cleanFiles = new File[valueCount];
    
        StringBuilder fileBuilder = new StringBuilder(key).append('.');
        for (int i = 0; i < valueCount; i++) {
            fileBuilder.append(i);
            cleanFiles[i] = new File(directory, fileBuilder.toString());
        }
    }
    

    这里cleanFiles就存放着缓存文件,其中valueCount=1,至于valueCount为什么是1是因为调用DiskLruCache的静态方法open public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 时传的valueCount参数为1

    那么既然valueCount为1,上面代码应该可以简化了吧?

    private Entry(String key) {
        cleanFiles = new File[1];
        StringBuilder fileBuilder = new StringBuilder(key).append('.');
        fileBuilder.append(0);
        cleanFiles[0] = new File(directory, fileBuilder.toString());
    }
    

    所以看了这么多,想到怎么用url获取本地图片了嘛?
    另外,试试把 xxx.0 换成 xxx.png ~

  • 相关阅读:
    四十八.监控概述 、 Zabbix基础 、 Zabbix监控服务
    123D
    bzoj3879
    bzoj1699
    LA6878
    uoj#149
    687C
    codeforces round #424 div2
    803E
    713C
  • 原文地址:https://www.cnblogs.com/yelanggu/p/10831507.html
Copyright © 2011-2022 走看看