Android | Glide細枝篇

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://juejin.im/post/5f0ec887e51d45349917c614","title":""},"content":[{"type":"text","text":"《看完不忘系列》之Glide"}]},{"type":"text","text":" (樹幹篇)一文對"},{"type":"codeinline","content":[{"type":"text","text":"Glide"}]},{"type":"text","text":"加載圖片的核心流程做了介紹,細枝篇作爲補充,將對一些具體實現細節進行深入。本文篇幅略大,大家可以根據目錄索引到感興趣的章節閱讀~"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"源碼基於最新版本"},{"type":"codeinline","content":[{"type":"text","text":"4.11.0"}]},{"type":"text","text":",先上一張職責圖預覽下,一家人就要整整齊齊~"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/12/123641426bb04181171d0896a24e5d5e.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文約3200字,閱讀大約10分鐘。如個別大圖模糊(官方會壓縮),可前往"},{"type":"link","attrs":{"href":"https://imholiday.cn/","title":""},"content":[{"type":"text","text":"個人站點"}]},{"type":"text","text":"閱讀"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Generated API"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過創建一些類,繼承相關接口,然後打上註解,由apt來處理這些類,從而實現接口擴展。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"全局配置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"註解"},{"type":"codeinline","content":[{"type":"text","text":"@GlideModule"}]},{"type":"text","text":"用來配置全局參數和註冊定製的能力,在application裏使用"},{"type":"codeinline","content":[{"type":"text","text":"AppGlideModule"}]},{"type":"text","text":",在library裏使用"},{"type":"codeinline","content":[{"type":"text","text":"LibraryGlideModule"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@GlideModule\npublic class MyAppGlideModule extends AppGlideModule {\n @Override\n public boolean isManifestParsingEnabled() {\n return false;//新版本不需要解析manifest裏的元數據(沒用過老版本,不太懂,按文檔返回false即可)\n }\n\n @Override\n public void applyOptions(Context context, GlideBuilder builder) {\n super.applyOptions(context, builder);\n //全局配置\n //builder.setBitmapPool(xxx);\n //builder.setDefaultRequestOptions(xxx);\n //...\n }\n\n @Override\n public void registerComponents(Context context, Glide glide, Registry registry) {\n super.registerComponents(context, glide, registry);\n //註冊一些定製的能力,比如擴展新的圖片來源ModelLoader\n //registry.register(xxx);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如現在的"},{"type":"codeinline","content":[{"type":"text","text":"Glide"}]},{"type":"text","text":"的Bitmap默認配置是"},{"type":"codeinline","content":[{"type":"text","text":"ARGB_8888"}]},{"type":"text","text":",如果項目圖片類型比較單一,不需要透明度通道和高色域,可以配置全局的"},{"type":"codeinline","content":[{"type":"text","text":"RGB_565"}]},{"type":"text","text":"減少一半內存。見"},{"type":"link","attrs":{"href":"https://muyangmin.github.io/glide-docs-cn/doc/configuration.html#%E9%BB%98%E8%AE%A4%E8%AF%B7%E6%B1%82%E9%80%89%E9%A1%B9","title":""},"content":[{"type":"text","text":"默認請求選項"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@GlideModule\npublic class MyAppGlideModule extends AppGlideModule {\n\n @Override\n public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {\n super.applyOptions(context, builder);\n builder.setDefaultRequestOptions(new RequestOptions()\n .format(DecodeFormat.PREFER_RGB_565));\n //注:由於png需要透明度通道,這類圖依舊會採用8888\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"或者可以根據設備評分來衡量,普通機型配置"},{"type":"codeinline","content":[{"type":"text","text":"RGB_565"}]},{"type":"text","text":"(在需要透明度通道的場景局部使用"},{"type":"codeinline","content":[{"type":"text","text":"ARGB_8888"}]},{"type":"text","text":"),高端機型則可以直接配置"},{"type":"codeinline","content":[{"type":"text","text":"ARGB_8888"}]},{"type":"text","text":",縱享奢華體驗。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a9/a9847b6e358b20d437b3f2ca8a583456.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"行爲打包"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"註解"},{"type":"codeinline","content":[{"type":"text","text":"@GlideExtension"}]},{"type":"text","text":"可以將一些通用行爲打包起來,擴展一個接口方便業務層調用。比如電商App很多頁面都有商品列表,這些商品圖片的寬高如果是固定的,就可以包裝起來,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@GlideExtension\npublic class MyAppExtension {\n private static final int GOODS_W = 300; //商品圖寬度\n private static final int GOODS_H = 400; //商品圖高度\n\n private MyAppExtension() { //私有化構造方法\n }\n\n @GlideOption\n public static BaseRequestOptions> goods(BaseRequestOptions> options) {\n return options\n .fitCenter()\n .override(GOODS_W, GOODS_H) //寬高\n .placeholder(R.mipmap.ic_launcher) //商品佔位圖\n .error(R.mipmap.ic_launcher); //商品圖加載失敗時\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"rebuild一下項目,生成類"},{"type":"codeinline","content":[{"type":"text","text":"build/generated/ap_generated_sources/debug/out/com/holiday/srccodestudy/glide/GlideOptions.java"}]},{"type":"text","text":",裏面會多出一個方法,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"class GlideOptions extends RequestOptions implements Cloneable {\n public GlideOptions goods() {\n return (GlideOptions) MyAppExtension.goods(this);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這時,就可以用goods來直接使用這一組打包好的行爲了,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//要用GlideApp\nGlideApp.with(this).load(url).goods().into(img);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Generated API"}]},{"type":"text","text":"比較適合短週期/小型項目,中大型項目往往不會直接裸使用"},{"type":"codeinline","content":[{"type":"text","text":"Glide"}]},{"type":"text","text":",會包一箇中間層來進行隔離(禁止業務層用到"},{"type":"codeinline","content":[{"type":"text","text":"Glide"}]},{"type":"text","text":"的任何類),以便隨時可以升級替換,這個中間層就可以根據需要來自行擴展。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"空Fragment取消請求"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Glide.with(context),當context是Activity時,每個頁面都會被添加一個空fragment,由空fragment持有"},{"type":"codeinline","content":[{"type":"text","text":"頁面級別RequestManager"}]},{"type":"text","text":"來管理請求,那退出頁面時是如何取消請求的呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"with通過"},{"type":"codeinline","content":[{"type":"text","text":"RequestManagerRetriever"}]},{"type":"text","text":"獲取"},{"type":"codeinline","content":[{"type":"text","text":"SupportRequestManagerFragment"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//SupportRequestManagerFragment.java\n//創建SupportRequestManagerFragment\npublic SupportRequestManagerFragment() {\n //創建Lifecycle\n this(new ActivityFragmentLifecycle());\n}\n\n//RequestManager.java\n//創建RequestManager,傳入Lifecycle\nRequestManager(\n Glide glide,\n Lifecycle lifecycle,\n //...\n Context context) {\n //lifecycle添加RequestManager爲觀察者\n lifecycle.addListener(this);\n}\n\n//ActivityFragmentLifecycle.java\npublic void addListener(LifecycleListener listener) {\n //記錄觀察者們\n lifecycleListeners.add(listener);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"退出頁面時,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//SupportRequestManagerFragment.java\npublic void onDestroy() {\n lifecycle.onDestroy();\n}\n\n//ActivityFragmentLifecycle.java\nvoid onDestroy() {\n for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {\n lifecycleListener.onDestroy();\n }\n}\n\n//RequestManager.java\npublic synchronized void onDestroy() {\n //各種取消、註銷操作\n targetTracker.onDestroy();\n for (Target> target : targetTracker.getAll()) {\n clear(target);\n }\n targetTracker.clear();\n requestTracker.clearRequests();\n lifecycle.removeListener(this);\n lifecycle.removeListener(connectivityMonitor);\n mainHandler.removeCallbacks(addSelfToLifecycle);\n glide.unregisterRequestManager(this);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼看起來有點繞,大致如下圖,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/70/704b49a4432f5fd1f001ffdf7a72c658.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Cache緩存"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"內存"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內存緩存有兩級,一是處於活躍狀態,正被view使用着的緩存,稱"},{"type":"codeinline","content":[{"type":"text","text":"活躍資源"}]},{"type":"text","text":";二是沒被view使用的,就叫他"},{"type":"codeinline","content":[{"type":"text","text":"非活躍資源"}]},{"type":"text","text":"吧,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讀取內存:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//Engine.java\npublic LoadStatus load(...){\n //獲取內存緩存\n memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);\n}\n\nprivate EngineResource> loadFromMemory(\n EngineKey key, boolean isMemoryCacheable, long startTime) {\n\t//活躍資源,從ActiveResources的Map中獲取\n //Map activeEngineResources,值是弱引用,會手動計數\n EngineResource> active = loadFromActiveResources(key);\n if (active != null) {\n return active;\n }\n\t//非活躍資源,從LruResourceCache獲取,也有手動計數\n //返回後,說明這個緩存被view給用上了,非活躍資源則變成活躍\n EngineResource> cached = loadFromCache(key);\n if (cached != null) {\n return cached;\n }\n //內存沒有緩存,load就會去請求\n return null;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寫入內存:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//Engine.java\npublic synchronized void onEngineJobComplete(\n EngineJob> engineJob, Key key, EngineResource> resource) {\n if (resource != null && resource.isMemoryCacheable()) {\n //簡單理解,就是圖片加載完成,這時寫入活躍資源的\n activeResources.activate(key, resource);\n }\n}\n\npublic void onResourceReleased(Key cacheKey, EngineResource> resource) {\n //活躍資源已經沒有被引用了,就移出\n activeResources.deactivate(cacheKey);\n if (resource.isMemoryCacheable()) {\n //轉入非活躍資源\n cache.put(cacheKey, resource);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/64/64cac3a019594c23e23548956c29329e.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"磁盤"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看看緩存目錄"},{"type":"codeinline","content":[{"type":"text","text":"/data/data/com.holiday.srccodestudy/cache/image_manager_disk_cache/"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3e/3e9d1874af1976767b4299bd97d9dfe3.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先看日誌文件"},{"type":"codeinline","content":[{"type":"text","text":"journal"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"libcore.io.DiskLruCache //頭部名字\n1 //磁盤緩存版本\n1 //App版本\n1 //每個entry(日誌條目)存放的文件數,默認爲1,即一個entry對應一個圖片文件,比如下面就有4個entry,即4張圖片\n\nDIRTY 64d4b00d8ce8b0942d53b3048d5cf6aaa7173acd321e17420891bbc35b98629f\nCLEAN 64d4b00d8ce8b0942d53b3048d5cf6aaa7173acd321e17420891bbc35b98629f 5246\nDIRTY 2c23e32bd9b208092b3cbee8db6f1aff5bc11cb0d4ebd092604ee53099beff37\nCLEAN 2c23e32bd9b208092b3cbee8db6f1aff5bc11cb0d4ebd092604ee53099beff37 404730\nREAD 64d4b00d8ce8b0942d53b3048d5cf6aaa7173acd321e17420891bbc35b98629f\nREAD 2c23e32bd9b208092b3cbee8db6f1aff5bc11cb0d4ebd092604ee53099beff37\nDIRTY b566e62aa0e2fb8cb219ad3aa7a0ade9a96521526501ccd775d70aa4f6489272\nCLEAN b566e62aa0e2fb8cb219ad3aa7a0ade9a96521526501ccd775d70aa4f6489272 9878\nREAD 2c23e32bd9b208092b3cbee8db6f1aff5bc11cb0d4ebd092604ee53099beff37\nREAD b566e62aa0e2fb8cb219ad3aa7a0ade9a96521526501ccd775d70aa4f6489272\nDIRTY 55f4af9c1020e3272ce8063c351aff3518f3a1c9508f38345eab27686e263a4c\nCLEAN 55f4af9c1020e3272ce8063c351aff3518f3a1c9508f38345eab27686e263a4c 69284\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下半部分是操作記錄,行開頭指操作行爲,"},{"type":"codeinline","content":[{"type":"text","text":"DIRTY"}]},{"type":"text","text":"表示在編輯(處於髒數據狀態,別讀),"},{"type":"codeinline","content":[{"type":"text","text":"CLEAN"}]},{"type":"text","text":"(乾淨狀態)表示寫好了,可以讀了,"},{"type":"codeinline","content":[{"type":"text","text":"READ"}]},{"type":"text","text":"表示被讀入了,"},{"type":"codeinline","content":[{"type":"text","text":"REMOVE"}]},{"type":"text","text":"則表示被刪除,中間很長的一串字符就是緩存鍵或文件名字,最後的數字是文件大小,如404730 B=395.2 KB,只有處於"},{"type":"codeinline","content":[{"type":"text","text":"CLEAN"}]},{"type":"text","text":"狀態纔會寫大小。那麼圖中的文件名是什麼意思,爲啥key的後面還有"},{"type":"codeinline","content":[{"type":"text","text":".0"}]},{"type":"text","text":"後綴?因爲一個"},{"type":"codeinline","content":[{"type":"text","text":"entry"}]},{"type":"text","text":"(日誌條目)可以對應多個圖片,"},{"type":"codeinline","content":[{"type":"text","text":".0"}]},{"type":"text","text":"代表"},{"type":"codeinline","content":[{"type":"text","text":"entry"}]},{"type":"text","text":"的第一張圖片,如果有配置1對多,那就會有"},{"type":"codeinline","content":[{"type":"text","text":".1"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":".2"}]},{"type":"text","text":"這樣的後綴。選一個"},{"type":"codeinline","content":[{"type":"text","text":".0"}]},{"type":"text","text":"文件點擊右鍵,"},{"type":"codeinline","content":[{"type":"text","text":"Save as"}]},{"type":"text","text":"保存到電腦,改個jpg後綴,就能看圖了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來到"},{"type":"codeinline","content":[{"type":"text","text":"DiskLruCache"}]},{"type":"text","text":"類(看名字知道還是"},{"type":"codeinline","content":[{"type":"text","text":"最近最少使用算法"}]},{"type":"text","text":"),"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//DiskLruCache.java\n\n//有序Map,實現最近最少使用算法\nprivate final LinkedHashMap lruEntries =\n new LinkedHashMap(0, 0.75f, true);\n\n//讀取磁盤緩存\npublic synchronized Value get(String key) throws IOException {\n //根據key找到entry\n Entry entry = lruEntries.get(key);\n if (entry == null) {\n return null;\n }\n //還不可以讀,返回null\n if (!entry.readable) {\n return null;\n }\n //追加一行日誌:READ\n journalWriter.append(READ);\n journalWriter.append(' ');\n journalWriter.append(key);\n journalWriter.append('\\n');\n //Value就是用來封裝的實體\n return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);\n}\n\n//寫入磁盤緩存(這裏只是存進內存的Map,真正的寫入在DiskLruCacheWrapper)\nprivate synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {\n Entry entry = lruEntries.get(key);\n if (entry == null) {\n entry = new Entry(key);\n //存進LinkedHashMap\n lruEntries.put(key, entry);\n }\n Editor editor = new Editor(entry);\n entry.currentEditor = editor;\n //追加一行日誌:DIRTY\n journalWriter.append(DIRTY);\n return editor;\n}\n\n//刪除磁盤緩存\npublic synchronized boolean remove(String key) throws IOException {\n Entry entry = lruEntries.get(key);\n if (entry == null || entry.currentEditor != null) {\n return false;\n }\n //刪除entry對應的圖片文件\n for (int i = 0; i < valueCount; i++) {\n File file = entry.getCleanFile(i);\n size -= entry.lengths[i];\n entry.lengths[i] = 0;\n }\n //追加一行日誌:REMOVE\n journalWriter.append(REMOVE);\n //從內存Map中移除\n lruEntries.remove(key);\n return true;\n}\n\n//當日志操作數和entry數都達到2000,就清空日誌重寫\nprivate boolean journalRebuildRequired() {\n final int redundantOpCompactThreshold = 2000;\n return redundantOpCount >= redundantOpCompactThreshold //\n && redundantOpCount >= lruEntries.size();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼讀取和寫入時機在哪呢?我們反向追蹤一波"},{"type":"codeinline","content":[{"type":"text","text":"get"}]},{"type":"text","text":"方法,從"},{"type":"codeinline","content":[{"type":"text","text":"DiskLruCache"}]},{"type":"text","text":"到"},{"type":"codeinline","content":[{"type":"text","text":"DiskLruCacheWrapper"}]},{"type":"text","text":"的"},{"type":"codeinline","content":[{"type":"text","text":"get"}]},{"type":"text","text":",然後再追,發現有兩個類調了"},{"type":"codeinline","content":[{"type":"text","text":"get"}]},{"type":"text","text":",分別是"},{"type":"codeinline","content":[{"type":"text","text":"DataCacheGenerator"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"ResourceCacheGenerator"}]},{"type":"text","text":",前者是原始圖片的緩存,後者是經過"},{"type":"codeinline","content":[{"type":"text","text":"downsampled"}]},{"type":"text","text":"向下採樣或"},{"type":"codeinline","content":[{"type":"text","text":"transformed"}]},{"type":"text","text":"轉換過的圖片,在"},{"type":"link","attrs":{"href":"https://muyangmin.github.io/glide-docs-cn/doc/caching.html#磁盤緩存策略disk-cache-strategy","title":""},"content":[{"type":"text","text":"磁盤緩存策略"}]},{"type":"text","text":"中提到:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前支持的策略允許你阻止加載過程使用或寫入磁盤緩存,選擇性地僅緩存無修改的原生數據,或僅緩存變換過的縮略圖,或是兼而有之。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默認情況下,網絡圖片緩存的是原始數據,那我們繼續跟"},{"type":"codeinline","content":[{"type":"text","text":"DataCacheGenerator"}]},{"type":"text","text":","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//DataCacheGenerator.java\npublic boolean startNext() {\n while (modelLoaders == null || !hasNextModelLoader()) {\n sourceIdIndex++;\n if (sourceIdIndex >= cacheKeys.size()) {\n return false;\n }\n Key sourceId = cacheKeys.get(sourceIdIndex);\n Key originalKey = new DataCacheKey(sourceId, helper.getSignature());\n //獲取磁盤緩存的圖片文件\n cacheFile = helper.getDiskCache().get(originalKey);\n if (cacheFile != null) {\n this.sourceKey = sourceId;\n //獲取能夠處理File類型的modelLoaders集合,\n //modelLoader就是圖片加載類型,比如網絡url、本地Uri、文件File都有各自的loader\n modelLoaders = helper.getModelLoaders(cacheFile);\n modelLoaderIndex = 0;\n }\n }\n loadData = null;\n boolean started = false;\n while (!started && hasNextModelLoader()) {\n //成功找到ByteBufferFileLoader,可以處理File\n ModelLoader modelLoader = modelLoaders.get(modelLoaderIndex++);\n //傳入磁盤緩存的圖片文件cacheFile\n loadData =\n modelLoader.buildLoadData(\n cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());\n if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {\n started = true;\n loadData.fetcher.loadData(helper.getPriority(), this);\n }\n }\n return started;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼續跟"},{"type":"codeinline","content":[{"type":"text","text":"modelLoader.buildLoadData"}]},{"type":"text","text":",後邊就是把圖片文件cacheFile封裝成"},{"type":"codeinline","content":[{"type":"text","text":"ByteBufferFetcher"}]},{"type":"text","text":",然後調用上邊的"},{"type":"codeinline","content":[{"type":"text","text":"loadData.fetcher.loadData"}]},{"type":"text","text":"進行回調,就不繼續跟了,"},{"type":"codeinline","content":[{"type":"text","text":"startNext"}]},{"type":"text","text":"方法在"},{"type":"codeinline","content":[{"type":"text","text":"DecodeJob"}]},{"type":"text","text":"裏會被調用,樹幹篇中可知他就是圖片加載過程用到的一個Runnable,好了,下面看看緩存寫入時機,反向追蹤"},{"type":"codeinline","content":[{"type":"text","text":"edit"}]},{"type":"text","text":"方法,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//DiskLruCacheWrapper.java\npublic void put(Key key, Writer writer) {\n String safeKey = safeKeyGenerator.getSafeKey(key);\n writeLocker.acquire(safeKey);\n try {\n try {\n DiskLruCache diskCache = getDiskCache();\n Value current = diskCache.get(safeKey);\n //已經有緩存,結束\n if (current != null) {\n return;\n }\n\t\t\t//獲取Editor\n DiskLruCache.Editor editor = diskCache.edit(safeKey);\n try {\n File file = editor.getFile(0);\n if (writer.write(file)) {//編碼寫入文件\n //提交“事務”,追加一行日誌:CLEAN,表示該條目對應的緩存文件已經乾淨可以使用了\n editor.commit();\n }\n } finally {\n editor.abortUnlessCommitted();\n }\n } catch (IOException e) {\n }\n } finally {\n writeLocker.release(safeKey);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣,"},{"type":"codeinline","content":[{"type":"text","text":"put"}]},{"type":"text","text":"方法也會在"},{"type":"codeinline","content":[{"type":"text","text":"DecodeJob"}]},{"type":"text","text":"裏被調用,就不往上跟了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2c/2c2fb30fb1bc2d171672bbe8c5880173.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"合併內存緩存和磁盤緩存,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bd/bd182445a818a06d627c900b4fdb5900.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"BitmapPool令人詬病"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Glide"}]},{"type":"text","text":"有將Bitmap進行池化,默認是"},{"type":"codeinline","content":[{"type":"text","text":"LruBitmapPool"}]},{"type":"text","text":",他會決定怎麼複用Bitmap、何時回收Bitmap、池子上限時清理,也就是說,他全盤接管了Bitmap的處理,如果項目中有"},{"type":"codeinline","content":[{"type":"text","text":"在回調方法外持有Bitmap"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"手動回收Bitmap"}]},{"type":"text","text":"的場景,會發生意料外的crash,詳見"},{"type":"link","attrs":{"href":"https://muyangmin.github.io/glide-docs-cn/doc/resourcereuse.html#資源重用錯誤的徵兆","title":""},"content":[{"type":"text","text":"資源重用錯誤的徵兆"}]},{"type":"text","text":"。即,我們要有這樣的意識,既然使用了"},{"type":"codeinline","content":[{"type":"text","text":"Glide"}]},{"type":"text","text":",就不要再關心Bitmap的事情了,全盤交由"},{"type":"codeinline","content":[{"type":"text","text":"BitmapPool"}]},{"type":"text","text":"管理即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"發散:所謂池化,就是設計模式中的享元模式,即維護一個有限個數的對象池來實現對象複用,從而避免頻繁的創建銷燬對象。比如Handler消息機制中的"},{"type":"codeinline","content":[{"type":"text","text":"Message.obtain"}]},{"type":"text","text":",就是從消息池(鏈表)裏取出對象來複用,池子的消息總數被限制在MAX"},{"type":"text","marks":[{"type":"italic"}],"text":"POOL"},{"type":"text","text":"SIZE=50。Android內的很多實現都是基於Handler(消息驅動)的,池化能減少很大部分的創建銷燬。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Decoder解碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鏈路有點長,直接看調用棧,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c3/c31785bffa628b504e0e2190fb60eadb.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可見最終走的是native層的"},{"type":"codeinline","content":[{"type":"text","text":"nativeDecodeStream"}]},{"type":"text","text":",哈迪就不跟了,對inputstream轉成bitmap感興趣的讀者自行研究啦~"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0b/0ba09e821484cd12c9c21b9af1621b4e.jpeg","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Glide"}]},{"type":"text","text":"有如下優勢:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"空Fragment感知頁面生命週期,避免無效請求"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"高度可配置,詳見"},{"type":"link","attrs":{"href":"https://muyangmin.github.io/glide-docs-cn/doc/configuration.html","title":""},"content":[{"type":"text","text":"配置"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"三級緩存(網絡層緩存如okhttp就不考慮了):內存活躍資源"},{"type":"codeinline","content":[{"type":"text","text":"ActiveResources"}]},{"type":"text","text":"、內存非活躍資源"},{"type":"codeinline","content":[{"type":"text","text":"LruResourceCache"}]},{"type":"text","text":"、磁盤緩存"},{"type":"codeinline","content":[{"type":"text","text":"DiskLruCache"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"可定製,引入apt處理註解,打包行爲,擴展接口。(哈迪沒怎麼用,感覺有點雞肋,可能以後會真香)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"可擴展,可以替換網絡層、定製自己的圖片來源"},{"type":"codeinline","content":[{"type":"text","text":"ModelLoader"}]},{"type":"text","text":",詳見"},{"type":"link","attrs":{"href":"https://muyangmin.github.io/glide-docs-cn/tut/custom-modelloader.html","title":""},"content":[{"type":"text","text":"編寫定製的ModelLoader"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"無侵入,into可以傳入最簡單的ImageView"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"優秀的設計模式運用、應用層優雅的鏈式調用"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至於缺點吧,暫時還沒想到。本文只列出了哈迪覺得比較精彩的細節,可能還有遺漏的一些點,大家有補充的可以留下評論,後續我會更新進本文。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"參考資料"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://muyangmin.github.io/glide-docs-cn/","title":""},"content":[{"type":"text","text":"官方文檔"}]},{"type":"text","text":" & "},{"type":"link","attrs":{"href":"https://github.com/bumptech/glide","title":""},"content":[{"type":"text","text":"GitHub"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/18/18280df97439e1bea7bded6864c8a8a8.jpeg","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章