Glide框架设计<二>-------Glide复用池、磁盘缓存、图片资源加载手写实现

继续接着上一次https://www.cnblogs.com/webor2006/p/12313876.html的缓存进行编写。

Bitmap复用池:

概念:

关于啥是Bitmap复用,这里还是借用博主的这篇文章https://www.jianshu.com/p/97fd67720b34的说明过一下,先来看两张对比图:

复用前:

 

可以看到每一张Bitmp在显示时都申请了一块内存,而如果采用复用Bitmap的情况,则会长成这样:

也就是如果采用了复用之后,如果存在能被复用的图片会重复使用该图片的内存。那复用之后除了减少内存的开销之外,还有另一层意义:

具体实现:

在上一次的LruMemoryCache中还有一块待完善的,就是移出的方法还木有写,如下:

先把它完善一下:

另外关于这里面的移除还分为主动和被动移除,如果是调了上面这个remove2很显示是主动移除,此时就不应该回调这个回调了:

为啥?因为如果回调了此方法,是需要将这个资源往复用池中添加的,如果是主动要移除的资源就没必要往复用池中放了;而如果是被动移除的像LruCache中已经达到了最大上限了再添加则就会将最少使用的给移除掉,此时移除掉的就需要放到复用池当中,所以这里需要加个标识来判断一下,如下:

好,接下来则来开始定义咱们的复用池了,先定义相关的接口:

然后定义具体类:

package com.android.glidearcstudy.glide.recycle;

import android.graphics.Bitmap;

import androidx.collection.LruCache;

public class LruBitmapPool extends LruCache<Integer, Bitmap> implements BitmapPool {

    private final static int MAX_OVER_SIZE_MULTIPLE = 2;

    public LruBitmapPool(int maxSize) {
        super(maxSize);
    }

    /**
     * 将Bitmap放入复用池
     */
    @Override
    public void put(Bitmap bitmap) {
        //TODO
    }

    /**
     * 获得一个可复用的Bitmap
     */
    @Override
    public Bitmap get(int width, int height, Bitmap.Config config) {
        return null;
    }


    @Override
    protected int sizeOf(Integer key, Bitmap value) {
        return value.getAllocationByteCount();
    }

    @Override
    protected void entryRemoved(boolean evicted, Integer key, Bitmap oldValue, Bitmap newValue) {
        //TODO
    }
}

它也是一个LruCache,接下来具体来实现一下,先实现sizeOf(),跟上次的内存缓存一样:

然后再来实现一下存放方法:

接下来实现get方法,这里从复用池中的所有可复用的bitmap中要找到一个近视差不多大小的图才行,如果要显示的图的大小在复用池中找不到,那此时就不能从复用池来拿了,实现如下:

图中打问号的则是我们接下来要处理的,如何从复用池中来获取跟我们要申请图片大小近视大的呢?当然我们可以用遍历的方法来实现它,但是这里可以借助于不怎么常用的一个Map比较方便的实现,如下:

其中NavigableMap是个啥东东,看一下官方的解释:

而我们调用的是这个方法:

好,接下来继续完善这个get()方法:

好,接下来咱们来应用一下复用池:

public class CacheTest implements Resource.OnResourceListener, MemoryCache.ResourceRemoveListener {

    LruMemoryCache lruMemoryCache;
    ActiveResources activeResource;
    BitmapPool bitmapPool;

    public Resource test(Key key) {
        bitmapPool = new LruBitmapPool(10);
        //内存缓存
        lruMemoryCache = new LruMemoryCache(10);
        lruMemoryCache.setResourceRemoveListener(this);
        //活动资源缓存
        activeResource = new ActiveResources(this);

        /**
         * 第一步 从活动资源中查找是否有正在使用的图片
         */
        Resource resource = activeResource.get(key);
        if (null != resource) {
            //当不使用的时候 release
            resource.acquire();
            return resource;
        }
        /**
         * 第二步 从内存缓存中查找
         */
        resource = lruMemoryCache.get(key);
        if (null != resource) {
            //1.为什么从内存缓存移除?
            // 因为lru可能移除此图片 我们也可能recycle掉此图片
            // 如果不移除,则下次使用此图片从活动资源中能找到,但是这个图片可能被recycle掉了
            lruMemoryCache.remove2(key);//从内存缓存中移除
            resource.acquire();
            activeResource.activate(key, resource);//再加入到活动资源缓存中
            return resource;
        }
        return null;
    }

    /**
     * 这个资源没有正在使用了
     * 将其从活动资源移除
     * 重新加入到内存缓存中
     */
    @Override
    public void onResourceReleased(Resource resource) {
        //TODO
    }

    /**
     * 从内存缓存被动移除
     * 此时得放入复用池
     */
    @Override
    public void onResourceRemoved(Resource resource) {
        bitmapPool.put(resource.getBitmap());
    }
}

最后onResourceReleased()代表该资源没有在使用了,此时需要将它加入到内存缓存中,这里的回调还是增加一个key参数,所以修改一下回调方法:

至此,关于内存缓存和活动资源缓存的代码就写完了。

磁盘缓存:

当内存缓存木有找到我们想要的图片之后,接下来则需要从磁盘缓存开找了,对于glide官方而言,磁盘缓存也是使用三方开源的,看一下:

这里就不详细看了,代码量也有点大,先将其拷进工程来直接当工具类来使用既可:

大致瞅一下,其实它也是一个LRU算法,只是说是自己实现了一个,而不是继承至LruCache来实现的:

也有像LruCache的trimToSize()的实现:

 

接下来咱们利用这个三方开源的磁盘缓存类来使用一下,先新建一个接口:

接下来具体实现一下,也是大致的过一下,跟缓存的实现差不多:

package com.android.glidearcstudy.glide.cache;

import android.content.Context;

import androidx.annotation.Nullable;

import com.android.glidearcstudy.glide.Utils;
import com.android.glidearcstudy.glide.disklrucache.DiskLruCache;

import java.io.File;
import java.io.IOException;
import java.security.MessageDigest;

public class DiskLruCacheWrapper implements DiskCache {


    final static int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
    final static String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

    private MessageDigest MD;
    private DiskLruCache diskLruCache;

    public DiskLruCacheWrapper(Context context) {
        this(new File(context.getCacheDir(), DEFAULT_DISK_CACHE_DIR), DEFAULT_DISK_CACHE_SIZE);
    }

    protected DiskLruCacheWrapper(File directory, long maxSize) {
        try {
            MD = MessageDigest.getInstance("SHA-256");
            //打开一个缓存目录,如果没有则首先创建它,
            // directory:指定数据缓存地址
            // appVersion:APP版本号,当版本号改变时,缓存数据会被清除
            // valueCount:同一个key可以对应多少文件
            // maxSize:最大可以缓存的数据量
            diskLruCache = DiskLruCache.open(directory, 1, 1, maxSize);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getKey(Key key) {
        key.updateDiskCacheKey(MD);
        return new String(Utils.sha256BytesToHex(MD.digest()));
    }

    @Nullable
    @Override
    public File get(Key key) {
        String k = getKey(key);
        File result = null;
        try {
            DiskLruCache.Value value = diskLruCache.get(k);
            if (value != null) {
                result = value.getFile(0);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public void put(Key key, Writer writer) {
        String k = getKey(key);
        try {
            DiskLruCache.Value current = diskLruCache.get(k);
            if (current != null) {
                return;
            }
            DiskLruCache.Editor editor = diskLruCache.edit(k);
            try {
                File file = editor.getFile(0);
                if (writer.write(file)) {
                    editor.commit();
                }
            } finally {
                editor.abortUnlessCommitted();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void delete(Key key) {
        String k = getKey(key);
        try {
            diskLruCache.remove(k);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void clear() {
        try {
            diskLruCache.delete();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            diskLruCache = null;
        }
    }
}

其中用到了一个加密:

其中我们的Key接口需要定义一下:

然后它具体的实现类有两个:

实现也比较简单:

package com.android.glidearcstudy.glide.load;

import com.android.glidearcstudy.glide.cache.Key;

import java.security.MessageDigest;

public class EngineKey implements Key {

    private final Object model;
    private final int width;
    private final int height;

    public EngineKey(Object model, int width, int height) {
        this.model = model;
        this.width = width;
        this.height = height;
    }

    @Override
    public void updateDiskCacheKey(MessageDigest messageDigest) {
        messageDigest.update(getKeyBytes());
    }

    @Override
    public byte[] getKeyBytes() {
        return toString().getBytes();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EngineKey engineKey = (EngineKey) o;

        if (width != engineKey.width) return false;
        if (height != engineKey.height) return false;
        return model != null ? model.equals(engineKey.model) : engineKey.model == null;
    }

    @Override
    public int hashCode() {
        int result = model != null ? model.hashCode() : 0;
        result = 31 * result + width;
        result = 31 * result + height;
        return result;
    }

    @Override
    public String toString() {
        return "EngineKey{" +
                "model=" + model +
                ", width=" + width +
                ", height=" + height +
                '}';
    }
}
package com.android.glidearcstudy.glide.load;

import com.android.glidearcstudy.glide.cache.Key;

import java.security.MessageDigest;

public class ObjectKey implements Key {

    private final Object object;

    public ObjectKey(Object object) {
        this.object = object;
    }

    /**
     * 当磁盘缓存的时候 key只能是字符串
     * ObjectKey变成一个字符串
     * 序列化:json
     * <p>
     * 将ObjectKey转变成一个字符串的手段
     *
     * @param md md5/sha1
     */
    @Override
    public void updateDiskCacheKey(MessageDigest md) {
        md.update(getKeyBytes());
    }

    @Override
    public byte[] getKeyBytes() {
        return object.toString().getBytes();
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ObjectKey objectKey = (ObjectKey) o;

        return object != null ? object.equals(objectKey.object) : objectKey.object == null;
    }

    @Override
    public int hashCode() {
        return object != null ? object.hashCode() : 0;
    }
}

关于磁盘缓存先大致过到这。

图片资源加载:

接下来则是图片加载的处理了,图片加载有N种方式:

所以咱们需要将其抽像一下,如下:

package com.android.glidearcstudy.glide.load.model;


import com.android.glidearcstudy.glide.cache.Key;
import com.android.glidearcstudy.glide.load.model.data.DataFetcher;

/**
 * @param <Model> 此泛型表示的是数据的来源
 * @param <Data>  此泛型加载成功后的数据类型,比如InputStream、byte[]
 */
public interface ModelLoader<Model, Data> {

    class LoadData<Data> {
        //缓存的key
        public final Key key;
        //加载数据
        public final DataFetcher<Data> fetcher;

        public LoadData(Key key, DataFetcher<Data> fetcher) {
            this.key = key;
            this.fetcher = fetcher;
        }
    }

    /**
     * 此Loader是否能够处理对应Model的数据
     */
    boolean handles(Model model);

    /**
     * 创建加载数据
     */
    LoadData<Data> buildData(Model model);
}

其中DataFetcher也是一个接口,不同的形式加载是不一样的:

package com.android.glidearcstudy.glide.load.model.data;

/**
 * 负责数据获取
 */
public interface DataFetcher<Data> {

    interface DataFetcherCallback<Data> {
        /**
         * 数据加载完成
         */
        void onFetcherReady(Data data);

        /**
         * 加载失败
         */
        void onLoadFaled(Exception e);
    }

    void loadData(DataFetcherCallback<? super Data> callback);

    void cancel();

    Class<?> getDataClass();

}

好,下面来实现一下具体的数据获取的方式,首先是从网络上获取,最终返回是一个InputStream,所以此时实现如下:

package com.android.glidearcstudy.glide.load.model.data;

import android.net.Uri;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * 网络数据加载
 */
public class HttpUriFetcher implements DataFetcher<InputStream> {

    private final Uri uri;
    private boolean isCanceled;

    public HttpUriFetcher(Uri uri) {
        this.uri = uri;
    }

    @Override
    public void loadData(DataFetcherCallback<? super InputStream> callback) {
        HttpURLConnection conn = null;
        InputStream is = null;
        try {
            URL url = new URL(uri.toString());
            conn = (HttpURLConnection) url.openConnection();
            conn.connect();
            is = conn.getInputStream();
            int responseCode = conn.getResponseCode();
            if (isCanceled) {
                return;
            }
            if (responseCode == HttpURLConnection.HTTP_OK) {
                callback.onFetcherReady(is);
            } else {
                callback.onLoadFaled(new RuntimeException(conn.getResponseMessage()));
            }
        } catch (Exception e) {
            callback.onLoadFaled(e);
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != conn) {
                conn.disconnect();
            }
        }
    }

    @Override
    public void cancel() {
        isCanceled = true;
    }

    @Override
    public Class<InputStream> getDataClass() {
        return InputStream.class;
    }
}

而文件数据加载则是:

package com.android.glidearcstudy.glide.load.model.data;

import android.content.ContentResolver;
import android.net.Uri;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * 文件数据加载
 */
public class FileUriFetcher implements DataFetcher<InputStream> {

    private final Uri uri;
    private final ContentResolver cr;

    public FileUriFetcher(Uri uri, ContentResolver cr) {
        this.uri = uri;
        this.cr = cr;
    }

    @Override
    public void loadData(DataFetcherCallback<? super InputStream> callback) {
        InputStream is = null;
        try {
            is = cr.openInputStream(uri);
            callback.onFetcherReady(is);
        } catch (FileNotFoundException e) {
            callback.onLoadFaled(e);
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void cancel() {

    }

    @Override
    public Class<InputStream> getDataClass() {
        return InputStream.class;
    }
}

好,对于之前定义的ModelLoader定义了接口还木有定义实现类,下面针对HTTP的来实现一下:

package com.android.glidearcstudy.glide.load.model;

import android.net.Uri;

import com.android.glidearcstudy.glide.load.ObjectKey;
import com.android.glidearcstudy.glide.load.model.data.HttpUriFetcher;

import java.io.InputStream;

public class HttpUriLoader implements ModelLoader<Uri, InputStream> {

    /**
     * http类型的uri此loader才支持,只支持http和https
     */
    @Override
    public boolean handles(Uri uri) {
        String scheme = uri.getScheme();
        return scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https");
    }

    @Override
    public LoadData<InputStream> buildData(Uri uri) {
        return new LoadData<InputStream>(new ObjectKey(uri), new HttpUriFetcher(uri));
    }

}

可以看到这个ModelLoader就已经将要加载的所有东东都组装好,那怎么用呢?下面用测试类来试一下:

可见使用非常之灵活,目前只实现了一个Http的加载器,还有其它的加载类型只要对ModelLoader进行拓展既可,基实可以看一下glide官方其实拓展了N多个Loader,可以大致瞅一下:

重点是要学会这种基础灵活的框架搭建的思路,目前先只创建了一个http的加载器,关于其它的Loader的创建下次再继续。

原文地址:https://www.cnblogs.com/webor2006/p/12322227.html