繼續接着上一次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的創建下次再繼續。
