1、定義
Glide 一個被google所推薦的圖片加載庫,做者是bumptech。對Android SDk 最低要求是 API 10
與之功能相似的是Square公司的picasso
2、基本概念
Model :數據來源 :Uri、本地文件、資源ID
Data :加工數據
Resource :對原始數據進行解碼,解碼以後的資源 resource
Resource decode :資源解碼器
TransformedResource:轉換資源
TranscodedResource:轉碼,將靜態、Gif動態圖進行格式轉換以便能加載
Target :目標圖片
3、總體流程
A:Model圖片數據源 ———ModelLoader加載—>原始數據Data——Decoder解碼——>
Resource——Transform裁剪——>TransformResource——Transcode轉碼——>TranscodeResource——封裝——>Target
4、源碼
引入
:compile 'com.github.bumptech.glide:glide:3.7.0'
4.一、使用流程三步曲: Glide
.with(「上下文context」)
.load(「url」)
.into(「顯示的控件資源");
4.二、經常使用加載圖片的配置參數:
public void LoadImage(View view) {
//with 建立一個加載圖片的Glide實例,流式接口
Glide.with(getApplicationContext()) //指定的Context,傳入的參數會決定整個Glide的生命週期
ps:圖片的加載會和傳入的Acitivty或是Fragment保持一致,因此建議使用Activity 或是Fragment做爲參數而不是單純的使用this 或是 context
.load("url")//指定的圖片的URL
加載佔位圖:-int or Drawable
.placeholder(R.mipmap.ic_launcher) //指定圖片未成功加載前現實的圖片佔位符
// ,直到加載的網絡圖片顯示就會被替換,僅支持本地圖片資源
錯誤佔位圖:-int or Drawable
.error(R.mipmap.ic_launcher) //指定圖片加載失敗顯示的圖片佔位圖
加載本地縮略圖:
.thumbnail( float ) //0.2f Glide會顯示原始圖片的20%大小,注意ImageView的ScaleType值的設置
加載網絡縮略圖:
DrawableRequestBuilder<String> thumbnailRequest = Glide.with(context).load(url);
Glide.with( context) .load(url)
.thumbnail (thumbnailRequest ).into (imageView);
加載圖片動畫效果:
.crossFade() or crossfade(int duration) //強制開啓GLide默認的圖片淡出淡入效果,默認持續時間300ms,設置dontAnimate()設置無任何淡出淡入效果
顯示Gif 和Video 功能:
String gifUrl = 「…xxxxoo.git」;
.load (gifUrl)
.asGif()
.error(xxxx)
// 若是圖片類型不是Gif的話 就會看成load 失敗來處理
顯示靜態的Gif圖片 //僅僅顯示Gif的第一楨圖像
String gifUrl = 「…xxxxoo.git」;
.load (gifUrl)
.asBitmap()
.error(xxxx)
顯示手機本地視頻文件:
String filePath = 「/storage/emulated/0/xxxx.mp4"
.load(Uri.fromFile ( new File (filePath) ))
.override(300, 300) //不自動適配圖片尺寸的時候進行手動進行圖片尺寸設置 ,glide能夠自動限制圖片的尺寸來優化圖片的加載
//ps:Glide不會完整的加載原始圖片資源到內存當中,會自動判斷imageView大小再進行最優尺寸選擇加載
.fitCenter() //指定圖片縮放類型1,顯示的圖像寬和高都小於等於ImageView的邊界範圍
//ps:缺陷 有可能不被填滿所須要的ImageView, FIT_CENTER
.centerCrop() //指定圖片縮放類型2。顯示的圖像填充整個ImageView,而後裁剪額外超出的部分
//ps:缺陷,有可能完整填充ImageView可是圖片不能完整顯示,CENTER_CROP
.skipMemoryCache(true) //不將該圖片放在內存緩存中,可是仍然會利用該部分磁盤緩存,依然會在磁盤緩存中創建區域,Glide默認將圖片放入內存緩存中
//ps: Glide默認會將圖片緩存到內存緩存中因此無需傳入false值,若同一Url地址在首次沒有調用該方法則第二次直接從內存中緩存緩存圖片,若想調整行爲須要保證每次調用行爲一致
//硬盤緩存策略-枚舉值:默認開啓
.diskCacheStrategy(DiskCacheStrategy.NONE) //禁用硬盤的緩存 讓其處於null狀態,跳過磁盤緩存
.diskCacheStrategy(DiskCacheStrategy.SOURCE) //僅僅只緩存原來的全分辨率尺寸圖像
.diskCacheStrategy(DiskCacheStrategy.RESULT) //僅僅只緩存資源最終的加載圖像(下降壓縮分辨率後的圖片)
.diskCacheStrategy(DiskCacheStrategy.ALL) //緩存全部版本的圖片
.priority(Priority.HIGH)//優先級 先處理優先級高的圖片信息 ,但不保證全部圖片按照優先級順序進行加載
.into(imageview); //顯示到指定的ImageView
}
4.三、with()解析: 基礎目的:獲取RequestManager對象(管理Request請求)
根據當前的上下文和組建選擇不一樣的with構造方法,不一樣的參數對於圖片加載的生命週期產生不一樣的影響,將圖片加載的生命週期和組件相掛鉤。
以context 爲例:
public static RequestManager with(Context context) {
//用於產生requestManager 圖片請求管理
RequestManagerRetriever 生產requestManager
RequestManagerRetrieverretriever = RequestManagerRetriever.get();
return retriever.get(context);
}
//經過RequestManagerRetriever 這個生產Request類來處理,同時Glide綁定了組件的生命週期
RequestManager的建立流程:
public RequestManager get(Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
}
//當前Context 是不是在主線程,context是不是application的實例
else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
//返回一個單例模式的ApplicationManager
return getApplicationManager(context);
}
//雙重鎖檢查機制的單例模型
獲取惟一個RequestManager,進行圖片請求的處理
private RequestManager getApplicationManager(Context context) {
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
applicationManager = new RequestManager(context.getApplicationContext(),
new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
}
}
}
return applicationManager;
}
RequestManagerRetriever的 get()解析:
a、傳入application 類型的Context
當Glide傳入的是整個程序的生命週期的時候,就不須要進行過多處理
b、傳入非application 類型的Context (activity、fragment)
public RequestManager get(FragmentActivity activity) {
//是不是在後臺線程,只有在非UI線程才進else
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm);
}
}
supportFragmentGet():
RequestManager supportFragmentGet(Context context, FragmentManager fm) {
//Fragment 添加到Activity有兩種方式,A有Ui的Fragment B沒有Ui界面的Fragment
//這裏使用第二種方式,經過RequestManagerFragment來監聽Activity的生命週期來完成綁定生命週期,圖片加載選擇的過程
ps:Glide沒法直接監聽Activity的生命週期
SupportRequestManagerFragment current
= getSupportRequestManagerFragment(fm);
//建立RequestManager實例,完成Glide對象的構造,經過RequestManager就能夠控制整個界面的生命週期的監聽,經過監聽進行圖片的相應操做
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager = new RequestManager(context,
current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
setRequestManager():將RequestManagerFragment這個空界面的Fragment和RequestManager進行綁定;
目的:監聽Activity生命週期,管理圖片加載的整個流程 綁定到Activity一塊兒操做
public void setRequestManager(RequestManager requestManager) {
this.requestManager = requestManager;
}
ps:一個RequestManager 對應 一個RequestManagerFragment,一一對應關係
思考:前文提到的 SupportRequestManagerFragment 空的Fragment 是如何和RequestManager創建生命週期關係的呢?
答:在RequestManagerFragment中含有ActivityFragmentLifecycle函數,用於管理組建生命週期的。RequestManager實際上在RequestManagerFragment中註冊了一些回調接口,經過接口監聽Activity 或是Fragment的生命週期
例如:
@Override
public void onStart() {
super.onStart();
lifecycle.onStart();
//說明RequestManagerFragment 這個無Ui的Fragment是經過LifeCycle進行生命週期的管理
}
根據不一樣的生命週期進行相應的處理
4.四、load()解析: 初始化操做 獲取DrawableTypeRequest對象
思考:爲何Glide會有一大堆重載方法?
答:由於Glide支持多種格式的圖片來源,依託於此Glide加載也就須要不一樣的類型
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>)
fromString().load(string);
}
DrawableTypeRequest() :表示Glide中全部加載圖片的Request請求
class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> implements DownloadOptions
能想象到DrawableTypeRequest 應該就是經過Builder()內部類的構建者模式進行構建初始化的。
DrawableRequestBuilder:對Glide參數初始化的配置工做
而DrawableRequestBuilder又extends繼承自 GenericRequestBuilder
GenericRequestBuilder:Glide全部配置參數基本的最終類:
public class DrawableRequestBuilder<ModelType>
extends GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable>
implements BitmapOptions, DrawableOptions
ps:DrawableRequestBuilder能夠經過鏈式調用配置參數
在GenericRequestBuilder中有一個很重要的成員變量:
protected final RequestTracker requestTracker; //負責跟蹤整個圖片請求的週期
fromString():傳入String的Class對象做爲參數
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
loadGeneric():
private <T> DrawableTypeRequest<T>
loadGeneric(Class<T> modelClass) {
//建立兩個ModelLoader對象,經過數據來源,ML未來源加載成原始數據
ModelLoader<T, InputStream> streamModelLoader
= Glide.buildStreamModelLoader(modelClass, context);
ModelLoader<T, ParcelFileDescriptor>fileDescriptorModelLoader
= Glide.buildFileDescriptorModelLoader(modelClass, context);
if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
throw new IllegalArgumentException(...);
}
//建立DrawableTypeRequest 對象
return optionsApplier.apply(
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}
DrawableTypeRequest():相對經常使用的方法,經過返回不一樣的TypeRequest來選擇須要的方法
//強制指定加載靜態圖片
public BitmapTypeRequest<ModelType> asBitmap() {
return optionsApplier.apply(new BitmapTypeRequest<ModelType>
(this, streamModelLoader,
fileDescriptorModelLoader, optionsApplier));
}
//強制指定加載動態圖片
public GifTypeRequest<ModelType> asGif() {
return optionsApplier.apply(new GifTypeRequest<ModelType> (this, streamModelLoader, optionsApplier));
}
4.五、into()解析:——在主線程中調用的更新UI操做
public Target<TranscodeType> into(ImageView view) {
//一、是否在主線程中操做,非主線程拋出異常
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
//二、判斷類型是否進行哪類圖片處理
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
//三、建立一個Target對象,圖片所要顯示的控件
return into(glide.buildImageViewTarget(view, transcodeClass));
}
applyCenterCrop():
————>centerCrop()————>centerCrop():
public DrawableRequestBuilder<ModelType> centerCrop() {
return transform(glide.getDrawableCenterCrop());
}
transform()://圖片轉換,對transformation進行賦值操做
public GenericRequestBuilder<ModelType, DataType,
ResourceType, TranscodeType> transform(
Transformation<ResourceType>... transformations) {
isTransformationSet = true;
if (transformations.length == 1) {
transformation = transformations[0];
} else {
transformation = new MultiTransformation<ResourceType>(transformations);
}
return this;
}
glide.buildImageViewTarget() 中Target接口 :
//Glide能綁定生命週期的緣由:
interface Target<R> extends LifecycleListener
接口內含有onStart()、onStop()、onDestory()函數
在進行.with()操做的時候,會傳入context or Activity or Fragment進行相應生命週期的綁定操做 ,這時候就是經過LifecycleListener進行組件的監聽
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
//經過imageViewTargetFactory工廠構建Target
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
//經過傳入class對象判斷Glide須要加載哪類圖片,buildTarget()就建立哪類Target
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
if (GlideDrawable.class.isAssignableFrom(clazz)) {
//未調用asBitmap()方法就會默認採起GlideDrawableImageViewTarget()方法
return (Target<Z>) new GlideDrawableImageViewTarget(view);
} else if (Bitmap.class.equals(clazz)) {
//調用了asBitmap()
return (Target<Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
//使用較少
return (Target<Z>) new DrawableImageViewTarget(view);
} else {
throw new IllegalArgumentException("Unhandled class: " + clazz
+ ", try .as*(Class).transcode(ResourceTranscoder)");
}
}
into():
三、新建一個Request綁定到Target上
四、發送Request 交給RequestTracker進行相應的處理
public <Y extends Target<TranscodeType>> Y into(Y target) {
//是否在主線程 (更新UI必須在主線程)
Util.assertMainThread();
…
一、對舊的綁定的Target對象進行清除
//獲取固然Target對象所綁定的舊的Request對象
Request previous = target.getRequest();
if (previous != null) {
//清理操做
previous.clear();
//取消當前Request請求避免圖片錯位
requestTracker.removeRequest(previous);
//在該實現類中將成員變量賦值爲null
ps:並調用REQUEST_POOL.offer(this);當前一個Request不用的時候會被放入請求池以備複用
previous.recycle();
}
二、建立一個加載圖片的Request
ps:list圖片錯位的問題?
解決:給View綁定setTag(),將view和圖片進行綁定
Request request = buildRequest(target);
//將圖片Request請求和ImageView綁定
target.setRequest(request);
lifecycle.addListener(target);
//三、將Request發送給RequestTracker執行request請求
requestTracker.runRequest(request);
return target;
}
如何buildRequest?
buildRequest():————>buildRequestRecursive()
...
obtainRequest()————>
GenericRequest.obtain():實際建立Request()
GenericRequest.obtain():
//從線程池中獲取一個請求,若是有能夠複用的就複用一個請求,若是沒有就建立一個新的
...
GenericRequest<A, T, Z, R> request =
(GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
if (request == null) {
request = new GenericRequest<A, T, Z, R>();
}
request.init();//初始化操做
return request;
requestTracker.runRequest:
public void runRequest(Request request) {
//將當前request加入到set<Request> 集合當中
requests.add(request);
//對當前狀態進行判斷,當前是有Request請求進入else ,將當前Request加入pending懸掛中的集合中
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}
request.begin():{
…
//判斷前面是否調用了overrideWidth 限定寬高的方法,直接執行onSizeReady()
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
//沒有限定款高經過getSize()計算寬高
target.getSize(this);
}
//當前狀態是否已經完成,圖片是否不是失敗狀態進行判斷
if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
//設定圖片(獲取佔位圖)
target.onLoadStarted(getPlaceholderDrawable());
...
}
target.getSize——>
public void getSize(SizeReadyCallback cb) {
sizeDeterminer.getSize(cb);
}
getSize():內部根據ImageView寬高進行再次計算後再執行onSizeReady()
public void getSize(SizeReadyCallback cb) {
//一、獲取寬高
int currentWidth = getViewWidthOrParam();
int currentHeight = getViewHeightOrParam();
//三、當View繪製完時候就會進入onSizeReady()方法
if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
cb.onSizeReady(currentWidth, currentHeight);
}
…
//二、當前View沒有被測量完,會被添加到viewTreeObserver觀察者中
final ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
observer.addOnPreDrawListener(layoutListener);
一、經過LoadProvider()方法獲取ModelLoader和Transcoder
二、根據ModelLoader獲取DataFetcher
三、engine.load 進行實際圖片加載
ModelLoader:從數據源中獲取原始數據,通常是輸入流inputStream,在Glide中被封裝成Data
DataFetcher:將Data原始數據轉換成能直接用的不一樣形式的圖片數據類型
ResourceTranscoder:將原始數據解碼,將io輸入流解碼成bitmap的解碼工具對象
Resource:解碼後的資源
onSizeReady
(int width, int height) {:
…
ModelLoader<A, T> modelLoader
= loadProvider.getModelLoader();
//經過loadProvider接口獲取到dataFetcher、loadProvider、transcoder
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
…
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);
}
LoadProvider():GenericRequest的成員變量
public interface LoadProvider<A, T, Z, R> extends DataLoadProvider<T, Z> {
ModelLoader<A, T> getModelLoader();
ResourceTranscoder<Z, R> getTranscoder();
LoadProvider初始化:在GenericRequest的實現類
DrawableTypeRequest(...) {
super(context, modelClass,
buildProvider(glide, streamModelLoader, fileDescriptorModelLoader,)
...
}
buildProvider的返回值就是LoadProvider,FixedLoadProvider是LoadProvider的實現類:獲取原始圖片,進行編解碼、轉碼等功能
private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide,
ModelLoader<A, InputStream> streamModelLoader,
ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader...) {
//判斷來自不一樣數據的Loader
if (streamModelLoader == null && fileDescriptorModelLoader == null) {
return null;
}
//ResourceTranscoder是否爲空,爲null則經過Glide建立新的Transcoder
ps:對原始數據進行解碼
if (transcoder == null) {
transcoder = glide.buildTranscoder(resourceClass, transcodedClass);
}
//DataLoadProvider對圖片進行編解碼的接口,實現類中LoadProvider
DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider
= glide.buildDataProvider(ImageVideoWrapper.class,resourceClass);
//封裝了兩個ModelLoader
ImageVideoModelLoader<A> modelLoader = new
ImageVideoModelLoader<A>(streamModelLoader,fileDescriptorModelLoader);
return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>
(modelLoader, transcoder, dataLoadProvider);
}
DataLoadProvider:負責編解碼
Data:從數據源獲取的數據
Resource:解碼後的資源
解碼:Data————>Resource 資源
編碼:將Data、Resource———>持久化到本地的過程 用於實現Glide的磁盤緩存功能
engine.load():
engine:負責圖片加載,管理正在使用和處於緩存的圖片類
LoadStatus load(){
…
//加載圖片的惟一標識id,好比加載網絡圖片的話就是一個網絡圖片的url地址
final String id = fetcher.getId();
//傳入惟一標識id ,其餘不少參數依然能夠決定EngineKey的值,構建Glide緩存中的一個key值
EngineKey key = keyFactory.buildKey(id,…);
...
//從緩存中獲取圖片
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
//若緩存圖片是空的則調用loadFromActiveResources
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
…
//若二者都沒有獲取到就本身開啓一個runnable 從磁盤或是網絡進行圖片的獲取
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
engineJob.start(runnable);
}
GLide中的內存緩存策略
GLide 構建內存緩存的組成部分:cache、active
A、內存緩存讀取操做原理
一、 LruCache算法:近期最少使用算法,將最近使用的對象的強引用存儲在LinkHashMap上;而且把最近最少使用的對象,在緩存池達到預設值以前從內存中移除
二、弱引用緩存機制
Glide中的內存緩存對象:Engine類中的 Load()函數
MemoryCache——loadFromCache(EngineKey,boolean isMemoryCacheable)
cached :最近使用過的而當前不在使用的EngineResource,LoadFromCache內部使用linkHashMap當內存達到閥值會經過LruCache算法進行清除操做
loadFromCache():一、從內存緩存MemoryCache中獲取圖片
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
//配置Glide 的時候使用的isMemoryCacheable,跳過內存緩存操做skipMemoryCache(true)
默認狀況下爲true 的時候開啓緩存模式
if (!isMemoryCacheable) {
return null;
}
//獲取實際緩存對象
EngineResource<?> cached = getEngineResourceFromCache(key);
//ps:二、在該方法中會使用緩存的key值 從cache中獲取值並調用remove函數把這個圖片從MemoryCahce緩存中移除掉
//Resource cached = this.cache.remove(key);
if (cached != null) {
cached.acquire();
//寫入一個弱引用的 緩存
activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
}
return cached;
}
ps:使用弱引用的好處,使得該部分緩存不會被Lru算法給回收掉資源
loadFromActiveResources():三、調用該方法從正在使用的部分中繼續獲取圖片
ActiveResource——loadFromActiveResources(EngineKey,boolean isMemoryCacheable)——HashMap<key,WeakReference<EngineResource>>
active:保存當前正在使用的EngineResource對象
ps: 會以EngineKey爲鍵,以EngineResource的弱引用爲值
HashMap的Get()、 Put()函數對應着 緩存的讀和取 的操做
//四、若前兩步都沒有獲取到圖片資源,就建立一個Engine runnbale對象加載,從磁盤或是網絡獲取圖片
EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
transcoder, diskCacheProvider, diskCacheStrategy, priority);
EngineRunnablerunnable = new EngineRunnable(engineJob, decodeJob, priority);
jobs.put(key, engineJob);
B、內存緩存寫入操做原理
if (cached != null) {
cb.onResourceReady(cached);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
cb:callback回調
onResourceReady :ResourceCallback的具體實現
在onResourceReady()中:定義一個handler發送message,將執行邏輯切回到主線程中操做
public void onResourceReady(final Resource<?> resource) {
this.resource = resource;
MAIN_THREAD_HANDLER.obtainMessage(
MSG_COMPLETE, this).sendToTarget();
}
其中MAIN_THREAD_HANDLER是MainThreadCallback回調定義:
private static final Handler MAIN_THREAD_HANDLER =
new Handler(Looper.getMainLooper(),
new MainThreadCallback());
MainThreadCallback():接受Message信息並處理的callback回調,經過名字能夠知道該消息是在主線程進行結果回調
private static class MainThreadCallbackimplements Handler.Callback {
@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();
}
...
}
job.handleResultOnMainThread():
private void handleResultOnMainThread() {
//一、若是任務取消則回收資源 recycle
if (isCancelled) {
resource.recycle();
return;
//二、若callback集合爲null 空 則拋出異常
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
}
//三、經過工廠累建立包含圖片資源的engineResource對象
engineResource = engineResourceFactory.build(resource, isCacheable);
...
//三、將對象回調到onEngineJobComplete()當中
engineResource.acquire();
//四、acquire():記錄圖片被引用的次數,同時在該函數結尾經過 ++this.acquire 來進行圖片次數的+1 操做;當acquired >0 表示有圖片正在使用中,也就是應該把圖片放入到activeResource 這個弱引用緩存中
listener.onEngineJobComplete(key, engineResource);
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource);
}
}
//五、release() 當圖片等於0的時候表示圖片沒有在使用了就調用onResourceReleased釋放資源操做,該實如今Engine()函數中
engineResource.release();
}
onEngineJobComplete():
public void onEngineJobComplete(Key key, EngineResource<?> resource) {
Util.assertMainThread();
if (resource != null) {
resource.setResourceListener(key, this);
if (resource.isCacheable()) {
//最終調用activeResources.put()這個函數進行了緩存的寫入操做
ps:這裏寫入的是弱引用的緩存
activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
}
}
// TODO: should this check that the engine job is still current?
jobs.remove(key);
}
onResourceReleased():
public void onResourceReleased(Key cacheKey, EngineResource resource) {
Util.assertMainThread();
//一、把整個緩存圖片從activeResources從緩存圖片中刪除
activeResources.remove(cacheKey);
if (resource.isCacheable()) {
//二、經過put()加入到LruCache算法中,
ps:實現正在使用的圖片經過弱引用進行緩存,而不在使用的利用LruCache進行緩存
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
磁盤緩存讀寫 待補充
5、Glide經常使用
5.1 、Target函數的使用- Glide獲取 Bitmap資源自己
Target其實就是整個圖片的加載的生命週期,經過它在圖片加載完成以後獲取Bitmap
以 SimpleTarget 爲例:
private SimpleTarget<Bitmap> mSimpleTarget= new SimleTarget<Bitmap> (可添加寬*高 尺寸限定){
@Override
public void onResourceReady( Bitmap resource,
GlideAnimation< ? super Bitmap>animation) {
ImageView.setImageBitmap(resource);
}
};
private void loadImageSimpleTarget(){
Glide .with (1 ). load(url ) . asBitmap() . into ( mSimpleTarget );
}
在這裏若是SimpletTarget 使用匿名內部類的方式建立 SimpleTarget 的對象,這樣會增大該對象在 Glide 完成圖片請求以前就被回收的可能性。
(1):這裏若是傳入的是Target ,可能獨立於with的生命週期以外,因此最好給with裏傳入參數context .getApplicationContext(),讓Glide持有整個app的生命週期。
5.二、ViewTarget函數的使用
在自定義View的時候,Glide有時候並不支持加載圖片到自定義View中的時候,這時候就須要ViewTarget
public void loadImageTarget(Context context){
CustomView mCustomView = (CustomView) findViewById(R.id.custom_view); ViewTarget viewTarget =
new ViewTarget<CustomView,GlideDrawable>( mCustomView) {
@Override
public void onResourceReady(GlideDrawable resource,
GlideAnimation<? super GlideDrawable> glideAnimation) { this.view.setImage(resource);
}
};
Glide.with(context) .load(mUrl) .into(viewTarget);
}
onResourceReady回調方法中使用了自定義view本身的方法設置圖片,能夠看到在建立ViewTarget的時候傳入了CustomView 對象
5.三、Transformations函數的使用
若是須要對圖片進行圖片切圓角、灰階處理 等功能 就須要 經過 Transformations來操做bitmap 。經過修改尺寸、範圍、顏色、像素、位置等達到需求。
ps:如果對圖片進行常規的bitmap轉換的話,可使用抽象類 BitmapTransformation 進行
對圖片切圓角操做 :getId()是圖片的惟一標識符必須惟一
public class RoundTransformation extends BitmapTransformation {
private float radius = 0f;
public RoundTransformation(Context context) {
this(context, 4);
}
public RoundTransformation(Context context, int px) {
super(context);
this.radius = px;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
int outWidth, int outHeight) {
return roundCrop(pool, toTransform);
}
private Bitmap roundCrop(BitmapPool pool, Bitmap source) {
if (source == null)
return null;
Bitmap result = pool.get(source.getWidth(),
source.getHeight(), Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(source.getWidth(),
source.getHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP,
BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
RectF rectF = new RectF(0f, 0f, source.getWidth(),
source.getHeight()); canvas.drawRoundRect(rectF, radius,radius, paint);
return result;
}
@Override
public String getId() {
return getClass().getName() + Math.round(radius);
}
}
下面是transform的調用:
Glide.with(context)
.load(mUrl)
.transform(new RoundTransformation(context , 20))
//.bitmapTransform( new RoundTransformation(context , 20) ) .into(mImageView);
ps:若須要同時執行多個transformation的話 ,不能再像這樣經過鏈式調用的形式屢次調用.transform() 或是.bitmapTransform() 方法。上面的transform會被最後一個覆蓋掉。
這時候能夠這樣作:
Glide.with(context)
.load(mUrl)
.transform(new RoundTransformation(context , 20)
, new RotateTransformation(context , 90f))
.into(mImageView);
對圖片旋轉操做
public class RotateTransformationextends BitmapTransformation {
private float rotateRotationAngle = 0f;
public RotateTransformation(Context context, float rotateRotationAngle) {
super( context );
this.rotateRotationAngle = rotateRotationAngle;
}
@Override
protected Bitmap transform(BitmapPool pool,
Bitmap toTransform, int outWidth, int outHeight) {
Matrix matrix = new Matrix();
matrix.postRotate(rotateRotationAngle);
return Bitmap.createBitmap(toTransform, 0, 0,
toTransform.getWidth(), toTransform.getHeight(), matrix, true);
}
@Override
public String getId() {
return getClass().getName() + Math.round(rotateRotationAngle);
}
}
ps:這裏須要注意一點 .centerCrop() 和 .fitCenter() 也都是 Transformation 因此也是遵循同時使用多個 Transformation 的規則的,即:當你使用了自定義轉換後你就不能使用 .centerCrop() 或 .fitCenter() 了。
這裏有一個 GLide Transformations 的庫,它提供了不少 Transformation 的實現,很是值得去看,沒必要重複造輪子對吧!
5.四、Animate動畫函數的使用
圖片間切換的平滑過渡 是很是重要的!!因此Glide又一個標準動畫去柔化Ui中的改變,可是若是是設置本身的動畫的話:
一個小例子:
<set
<scale
android:duration="@android:integer/
config_longAnimTime」
android:fromXScale="0.1」
android:fromYScale="0.1」
android:pivotX="50%」
android:pivotY="50%」
android:toXScale="1」
android:toYScale="1"/>
</set>
XML 動畫縮放動畫,圖片剛開始小的,而後逐漸增大到原尺寸。咱們如今要應用到 Glide 加載圖片中去,調用 .animate() 方法傳入 XML 動畫的 id 便可。
Glide.with(context)
.load(mUrl)
.transform(new RoundTransformation(this , 20))
.animate( R.anim.zoom_in )
.into(mImageView);
animate在Target中的使用:
ViewPropertyAnimation.Animatoranimator
= new ViewPropertyAnimation.Animator() {
@Override
public void animate(View view) {
view.setAlpha( 0f );
ObjectAnimator fadeAnim
= ObjectAnimator.ofFloat( view, "alpha", 0f, 1f );
fadeAnim.setDuration( 2500 );
fadeAnim.start();
}
};
Glide.with(context)
.load(mUrl)
.animate( animator )
.into(viewTarget);
5.五、Modules函數的使用
Glide 的Module 是一個能夠全局改變Glide 的行爲的東西。爲了實現需求須要去實現 interface GlideModule 來實現功能
public class ExampleModule implements GlideModule{
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// todo
}
@Override
public void registerComponents(Context context, Glide glide) {
// todo
} }
主要使用的是 applyOptions(Context context, GlideBuilder builder) , 咱們本身的須要從新定義的代碼寫在該方法裏就能夠了。而後咱們還須要去 AndroidManifest.xml 中使用 meta 聲明咱們上面實現的 Module:
<application>
<meta-data android:name
="com.mrtrying.demoglide.module.ExampleModule" android:value="GlideModule" />
... </application>
到這裏咱們就完成了 ExampleModule 的聲明,Glide 將會在工做是使用咱們所定義的 Module
TIPS
-
咱們須要將 android:name 屬性改爲 包名+類名 的形式,這樣的引用纔是正確的。若是你想刪掉 Glide Module,只須要刪除在 AndroidManifest.xml 中的聲明就能夠了。Java 類能夠保存,說不定之後會用呢。若是它沒有在 AndroidManifest.xml 中被引用,那它不會被加載或被使用。
-
定製 module 的話 Glide 會有這樣一個優勢:你能夠同時聲明多個 Glide module。Glide 將會(沒有特定順序)獲得全部的聲明 module。由於你當前不能定義順序,請確保定製不會引發衝突!
applyOptions(Context context, GlideBuilder builder) 中有兩個參數, 咱們經過使用 GlideBuilder 來實現咱們的需求。先看看 GlideBuilder 中可用的方法
- .setMemoryCache(MemoryCache memoryCache)
- .setBitmapPool(BitmapPool bitmapPool)
- .setDiskCache(DiskCache.Factory diskCacheFactory)
- .setDiskCacheService(ExecutorService service)
- .setResizeService(ExecutorService service)
- .setDecodeFormat(DecodeFormat decodeFormat)
能夠看到,這個 GlideBuilder 對象給你訪問了 Glide 重要的核心組件。接下來咱們就要試着去使用這些方法
增長 Glide 的圖片質量
在 Android 中有兩個主要的方法對圖片進行解碼:ARGB_8888 和 RGB_565 。前者爲每一個像素使用4個字節,後者每一個像素僅使用2個字節。ARGB_8888 的有時就是圖像質量更高以及能儲存一個 alpha 通道。 Picasso 使用的就是 ARGB_8888 , Glide 默認使用低質量的 RGB_565 ,可是如今你就可使用 Glide module 來改變圖片解碼規則。就象這樣
public class QualityModule implements GlideModule{
@Override
public void applyOptions(Context context , GlideBuilder builder){
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
}
@Override
public void registerComponents(Context context , Glide glide){
// nothing to do here
}
}
這樣咱們就簡單的增長了 Glide 的圖片質量。
每每咱們還會遇到一些狀況,但願 Glide 可使用咱們本身的網絡框架,咱們就須要作一些事情來實現這個需求了。Glide 的開發者不強制設置網絡庫給你,因此Glide能夠說和 HTTPS 無關。理論上,它能夠與任何的網絡庫實現,只要覆蓋了基本的網絡能力就行。一樣是須要實現 Glide 的 ModuleLoader 的接口,爲了讓咱們更加易用,Glide 爲 OkHttp 和 Volley 兩個網絡庫提供了實現。
假設我要集成 OkHttp 做爲 Glide 的網絡庫,我能夠手動實現一個 GlideModule 也能夠在 build.gradle 中添加依賴:
dependencies{
//...
// Glide
compile 'com.github.bumptech.glide:glide:3.7.0'
// Glide's OkHttp Integration
compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
compile 'com.squareup.okhttp:okhttp:3.2.0'
}
Gradle 會自動合併必要的 GlideModule 到你的 AndroidManifest.xml , Glide 會承認在 manifest 中存在,而後使用 OkHttp 作到的全部網絡鏈接!!
做者:MrTrying
來源:簡書
以上一小節選取了 該文章的內容多謝分享!
6、Gilde相關知識補充-bitmap &oom & 優化bitmap
4.6.1 bitmap 致使OOM
a、在使用list類View
1、加載大量View組件又沒有合理的處理緩存的狀況下,大量加載bitmap很容易形成OOM。
解決方法:
(1)三級緩存
(2)設置listView的監聽事件,當滑動的時候不進行圖片的加載,當中止滑動的時候再加載
2、圖片分辨率愈來愈高,消耗的內存愈來愈大
注意:圖片Bitmap所佔用的內存 = 圖片長度 * 圖片寬度 * 一個像素點佔用的字節數
3、VM值上限dalvik.vm.heapgrowthlimit
限定了每一個app 可用的最大內存,若是超過這個值就會出現OOM現象
4.6.2 bitmap的4種優化策略
1、對圖片質量進行壓縮
private Bitmap compressImage(Bitmap image) {
//一、建立字節輸出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//二、Bitmap.CompressFormat進行壓縮(類型,值越小表明壓縮比例越大,100表示不壓縮,壓縮好的圖片放在字節輸出流)
image.compress(Bitmap.CompressFormat.JPEG
, 100, baos);
int options = 100;
//三、循環判斷,壓縮好的圖片大小是否大於100kb,大於就進一步壓縮
while (baos.toByteArray().length / 1024 > 100) {
//3.一、清空輸出流
baos.reset();
//3.二、再次壓縮
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
//3.三、每次減小10options 數值
options -= 10;
}
//四、將壓縮好的數據放入到字節輸入流中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
//五、經過BitmapFactory.decodeStream完成bitmap的重建
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
return bitmap;
}
2、對圖片按比例進行壓縮
/**
* 圖片比例壓縮
*/
private Bitmap comBitmapInSize(Bitmap image) {
//一、建立字節數組輸入輸出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteArrayInputStream isBm;
//二、建立BitmapFactory的Options內部類對象,經過該對象能夠對bimtap進行寬高、內存等的控制
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//三、解碼bitmap只返回它的寬高數值,而沒必要爲他申請內存,經過設爲true能夠節省內存空間
newOpts.inJustDecodeBounds = true;
Bitmap bitmap;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
float hh = 1290f; //設置高度爲1280f
float ww = 720f; //設置寬度爲720f
//四、建立be縮放比例
int be = 1;
if (w > h && w > ww) {
//4.一、若是寬大於高的話,就根據寬的大小進行縮放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {
//4.二、若是寬小於高的話,就根據高的大小進行縮放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
//五、將計算好的比例數字be 賦值給Options中的縮放比例變量inSamplesize
newOpts.inSampleSize = be;
//六、將Options的inJustDecodeBounds設置爲false表示須要將圖片加載到內存中
newOpts.inJustDecodeBounds = false;
isBm = new ByteArrayInputStream(baos.toByteArray());
//七、重建bitmap
bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
return bitmap;
}
3、關於bitmap的recycle方法(含有爭議話題)
/**
* Free the native object associated with this bitmap, and clear the
* reference to the pixel data. This will not free the pixel data synchronously;
* it simply allows it to be garbage collected if there are no other references.
* The bitmap is marked as "dead", meaning it will throw an exception if
* getPixels() or setPixels() is called, and will draw nothing. This operation
* cannot be reversed, so it should only be called if you are sure there are no
* further uses for the bitmap. This is an advanced call, and normally need
* not be called, since the normal GC process will free up this memory when
* there are no more references to this bitmap.
*/
/**
* 當釋放這個bitmap相關聯的native對象的時候,它會清除像素數據。ps:不會同步的釋放像素數據,只是根據在沒有其餘引用的狀況下收集 該GC垃圾,同時將狀態標記爲dead狀態。這時候意味着調用 setPixels() 或是getPixels()時候都會拋出異常。而不回去繪製任務東西。一般狀況下不須要手動的調用,當其沒有引用這個bitmap的時候,正常的垃圾回收進程會釋放掉該部份內存。
*
*/
public void recycle() {
...
}
註解: 手動的調用recycle()自己可能並無被釋放資源。因此硬要調用recycle的時候要將bitmap設置爲null,好讓GC和垃圾回收器回收這部分的對象。
4、捕獲異常
針對OOM 的區域進行 異常捕獲
public void handleBitmapCrash() {
Bitmap bitmap = null;
String path = "xx/xx/xx";
try {
//實例化bitmap
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
}
7、Gilde相關知識補充-三級緩存 /LruChache算法
Android的緩存策略-三級緩存
內存 - 本地 - 網絡
意義:
第一次從網絡獲取 ,以後將獲取的圖片保存到本地和內存各一份,當程序再次用到該圖片的時候首先會從內存中判斷是否有緩存,有的話直接從內存中獲取,沒有的話再從本地SD卡中進行獲取。若是內存和本地均沒有的話,再次從網絡上獲取。
思考:內存緩存是如何實現的呢?
緩存主要是經過添加 、獲取、刪除 進行操做的。
不論內存緩存仍是硬盤緩存,緩存的大小都是固定的,有個max限制。當該部分緩存用滿以後其餘緩存就沒法再添加,因此須要進行刪除無用緩存的操做。這時候就須要一個刪除舊緩存的算法,也就說常說的LRU(Least Recently Used)近期最少使用緩存算法
LruChache算法
LRU核心
當緩存已滿的時候會優先淘汰掉最近使用最少的緩存對象
整個Lru算法的核心是LinkedHashMap,其繼承自HashMap,其構造函數中會有一個boolean類型的值,尤爲控制插入的順序,a是Lru順序,b是正常順序
public class LruCache<T, Y> {
private final LinkedHashMap<T, Y> cache= new LinkedHashMap<T, Y>(100, 0.75f(負載因子), true(當爲true時候說明整個順序是經過Lru算法順序刪除元素的));
int size : 當前緩存的大小
int maxSize : 當前緩存的最大值
int putCount : put方法調用的次數
int createCount : create方法調用的次數
int evictionCount : 當須要淘汰一些緩存變量時候的計數器
int hitCount : 緩存對象使用到hitCount +1 計數
int missCount : 緩存未命中 計數 +1
Get():
傳入key值 獲取相應的item,如果無緩存會建立並返回相應的item,相應的item會被移動到隊列的尾部。
public final V get(K key){
if(key == null ) {
throw new NullPointerException(「key==null"):
}
V mapValue;
//利用LinkedHashMap的get方法獲取相應的value
synchronized(this){
mapValue = map.get(key);
if(mapValue !=null){
//獲取到緩存 hitCount+1 並返回Value值
hitCount++;
return mapValue;
}
missCount++ ;
}
//未命中,調用create()建立一個對象
V createdValue = create(key);
if(createdValue == null) {
return null;
}
synchronized (this ){
createCount++;
//覆蓋原有Value值並添加到mapValue中
mapValue = map.put(key , createdValue);
if(mapValue !=null){
//若是該值不爲空,將從新建立好的cratedValue又覆蓋成原來的mapValue,逆推上一步操做
map.put(key,mapValue); //插入的新的對象會存儲到列表的尾端
}else{
//加入新建立的對象須要從新建立緩存size大小
size += safeSizeOf(key ,createValue);
}
if(mapValue != null){
entryRemoved(false, key,createdValue,mapValue);
return mapValue;
}else {
//新加入對象都會調用trimToSize(),來查看對象是否須要被回收。根據傳入的緩存的最大容量調整緩存的大小。傳入 -1 表示清空全部緩存對象
trimToSize(maxSize);
return createdValue;
}
}
Put():
public final V put (K key , V value){
if(key == null || value == null) {
throw new NullPointerException (「key ==null ||value ==null ")
}
V previos ;
synchronized (this ){
//調用put方法每次計數+1
putCount ++;
// 每次put都會形成整個緩存大小增大,因此須要自增當前緩存
size += safeSizeOf (key ,value );
previous= map .put(key , value );
//若是以前存在key爲鍵值的對象,這時候當前的緩存size 須要減掉這個對象的大小
if( previous !=null){
size -= safeSizeOf (key ,previous);
}
}
if(previous !=null ){
entryRemoved (false ,key ,previous ,value);
}
//調整內存緩存大小,插入的新對象會存儲在列表的尾端
trimToSize (maxSize);
return previous;
}
- LruCache中將LinkedHashMap的順序設置爲LRU順序來實現LRU緩存
- 每次調用get則將該對象移到鏈表的尾端
- 調用put插入新的對象也是存儲在鏈表尾端
- 當內存緩存達到設定的最大值時候,將鏈表頭部的對象(近期最少使用到的)移除