第六章 圖片

文章目錄

第六章 圖片

(一)Android加載大圖

(1)爲什麼加載大圖需單獨處理

有限的內存加載無限大的圖片,導致OOM:
1、圖片庫裏展示的圖片(大部分由手機攝像頭拍出來)分辨率比手機屏幕分辨率高得多,應該將圖片壓縮和用來展示的控件大小相近,否則會佔用內存
2、Android對圖片解碼:圖片大小=圖片總像素數*圖片每個像素大小

(2)解決方案

1、按照縮放比解析位圖

首先獲得屏幕的大小,然後獲得圖片的大小,最後通過計算屏幕與圖片的縮放比,按照縮放比來解析位圖。

//1.獲取手機的分辨率  獲取windowmanager 實例
WindowManager wm  = (WindowManager) getSystemService(WINDOW_SERVICE);     screenWidth = wm.getDefaultDisplay().getWidth();
screenHeight = wm.getDefaultDisplay().getHeight();
//2.把xxxx.jpg 轉換成bitmap
//創建bitmap工廠的配置參數
BitmapFactory.Options options = new Options();          
//=true返回一個null 沒有bitmap:不去爲bitmap分配內存 但是能返回圖片的一些信息(寬和高)     options.inJustDecodeBounds = true;     BitmapFactory.decodeFile("/mnt/sdcard/xxxx.jpg",options);     
//3.獲取圖片的寬和高       
int imgWidth = options.outWidth;     
int imgHeight = options.outHeight;
 //4.計算縮放比          
int scalex =  imgWidth/screenWidth;     
int scaley = imgHeight /screenHeight;
scale =min(scalex, scaley,scale);            
//5.按照縮放比顯示圖片,inSampleSize給圖片賦予縮放比,其值大於1時,會按縮放比返回一個小圖片用來節省內存   
options.inSampleSize = scale;          
//6.=false開始真正的解析位圖,可顯示位圖 
options.inJustDecodeBounds = false;     
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg",options);         
//7.把bitmap顯示到控件上     
iv.setImageBitmap(bitmap);   } }

2、 圖片分塊加載

圖片的分塊加載在地圖繪製的情況上最爲明顯,當想獲取一張尺寸很大的圖片的某一小塊區域時,就用到了圖片的分塊加載,在Android中BitmapRegionDecoder類的功能就是加載一張圖片的指定區域。

3、LruCache緩存機制

(二)Android圖片三級緩存機制

圖片的三級緩存機制一般是指應用加載圖片的時候,分別去訪問內存,文件和網絡而獲取圖片數據的一種行爲。

1、三級緩存流程圖

在這裏插入圖片描述

2、第一級:內存LruCache

LruCache是android提供的一個緩存工具類,其算法是最近最少使用算法。它把最近使用的對象用“強引用”存儲在LinkedHashMap中,並且把最近最少使用的對象在緩存值達到預設定值之前就從內存中移除。
LinkedHashMap:HashMap和雙向鏈表合二爲一即是LinkedHashMap。它通過維護一個額外的雙向鏈表保證了迭代順序。特別地,該迭代順序可以是插入順序,也可以是訪問順序。

(2.1)源碼分析

/**
 * 它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,並且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除。(最近使用的數據在尾部,老數據在頭部)
 */
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;
     public LruCache(int maxSize) {
         if (maxSize <= 0) {
             throw new IllegalArgumentException("maxSize <= 0");
         }
         this.maxSize = maxSize;         
// 注意第三個參數,是accessOrder=true,表示linkedHashMap爲訪問順序,當對已存在LinkedHashMap的Entry進行get和put操作時,會把Entry移動到雙向鏈表的表頭
         this.map = new LinkedHashMap<K, V>(0, 0.75f, true);     
}
LruCache的put方法
public final V put(K key, V value) {       
	V previous;
         // 對map進行操作之前,先進行同步操作(HashMap是線程不安全的,應該先進行同步操作)
	   //synchronized加鎖,表示一次只能有一個方法進入該線程
         synchronized (this) {
             putCount++;
             size += safeSizeOf(key, value);
//向map中加入緩存對象,若緩存中已存在,返回已有的值,否則執行插入新的數據,並返回null,並將緩存恢復爲之前的值
             previous = map.put(key, value)
             if (previous != null) {
                 size -= safeSizeOf(key, previous);
             }         
}          
if (previous != null) {
             entryRemoved(false, key, previous, value);
         }
         // 根據緩存大小整理內存,看是否需要移除LinkedHashMap中的元素
         trimToSize(maxSize);
         return previous;     }

trimToSize(maxSize)

public void trimToSize(int maxSize) {
    while (true) {
// while循環,不斷移除LinkedHashMap中雙向鏈表表頭表頭元素(近期最少使用的數據),直到滿足當前緩存大小小於或等於最大可緩存大小
//如果當前緩存大小已小於等於最大可緩存大小,則直接返回,不需要再移除LinkedHashMap數據
	if (size <= maxSize || map.isEmpty()) {
     	break; }
//得到雙向鏈表表頭header的下一個Entry(近期最少使用的數據=表頭數據)
            Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
            key = toEvict.getKey();
            value = toEvict.getValue();
//移除當前取出的Entry並重新計算當前緩存大小
            map.remove(key);
            size -= safeSizeOf(key, value);
            evictionCount++;
        }
        entryRemoved(true, key, value, null);
    }
}

LruCache的get方法

public final V get(K key) {
//如果該值在緩存中存在或可被創建便返回,當調用LruCache的get()方法獲取集合中的緩存對象時,就代表訪問了一次該元素,將會更新隊列,移動到表尾,這個更新過程就是在LinkedHashMap中的get()方法中完成的。
    V mapValue;
    synchronized (this) {
        mapValue = map.get(key);
        if (mapValue != null) {
            return mapValue;
        }
    }
}

由重排序可知,對LinkedHashMap的put和get操作,都會讓被操作的Entry移動到雙向鏈表的尾部。而移除是從map.entrySet().iterator().next()開始的,也就是雙向鏈表的表頭的header的after開始的,這也就符合了LRU算法的需求。

(2.2)使用

1、初始化緩存類,設定大小並重寫sizeof()方法
/* 內存緩存 */
    private LruCache<String, Bitmap> mMemoryCache = 4*1024*1024;
    //初始化這個cache前需要設定這個cache的大小,這裏的大小官方推薦是用當前app可用內存的八分之一
    mMemoryCache = new LruCache<String, Bitmap>(memoryCache) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // 重寫此方法來衡量每張圖片的大小,默認返回圖片數量。
                return bitmap.getByteCount() / 1024;
            }
        };
2、重寫添加/刪除緩存
    //將bitmap添加到內存中去
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }
    // 通過key來從內存緩存中獲得bitmap對象
    private Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }
3、模擬從網絡下載圖片加入緩存
    private void loadBitmapToImageView(int resId, ImageView imageView) {
        final String imageKey = String.valueOf(resId);
        final Bitmap bitmap = getBitmapFromMemCache(imageKey); // 先看這個資源在不在內存中,如果在直接讀取爲bitmap,否則返回null
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        } else {
            imageView.setImageResource(R.drawable.ic_launcher); // 如果沒有在內存中,先顯示默認的圖片,然後啓動線程去下載圖片
            BitmapWorkerTask task = new BitmapWorkerTask(imageView);
            task.execute(resId); // 啓動線程,模擬從網絡下載圖片,下載後加入緩存
        }
}

緩存如果沒有,則繼續

3、第二級:文件緩存

3.1)緩存文件存儲的路徑設定

存儲的路徑首先要考慮SD卡的緩存目錄,當SD卡不存在時,就只能存到內部存儲的緩存目錄了。

private File getCacheDir() {
    // 獲取緩存路徑目錄
    File file;
    if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
        file = mContext.getExternalCacheDir();        // 有SD卡就保存到sd卡
    } else {
        file = mContext.getCacheDir();        // 沒有就保存到內部儲存
    }
    return file;
}

3.2)解析文件生成Bitmap對象,並存入緩存

private Bitmap getBitmapFromFile() {
    // 根據url中獲取文件名字,存儲文件的文件名截取URL中的名字,並且文件名用md5加密
    String fileName = url.substring(url.lastIndexOf("/") + 1);
    File file = new File(getCacheDir(),MD5Util.encodeMd5(fileName));
    if (file.exists() && file.length() > 0) { 
          diskBitmap  = BitmapFactory.decodeFile(file.getAbsolutePath());
 	mImageCache.put(url, new SoftReference<Bitmap>(diskBitmap)); 	// 保存到內存中去
	return diskBitmap;
    } else {
        return null;
    }
}

4、第三級:聯網加載

4.1)簡單線程池處理耗時網絡請求

//構建出5條線程的線程池
private ExecutorService mExecutorService = Executors.newFixedThreadPool(5);

4.2)聯網加載數據HttpUrlConnection

//用HttpUrlConnection加載網絡圖片
URL loadUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) loadUrl.openConnection();

4.3)保存數據到內存和文件

mImageCache.put(url, new SoftReference<>(bm));    //保存到內存
String fileName = url.substring(url.lastIndexOf("/") + 1);//從Url中獲取文件名字,保存到磁盤
File file = new File(getCacheDir(), MD5Util.encodeMd5(fileName));//獲取存儲路徑
FileOutputStream os = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.JPEG, 100, os);//將圖片轉爲文件存儲

(三)Glide源碼解析

1.基本用法

//with(Context/Activity/Fragment)決定Glide加載圖片的生命週期
//load(url)url包括網絡圖片、本地圖片、應用資源、二進制流、Uri對象等等(重載)
//into(imageView)
Glide.with(this).load(url).into(imageView);
//擴展功能
.placeholder(R.drawable.loading)//加載圖片過程佔位符,加載完成會替換佔位符
.error(R.drawable.error)//加載圖片錯誤佔位符
.asGif()/.asBitmap()只顯示動態圖/只顯示靜態圖(不設置時,Glide會自動判斷圖片格式)
.diskCacheStrategy(DiskCacheStrategy.NONE)//禁用Glide緩存機制
.override(100, 100)//指定圖片大小(Glide會自動判斷ImageView的大小,然後將對應的圖片像素加載本地,節省內存開支)

2.圖片加載流程源碼分析

在這裏插入圖片描述

(2.1)with(Context/Activity/Fragment)

得到一個RequestManager對象(實現request和Activity/Fragment生命週期的關聯),Glide再根據傳入的with()方法的參數確定圖片加載的生命週期:
1、Application類型參數——應用程序生命週期
2、非Application類型參數——Activity/Fragment生命週期

(2.2)load(url)

得到一個DrawableTypeRequest對象(extends DrawableRequestBuilder)

(2.3)into(imageView)

在這裏插入圖片描述
在這裏插入圖片描述

1.DrawableRequestBuilder 父類 GenericRequestBuilder
return into(glide.buildImageViewTarget(view, transcodeClass));

其中glide.buildImageViewTarget(view,transcodeClass):

public interface Target<R> extends LifecycleListener {
//The typical lifecycle is onLoadStarted -> onResourceReady or onLoadFailed -> onLoadCleared.

request的載體,各種資源對應的加載類,含有生命週期的回調方法,方便開發人員在圖片加載過程進行相應的準備以及資源回收工作。
在buildTarget()方法中會根據傳入的class參數來構建不同的Target對象,如果你在使用Glide加載圖片的時候調用了asBitmap()方法,那麼這裏就會構建出BitmapImageViewTarget對象,否則的話構建的都是GlideDrawableImageViewTarget對象。
通過glide.buildImageViewTarget()方法,我們構建出了一個GlideDrawableImageViewTarget對象。即
into(GlideDrawableImageViewTarget)

2.into(target) 設置資源的Target,並創建,綁定,跟蹤,發起請求

在這裏插入圖片描述

public <Y extends Target<TranscodeType>> Y into(Y target) {
    Request request = buildRequest(target);
    target.setRequest(request);
    lifecycle.addListener(target);
    requestTracker.runRequest(request);
    return target;
}

(1) buildRequest(buildRequestRecursive)

//創建請求,如果配置了thumbnail(縮略圖)請求,則構建一個ThumbnailRequestCoordinator(包含了FullRequest和ThumbnailRequest)請求, 
否則調用了obtainRequest()方法來獲取一個Request對象,並傳入相應的參數(target,sizeMultiplier,priority,requestCoordinator...)
return GenericRequest.obtain(target, sizeMultiplier, priority, parentCoordinator…);

(2) requestTracker.runRequest

public void runRequest(Request request) {
//處理請求
    requests.add(request);
    if (!isPaused) {
        request.begin();//調用GenericRequest的begin方法
    } else {
        pendingRequests.add(request);
    }
}

GenericRequest.begin()

public void begin() {
    if (model == null) { //model爲Url,圖片路徑,Uri等等圖片資源模型
        onException(null); //調用setErrorPlaceholder(),將error佔位符填入imageView上
        return;
    }
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
    } else {//未指定寬高,則glide根據ImageView的layout_width和layout_height值做一系列的計算,來算出圖片應該的寬高(防止OOM),再調用onSizeReady(width,height)
        target.getSize(this);
    }
    if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        target.onLoadStarted(getPlaceholderDrawable());
    }
}
public void onSizeReady(int width, int height) {
    width = Math.round(sizeMultiplier * width);
    height = Math.round(sizeMultiplier * height);
//ModelLoader:各種資源model的ModelLoader,主要功能有:1、將任意複雜的model轉換爲可以被DataFetcher所decode的數據類型2、允許model結合View的尺寸獲取特定大小的資源
    ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
//每次通過ModelLoader加載資源都會創建的實例
    final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
// ResourceTranscoder : 資源轉換器,將給定的資源類型,轉換爲另一種資源類型,比如將Bitmap轉換爲Drawable,Bitmap轉換爲Bytes 
    ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
    loadedFromMemoryCache = true;
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this); //真正開始加載資源
    loadedFromMemoryCache = resource != null;
}
3.Engine負責任務創建,發起,回調,資源的管理

Engine.load(…)
真正的開始加載資源,load調用處理流程圖:
在這裏插入圖片描述

public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
        DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
        Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
//創建一個EngineJob:調度DecodeJob,添加,移除資源回調,並notify回調
    EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
//創建一個DecodeJob:調度任務的核心類,整個請求的繁重工作都在這裏完成:處理來自緩存或者原始的資源,應用轉換動畫以及transcode。
負責根據緩存類型獲取不同的Generator加載數據,數據加載成功後回調DecodeJob的onDataFetcherReady方法對資源進行處理
    DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation, transcoder, diskCacheProvider, diskCacheStrategy, priority);
    EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
//創建一個EngineRunnable implements Runnable
    EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    engineJob.start(runnable); //*
    return new LoadStatus(cb, engineJob);
}

*在子線程執行EngineRunnable的run()方法=> DecodeJob.decodeFromSource()/decodeFromCache()
(1)decodeSource()//獲取一個Resource對象
1、fetcher.loadData()進行網絡通訊獲得InputStream,幷包裝成ImageVideoWrapper對象

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
        throws IOException {
       urlConnection = connectionFactory.build(url);
       urlConnection.connect();
       return getStreamForSuccessfulRequest(urlConnection);
}

2、decoded = decodeFromSourceData(data)對ImageVideoWrapper對象(流的封裝類)進行解碼;包括對圖片的壓縮,旋轉,圓角等邏輯處理,得到加載好的圖片對象Bitmap,並層層包裝BitmapResource(Resource)->GifBitmapWrapper->GifBitmapWrapperResource(Resource)使加載的圖片能夠以Resource接口形式返回,並同時能夠處理Bitmap和gif圖片

public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {
        final Bitmap downsampled =downsampleWithSize(invalidatingStream, bufferedStream, options, pool, inWidth, inHeight, sampleSize, decodeFormat);

}
4.transformEncodeAndTranscode(decoded)

處理這個Resource對象,轉化爲可在ImageView顯示的Resource對象

5.得到一個可顯示Resource對象,並調用EngineJob.onResourceReady()
public void onResourceReady(final Resource<?> resource) {
    this.resource = resource;
//使用Handler發出了一條MSG_COMPLETE消息
    MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
private static class MainThreadCallback implements Handler.Callback {
//在MainThreadCallback的handleMessage()方法中就會收到MSG_COMPLETE消息。從這裏開始,所有的邏輯又回到主線程當中進行了,因爲很快就需要更新UI了
    @Override
    public boolean handleMessage(Message message) {
        if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
            EngineJob job = (EngineJob) message.obj;
            if (MSG_COMPLETE == message.what) {
                job.handleResultOnMainThread();
            } else {
                job.handleExceptionOnMainThread();
            }
            return true;
        }
        return false;
    }
}

最後在ImageViewTarget的onResourceReady()方法當中調用了setResource()方法,即調用ImageView.setImageDrawable()方法,圖片最終顯示出來

3.Glide緩存機制

(3.1)Glide緩存功能相關用法

1、設置內存緩存開關

skipMemoryCache(true)

2、 設置磁盤緩存模式

diskCacheStrategy(DiskCacheStrategy.NONE)

可以設置4種模式:

DiskCacheStrategy.NONE:表示不緩存任何內容。
DiskCacheStrategy.SOURCE:表示只緩存原始圖片。
DiskCacheStrategy.RESULT:表示只緩存轉換過後的圖片(默認選項)。
DiskCacheStrategy.ALL :表示既緩存原始圖片,也緩存轉換過後的圖片。

(3.2)Glide緩存源碼分析

3.2.1)設計思路

內存緩存的操作應該是在異步處理之前,磁盤緩存是耗時操作應該是在異步處理中完成。Glide的內存存緩存的讀存都在Engine類中完成。
內存緩存使用弱引用(ActiveCache)和LruCache(Cache)結合完成的,弱引用來緩存的是正在使用中的圖片。圖片封裝類Resources內部有個計數器判斷是該圖片否正在使用。

3.2.2)源碼分析
(1) Glide內存緩存源碼分析

流程:
讀:是先從lruCache取,取不到再從弱引用中取;
存:
內存緩存取不到,從網絡拉取回來先放在弱引用裏,渲染圖片,圖片對象Resources使用計數加一,計數>0,正在使用;
渲染完圖片,圖片對象Resources使用計數減一,如果計數爲0,圖片緩存從弱引用中刪除,放入lruCache緩存。
1、從內存緩存讀取圖片
Engine在加載流程的中的入口方法是load方法

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
…
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
        DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
        Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
    Util.assertMainThread();
    long startTime = LogTime.getLogTime();

    final String id = fetcher.getId();
//生成緩存的key    
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
            loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
            transcoder, loadProvider.getSourceEncoder());
//從LruCache獲取緩存圖片  
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        cb.onResourceReady(cached);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
        }
        return null;
    }
//從弱引用獲取緩存圖片
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
        cb.onResourceReady(active);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from active resources", startTime, key);
        }
        return null;
    }
…//進行異步處理
}

從內存緩存中讀取圖片的主流程:
生成緩存的key->從LruCache獲取緩存圖片->LruCache沒取到,從弱引用獲取圖片->內存緩存取不到,進入異步處理。
從內存混存取圖片的兩個方法loadFromCache()和loadFromActiveResources()。loadFromCache使用的就是LruCache算法,loadFromActiveResources使用的就是弱引用。

public class Engine implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
private final MemoryCache cache;
private final Map<Key, WeakReference<EngineResource<?>>> activeResources;

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {//skipMemoryCache()方法設置是否內存緩存被禁用
        return null;
    }
    EngineResource<?> cached = getEngineResourceFromCache(key); //從LruCache獲取圖片緩存
if (cached != null) {
//從LruResourceCache中獲取到緩存圖片之後會將它從緩存中移除,將緩存圖片存儲到activeResources當中。activeResources就是弱引用的HashMap,用來緩存正在使用中的圖片。
        cached.acquire();
        activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
    }
    return cached;
}
private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
        return null;
    }
    EngineResource<?> active = null;
    WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
    if (activeRef != null) {
        active = activeRef.get();
        if (active != null) {
            active.acquire();
        } else {
            activeResources.remove(key);
        }
    }
    return active;
}

2、將圖片存入內存緩存
EngineJob 獲取到圖片後 會回調Engine的onEngineJobComplete()將正在加載的圖片放到弱引用緩存

public void onEngineJobComplete(Key key, EngineResource<?> resource) {
    if (resource != null) {
        resource.setResourceListener(key, this);
        if (resource.isCacheable()) {
            activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));//將正在加載的圖片放到弱引用緩存
        }
    }
    jobs.remove(key);
}

EngineResource是用一個acquired變量用來記錄圖片被引用的次數,調用acquire()方法會讓變量加1,調用release()方法會讓變量減1。當引用計數acquired變量爲0,即表示圖片使用完,應該放入LruCache中。這裏調用了listener.onResourceReleased(key, this);這個listener就是Engine對象。

public void onResourceReleased(Key cacheKey, EngineResource resource) {
    activeResources.remove(cacheKey); //從弱引用中刪除圖片緩存
    if (resource.isCacheable()) {
        cache.put(cacheKey, resource); //如果支持緩存,則緩存到LruCache
    } else {
        resourceRecycler.recycle(resource); //不支持緩存直接調用垃圾回收,回收圖片
    }
}
(2) Glide磁盤緩存源碼分析

讀:先找處理後(result)的圖片,沒有的話再找原圖。
存:先存原圖,再存處理後的圖。
1、從磁盤緩存讀取圖片
EngineRunnable的run()方法->decode()方法

private Resource<?> decode() throws Exception {
    if (isDecodingFromCache()) {//從磁盤緩存讀取圖片
        return decodeFromCache();
    } else {//從原始位置讀取圖片
        return decodeFromSource();
    }
}

2、將圖片存入磁盤緩存
獲取圖片後存入圖片

public Resource<Z> decodeFromSource() throws Exception {
    Resource<T> decoded = decodeSource();
    return transformEncodeAndTranscode(decoded);
}

(1) 存入原圖

private Resource<T> decodeSource() throws Exception {
        decoded = decodeFromSourceData(data);
}
private Resource<T> decodeFromSourceData(A data) throws IOException {
    final Resource<T> decoded;
    if (diskCacheStrategy.cacheSource()) {//設置是否緩存原圖
        decoded = cacheAndDecodeSourceData(data);
    } else {
        decoded = loadProvider.getSourceDecoder().decode(data, width, height);
    }
    return decoded;
}
private Resource<T> cacheAndDecodeSourceData(A data) throws IOException {
    SourceWriter<A> writer = new SourceWriter<A>(loadProvider.getSourceEncoder(), data);
//獲取DiskCache工具類並寫入緩存
    diskCacheProvider.getDiskCache().put(resultKey.getOriginalKey(), writer);
    Resource<T> result = loadFromCache(resultKey.getOriginalKey());
    return result;
}

(2) 存入處理後的圖
在圖片處理之後,在transformEncodeAndTranscode()方法中存入處理後的圖

private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
    Resource<T> transformed = transform(decoded);
    writeTransformedToCache(transformed);
    Resource<Z> result = transcode(transformed);
    return result;
}
private void writeTransformedToCache(Resource<T> transformed) {
    SourceWriter<Resource<T>> writer = new SourceWriter<Resource<T>>(loadProvider.getEncoder(), transformed);
    diskCacheProvider.getDiskCache().put(resultKey, writer); //獲取DiskCache實例並寫入緩存
}

4.Glide框架總結

(1)主流程圖

在這裏插入圖片描述

(2)三件大事

1、準備數據

(1.1)構建GenericRequest對象——Glide配置
(1.2)構建decodeJob對象——異步處理核心對象

2、異步處理

(1)發起網絡請求,拿到數據流
(2)將數據流解碼成bitmap對象
(3)將bitmap轉碼成drawable對象

3、切換到主線程,將Drawable再ImageView顯示

(3)主流程細化——時序圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章