• 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的创建下次再继续。

  • 相关阅读:
    How to add a button in the seletions "More"
    Tags Used In OpenERP 7.0
    OpenERP Web Client设置闲置有效时间
    OpenERP7.0中非admin帐号新增其它用户问题
    Docker 使用docker-compose部署项目
    Docker 安装docker-compose多容器管理服务
    Jenkins集成Docker实现镜像构建和线上发布
    Centos下安装JDK、Maven和Git
    服务注册发现与调度
    Spring boot centos部署启动停止脚本
  • 原文地址:https://www.cnblogs.com/webor2006/p/12322227.html
Copyright © 2020-2023  润新知