[TOC]
# Glide使用
簡單使用:
```java
@Override
public void onCreate(Bundle savedInstanceState) {
//...
ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
Glide.with(this)
.load("http://goo.gl/gEgYUd")
.asBitmap() // 只允許加載靜態圖片,同理有asGif()方法
.placeholder(R.drawable.loading) // 占位圖
.error(R.drawable.error) // 異常占位圖
.override(100, 100) // 指定加載圖片的尺寸
.into(imageView);
}
```
其中,load方法有以下幾種重載:
```java
// 從字節數組加載
public DrawableTypeRequest<byte[]> load(byte[] model) {}
// 從File對象加載
public DrawableTypeRequest<File> load(File file) {}
// 從資源文件加載
public DrawableTypeRequest<Integer> load(Integer resourceId) {}
// 從File路徑或UriLoader構造的url、uri來加載
public DrawableTypeRequest<String> load(String string) {}
// 從ModelLoaderFactory生成的model來加載
public <T> DrawableTypeRequest<T> load(T model) {}
// 從UriLoader生成的Uri來加載
public DrawableTypeRequest<Uri> load(Uri uri) {}
```
# Glide源碼分析
## with方法
with方法的主要作用有兩個,一是獲得一個RequestManager對象,二是確定Glide的生命周期,當傳入全局Application Context時,Glide生命周期等同于應用生命周期;當傳入Activity或Fragment時,Glide會向當前Activity中添加一個隱藏的Fragment,來確定自身的生命周期,比如加載圖片過程中Activity被銷毀了,隱藏的Fragment是可以感知的,這樣Glide的加載就可以同步終止。
with方法有以下幾個重載:
```java
public static RequestManager with(Context context) {}
public static RequestManager with(Activity activity) {}
public static RequestManager with(FragmentActivity activity) {}
public static RequestManager with(Fragment fragment) {}
```
每一種方法都是根據入參獲取RequestManager:
```java
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
```
下面看看retriever的get方法:
```java
public RequestManager get(Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} 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());
}
}
return getApplicationManager(context);
}
```
可以看到,當前為主線程且Context非Application時是一種處理方式,其他是一種處理方式。當context為Application時,直接創建RequestManager對象:
```java
private RequestManager getApplicationManager(Context context) {
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
applicationManager = new RequestManager(context.getApplicationContext(),
new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
}
}
}
return applicationManager;
}
```
而當Context為Activity、Fragment、FragmentActivity時,處理如下:
```java
public RequestManager get(FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm);
}
}
```
可以看到,如果是在子線程的話,就按Context為Application來進行處理。否則調用supportFragmentGet方法來創建RequestManager:
```java
RequestManager supportFragmentGet(Context context, FragmentManager fm) {
SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
```
上面代碼中SupportRequestManagerFragment就是要添加的隱藏Fragment。
## load方法
load方法是RequestManager中的方法,有多個重載,上文已經提到,下面我們看看其中的一個重載方法:
```java
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string);
}
```
```java
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
```
```java
private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
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("Unknown type " + modelClass + ". You must provide a Model of a type for"
+ " which there is a registered ModelLoader, if you are using a custom model, you must first call"
+ " Glide#register with a ModelLoaderFactory for your custom model class");
}
return optionsApplier.apply(
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}
```
在loadGeneric方法中,會生成ModelLoader對象,ModelLoader對象時用于加載圖片的,根據load方法傳入的參數不同,會得到不同的ModelLoader對象,剛剛傳入的是String.class對象,因此得到的是StreamStringLoader對象,實現了ModelLoader接口。
loadGeneric方法最后創建了一個DrawableTypeRequest對象,DrawableTypeRequest類主要是提供了asBitmap和asGif兩個方法,這兩個方法會返回BitmapTypeRequest和GifTypeRequest,當然不調用asBitmap和asGif的情況下,默認返回DrawableTypeRequest對象。
接下來看看fromString后的load方法,由于在DrawableTypeRequest類中沒有load方法,因此到其父類DrawableRequestBuilder中看看,在該類源碼中,可以看到load、placeholder、error、centerCrop等等方法,基本就是我們常用的那些。
## into方法
into方法同樣是在DrawableRequestBuilder中定義的:
```java
public Target<GlideDrawable> into(ImageView view) {
return super.into(view);
}
```
調用了父類的方法:
```java
public Target<TranscodeType> into(ImageView view) {
//...
return into(glide.buildImageViewTarget(view, transcodeClass));
}
```
先是調用glide.buildImageViewTarget()方法構建出一個Target對象,Target對象是用來最終展示圖片用的。來看看是怎么構建出的:
```java
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
```
```java
public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
if (GlideDrawable.class.isAssignableFrom(clazz)) {
return (Target<Z>) new GlideDrawableImageViewTarget(view);
} else if (Bitmap.class.equals(clazz)) {
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)");
}
}
```
會根據傳入的class參數來構建不同的Target對象,我們在使用Glide加載圖片的時候調用了asBitmap()方法,那么這里就會構建出BitmapImageViewTarget對象,否則的話構建的都是GlideDrawableImageViewTarget對象。其中的DrawableImageViewTarget對象通常都是用不到的。
接下來回來看看拿到Target對象后調用的into方法:
```java
public <Y extends Target<TranscodeType>> Y into(Y target) {
//...
Request request = buildRequest(target);
target.setRequest(request);
lifecycle.addListener(target);
requestTracker.runRequest(request);
return target;
}
```
首先調用buildRequest方法出那個鍵一個Request對象,然后再執行這個Request。Request在Glide中是用來發出加載圖片請求的,是一個非常關鍵的組件。先來看看Request是如何build出來的:
```java
private Request buildRequest(Target<TranscodeType> target) {
return buildRequestRecursive(target, null);
}
```
```java
private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
//前面省略處理縮略圖相關代碼
return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
}
```
```java
private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
RequestCoordinator requestCoordinator) {
return GenericRequest.obtain(
loadProvider,
model,
signature,
context,
priority,
target,
sizeMultiplier,
placeholderDrawable,
placeholderId,
errorPlaceholder,
errorId,
fallbackDrawable,
fallbackResource,
requestListener,
requestCoordinator,
glide.getEngine(),
transformation,
transcodeClass,
isCacheable,
animationFactory,
overrideWidth,
overrideHeight,
diskCacheStrategy);
}
```
這里調用了GenericRequest的obtain方法:
```java
public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(
LoadProvider<A, T, Z, R> loadProvider,
A model,
Key signature,
Context context,
Priority priority,
Target<R> target,
float sizeMultiplier,
Drawable placeholderDrawable,
int placeholderResourceId,
Drawable errorDrawable,
int errorResourceId,
Drawable fallbackDrawable,
int fallbackResourceId,
RequestListener<? super A, R> requestListener,
RequestCoordinator requestCoordinator,
Engine engine,
Transformation<Z> transformation,
Class<R> transcodeClass,
boolean isMemoryCacheable,
GlideAnimationFactory<R> animationFactory,
int overrideWidth,
int overrideHeight,
DiskCacheStrategy diskCacheStrategy) {
@SuppressWarnings("unchecked")
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(loadProvider,
model,
signature,
context,
priority,
target,
sizeMultiplier,
placeholderDrawable,
placeholderResourceId,
errorDrawable,
errorResourceId,
fallbackDrawable,
fallbackResourceId,
requestListener,
requestCoordinator,
engine,
transformation,
transcodeClass,
isMemoryCacheable,
animationFactory,
overrideWidth,
overrideHeight,
diskCacheStrategy);
return request;
}
```
可以看到obtain方法實際就是獲得一個GenericRequest對象,同時對成員變量進行初始化。
拿到Request后,我們看看requestTracker.runRequest()方法中Request是怎么執行的:
```java
public void runRequest(Request request) {
requests.add(request);
//...
request.begin();
}
```
下面看看GenericRequest的begin方法:
```java
public void begin() {
startTime = LogTime.getLogTime();
if (model == null) {
onException(null);
return;
}
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished run method in " + LogTime.getElapsedMillis(startTime));
}
}
```
在begin方法中,我們主要關注以下幾行代碼:
* onException(null);
* onSizeReady(overrideWidth, overrideHeight);
* target.getSize(this);
* target.onLoadStarted(getPlaceholderDrawable());
下面對這四個方法我們一個一個分析
1、onException(null);
可以看到,如果model等于null,也就是在load方法中傳入的圖片URL地址為空,會調用onException方法,onException方法中會調用setErrorPlaceholder方法:
```java
public void onException(Exception e) {
//...
setErrorPlaceholder(e);
}
```
```java
private void setErrorPlaceholder(Exception e) {
Drawable error = model == null ? getFallbackDrawable() : null;
if (error == null) {
error = getErrorDrawable();
}
if (error == null) {
error = getPlaceholderDrawable();
}
target.onLoadFailed(e, error);
}
```
會先去獲取一個error的占位圖,獲取不到的話會去獲取一個loading的占位圖,然后調用target.onLoadFailed方法并傳入占位圖:
```java
public void onLoadFailed(Exception e, Drawable errorDrawable) {
view.setImageDrawable(errorDrawable);
}
```
將占位圖設置到ImageView即可。
2、target.onLoadStarted(getPlaceholderDrawable());
```java
public void onLoadStarted(Drawable placeholder) {
view.setImageDrawable(placeholder);
}
```
設置占位圖的代碼也很簡單
3、onSizeReady()和target.getSize()
當使用了override方法給圖片指定了固定寬高時,調用onSizeReady()方法;沒指定時,調用target.getSize()方法,target.getSize()方法的內部會根據ImageView的layout\_width和layout\_height值做一系列的計算,來算出圖片應該的寬高,然后再根據寬高調用onSizeReady()方法。那么來看看onSizeReady()方法:
```java
```
# Glide緩存原理
## 緩存機制
Glide的緩存機制,主要分為2種緩存,一種是內存緩存,一種是磁盤緩存。
使用內存緩存的原因是:防止應用重復將圖片讀入到內存,造成內存資源浪費;使用磁盤緩存的原因是:防止應用重復的從網絡或者其他地方下載和讀取數據。正是因為有著這兩種緩存的結合,才構成了Glide極佳的緩存效果。
## 緩存算法
1、LruCache最近最少使用算法,設定一個緩存大小,當緩存達到這個大小之后,會將最老的數據移除,避免圖片占用內存過大導致OOM。LruCache 內部用LinkHashMap存取數據,在雙向鏈表保證數據新舊順序的前提下,設置一個最大內存,往里面put數據的時候,當數據達到最大內存的時候,將最老的數據移除掉,保證內存不超過設定的最大值。
2、LinkHashMap繼承HashMap,在 HashMap的基礎上,新增了雙向鏈表結構,每次訪問數據的時候,會更新被訪問的數據的鏈表指針,具體就是先在鏈表中刪除該節點,然后添加到鏈表頭header之前,這樣就保證了鏈表頭header節點之前的數據都是最近訪問的(從鏈表中刪除并不是真的刪除數據,只是移動鏈表指針,數據本身在map中的位置是不變的)。
### 三級緩存原理
1、讀取一張圖片的時候,獲取順序:Lru算法緩存-》弱引用緩存-》磁盤緩存(如果設置了的話)。
當我們的APP中想要加載某張圖片時:
* 先去LruCache中尋找圖片,如果LruCache中有,則直接取出來使用,并將該圖片放入WeakReference中
* 如果LruCache中沒有,則去WeakReference中尋找,如果WeakReference中有,則從WeakReference中取出圖片使用
* 如果WeakReference中也沒有圖片,則從磁盤緩存/網絡中加載圖片
2、將圖片緩存的時候,寫入順序:弱引用緩存-》Lru算法緩存-》磁盤緩存中。
* 當圖片不存在的時候,先從網絡下載圖片,然后將圖片存入弱引用中,glide會采用一個acquired(int)變量用來記錄圖片被引用的次數, 當acquired變量大于0的時候,說明圖片正在使用中,也就是將圖片放到弱引用緩存當中;
* 如果acquired變量等于0了,說明圖片已經不再被使用了,那么此時會調用方法來釋放資源,首先會將緩存圖片從弱引用中移除,然后再將它put到LruResourceCache當中
* 這樣也就實現了正在使用中的圖片使用弱引用來進行緩存,不在使用中的圖片使用LruCache來進行緩存的功能。
# 總結
1、Glide的with方法兩個作用,一是獲得RequestManager對象,二是確定Glide的生命周期。
# 其他
## Bitmap的高效加載
1、圖片加載:
Bitmap在Android中指的是一張圖片,BitmapFactory提供了四類方法:decodeFile、decodeResource、decodeStream和decodeByteArray,分別用于從文件系統、資源、輸入流以及字節數組中加載出一個Bitmap對象。其中decodeFile和decodeResource方法又簡介調用了decodeStream方法,最終調用native方法實現加載。
2、按需加載
高效加載Bitmap的核心思想是采用BitmapFactory.Options來按需加載相應尺寸的圖片。當原圖很大,需要展示圖片的ImageView很小時,通過BitmapFactory.Options按一定采樣率來加載縮小后的圖片到內存,再將其設置給ImageView,這樣可以降低內存占用,避免OOM,提高Bitmap的加載性能。
3、采樣率
采樣率inSampleSize的取值應該總是為2的指數,如不是系統會自動向下取整。
a、將BitmapFactory.Options的inJustDecodeBounds參數設置為true并加載圖片,此時只會解析圖片寬高,不會真正加載到內存
b、取出寬高信息,根據采樣率規則結合目標View的大小計算出采樣率inSampleSize
c、將BitmapFactory.Options的inJustDecodeBounds參數設置為true,重新加載圖片
## 內存泄漏問題
## 大圖加載框架
# 參考文檔
[https://blog.csdn.net/guolin\_blog/article/details/53939176#commentBox](https://blog.csdn.net/guolin_blog/article/details/53939176#commentBox)
[https://juejin.im/post/5dd766e1e51d45233c7e857f](https://juejin.im/post/5dd766e1e51d45233c7e857f)
[https://juejin.im/post/5dbeda27e51d452a161e00c8](https://juejin.im/post/5dbeda27e51d452a161e00c8)
- 導讀
- Java知識
- Java基本程序設計結構
- 【基礎知識】Java基礎
- 【源碼分析】Okio
- 【源碼分析】深入理解i++和++i
- 【專題分析】JVM與GC
- 【面試清單】Java基本程序設計結構
- 對象與類
- 【基礎知識】對象與類
- 【專題分析】Java類加載過程
- 【面試清單】對象與類
- 泛型
- 【基礎知識】泛型
- 【面試清單】泛型
- 集合
- 【基礎知識】集合
- 【源碼分析】SparseArray
- 【面試清單】集合
- 多線程
- 【基礎知識】多線程
- 【源碼分析】ThreadPoolExecutor源碼分析
- 【專題分析】volatile關鍵字
- 【面試清單】多線程
- Java新特性
- 【專題分析】Lambda表達式
- 【專題分析】注解
- 【面試清單】Java新特性
- Effective Java筆記
- Android知識
- Activity
- 【基礎知識】Activity
- 【專題分析】運行時權限
- 【專題分析】使用Intent打開三方應用
- 【源碼分析】Activity的工作過程
- 【面試清單】Activity
- 架構組件
- 【專題分析】MVC、MVP與MVVM
- 【專題分析】數據綁定
- 【面試清單】架構組件
- 界面
- 【專題分析】自定義View
- 【專題分析】ImageView的ScaleType屬性
- 【專題分析】ConstraintLayout 使用
- 【專題分析】搞懂點九圖
- 【專題分析】Adapter
- 【源碼分析】LayoutInflater
- 【源碼分析】ViewStub
- 【源碼分析】View三大流程
- 【源碼分析】觸摸事件分發機制
- 【源碼分析】按鍵事件分發機制
- 【源碼分析】Android窗口機制
- 【面試清單】界面
- 動畫和過渡
- 【基礎知識】動畫和過渡
- 【面試清單】動畫和過渡
- 圖片和圖形
- 【專題分析】圖片加載
- 【面試清單】圖片和圖形
- 后臺任務
- 應用數據和文件
- 基于網絡的內容
- 多線程與多進程
- 【基礎知識】多線程與多進程
- 【源碼分析】Handler
- 【源碼分析】AsyncTask
- 【專題分析】Service
- 【源碼分析】Parcelable
- 【專題分析】Binder
- 【源碼分析】Messenger
- 【面試清單】多線程與多進程
- 應用優化
- 【專題分析】布局優化
- 【專題分析】繪制優化
- 【專題分析】內存優化
- 【專題分析】啟動優化
- 【專題分析】電池優化
- 【專題分析】包大小優化
- 【面試清單】應用優化
- Android新特性
- 【專題分析】狀態欄、ActionBar和導航欄
- 【專題分析】應用圖標、通知欄適配
- 【專題分析】Android新版本重要變更
- 【專題分析】唯一標識符的最佳做法
- 開源庫源碼分析
- 【源碼分析】BaseRecyclerViewAdapterHelper
- 【源碼分析】ButterKnife
- 【源碼分析】Dagger2
- 【源碼分析】EventBus3(一)
- 【源碼分析】EventBus3(二)
- 【源碼分析】Glide
- 【源碼分析】OkHttp
- 【源碼分析】Retrofit
- 其他知識
- Flutter
- 原生開發與跨平臺開發
- 整體歸納
- 狀態及狀態管理
- 零碎知識點
- 添加Flutter到現有應用
- Git知識
- Git命令
- .gitignore文件
- 設計模式
- 創建型模式
- 結構型模式
- 行為型模式
- RxJava
- 基礎
- Linux知識
- 環境變量
- Linux命令
- ADB命令
- 算法
- 常見數據結構及實現
- 數組
- 排序算法
- 鏈表
- 二叉樹
- 棧和隊列
- 算法時間復雜度
- 常見算法思想
- 其他技術
- 正則表達式
- 編碼格式
- HTTP與HTTPS
- 【面試清單】其他知識
- 開發歸納
- Android零碎問題
- 其他零碎問題
- 開發思路