Glide的介紹與使用
Glide是一個非常強大、優秀的圖片加載框架,不但使用簡單,而且加入了Activity和Fragment生命週期的管理。
Glide支持拉取,解碼和展示視頻快照,圖片和GIF動畫。Glide的Api非常靈活,開發者甚至可以插入和替換成自己喜愛的任何網絡棧。默認情況下,Glide使用的是一個定製化的基於HttpUrlConnection的棧,但同時也提供了與Google Volley和Square OkHttp快速集成的工具庫。
Glide的GitHub官網是https://github.com/bumptech/glide
Glide的使用如下:
1、在項目model的build.gradle(如app/build.gradle)文件當中添加依賴
dependencies {
implementation 'com.github.bumptech.glide:glide:3.7.0'
}
如果需要網絡功能,那麼在AndroidMainfest.xml中聲明一下網絡權限:
<uses-permission android:name="android.permission.INTERNET" />
2、直接調用接口
String url = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg";
ImageView mImageView = (ImageView) findViewById(R.id.image);
Glide.with(this)
.load(url)
.into(mImageView);
這是Glide最基本的使用方式:通過三步:with、load、into。
其中with
的參數可以是Activity、Fragment或者Context。如果傳入是Activity或者Fragment實例,那麼圖片的加載可以跟着Activity或Fragment的生命週期執行,當這個Activity或Fragment銷燬時,圖片加載也會停止。如果傳入ApplicationContext實例,那麼只有當應用程序被殺掉的時候,圖片加載纔會停止。
load
的參數除了上述字符串url,還有其他圖片資源的類型,如本地圖片、應用資源、二進制流、Uri對象等。
// 加載本地圖片
File file = new File(getExternalCacheDir() + "/image.jpg");
Glide.with(this).load(file).into(imageView);
// 加載應用資源
int resource = R.drawable.image;
Glide.with(this).load(resource).into(imageView);
// 加載二進制流
byte[] image = getImageBytes();
Glide.with(this).load(image).into(imageView);
// 加載Uri對象
Uri imageUri = getImageUri();
Glide.with(this).load(imageUri).into(imageView);
into
的參數是ImageView類型,即讓圖片顯示在哪個ImageView控件上。當然,這個參數還支持Target類型,例子如下
Glide.with(this)
.load(url)
.into(new SimpleTarget<GlideDrawable>() {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
//resource即Glide加載出來的圖片對象,可以進行處理成圓形、圓角等的drawable
mImageView.setImageDrawable(resource);
}
});
3、其他API接口用法
上述第2點是最基本用法,還可以使用Glide的擴展內容,一般就是基本用法中插入其他的API方法。例子如下:
int resource = R.mipmap.ic_launcher;
int resourceError = R.mipmap.ic_launcher_round;
Glide.with(this)
.load(url)
.asBitmap()
.placeholder(resource)
.error(resourceError)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.override(200, 200)
.into(mImageView);
asBitmap
指定圖片格式爲Bitmap,對應有GIF格式的設置asGif
。
placeholder
佔位圖,指在圖片的加載過程中先顯示一張臨時的圖片,等圖片加載出來了再替換成要加載的圖片。
error
異常佔位圖,當發生異常時使用的佔位圖,如url地址不存在,沒有網絡等異常。
diskCacheStrategy
設置緩存功能。DiskCacheStrategy.NONE表示禁用Glide的緩存功能。
override
指定圖片大小,單位爲px。
以上是一些擴展的API方法。
Glide的源碼簡析
Glide的源代碼比較複雜,詳細的分析可以參考郭霖的專欄Glide最全解析。越複雜的代碼,越要抓住主線去分析,可以先忽略細節的東西。Glide源碼特別難懂的原因之一,是它用到的很多對象很早之前就初始化,在初始化的時候你可能完全就沒有留意過它,因爲一時半會根本就用不着,但是真正需要用到的時候你卻找不到了。這時候可以先記錄對象代表的什麼,走完整個主線流程,具體細節,如初始化、特殊情況等,可以先忽略,後續有時間再回頭分析。
本節主要帶着以下幾點問題分析源碼:
1、Glide下載過程如何綁定Activity或者Fragment的生命週期?
2、Glide的網絡請求過程是什麼?
3、Glide獲取的資源怎麼顯示出來?
1、Glide下載過程如何綁定Activity或者Fragment的生命週期
入口方法是with()
//Glide.java
public static RequestManager with(FragmentActivity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
//RequestManagerRetriever.java
public RequestManager get(FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm);
}
}
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;
}
//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;
}
public class RequestManager implements LifecycleListener {
//...
RequestManager(Context context, final Lifecycle lifecycle, RequestManagerTreeNode treeNode,
RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
//...
if (Util.isOnBackgroundThread()) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
lifecycle.addListener(RequestManager.this);
}
});
} else {
lifecycle.addListener(this);
}
}
//...
@Override
public void onStart() {
resumeRequests();
}
@Override
public void onStop() {
pauseRequests();
}
@Override
public void onDestroy() {
requestTracker.clearRequests();
}
//...
}
可以看出,無論with的參數是Activity還是Fragment,Glide都會調用supportFragmentGet。Glide並沒有辦法知道Activity的生命週期,於是就使用了添加隱藏Fragment的這種小技巧,因爲Fragment的生命週期和Activity是同步的,如果Activity被銷燬了,Fragment是可以監聽到的,這樣Glide就可以捕獲這個事件並停止圖片加載了。
2、Glide的網絡請求過程是什麼?
入口方法是.load(url).into(mImageView)
(1)先看load
方法:
//RequestManager.java
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string);
}
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
//DrawableRequestBuilder.java (DrawableTypeRequest的父類)
public DrawableRequestBuilder<ModelType> load(ModelType model) {
super.load(model);
return this;
}
load
方法主要做一些初始化、封裝API的工作。在DrawableRequestBuilder
類中,除了load
方法,還有placeholder
、error
等接口方法。
在Glide類中,有以下代碼
register(String.class, ParcelFileDescriptor.class, new FileDescriptorStringLoader.Factory());
register(String.class, InputStream.class, new StreamStringLoader.Factory());
register(Uri.class, ParcelFileDescriptor.class, new FileDescriptorUriLoader.Factory());
register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
由此可知,fromString
執行loadGeneric(String.class)
後,從buildStreamModelLoader
獲取的數據流ModelLoader是StreamStringLoader
。如果url字符串是帶有http/https,那麼StreamStringLoader調用getResourceFetcher()方法會得到一個HttpUrlFetcher
對象(執行網絡請求的對象)。
(備註:StreamStringLoader > StreamUriLoader > HttpUrlGlideUrlLoader 一層一層傳遞,因此最後是調用HttpUrlGlideUrlLoader.getResourceFetcher,即返回HttpUrlFetcher)
(2)接着看into
方法
//GenericRequestBuilder.java (上面DrawableRequestBuilder的父類)
public Target<TranscodeType> into(ImageView view) {
//...
return into(glide.buildImageViewTarget(view, transcodeClass));
}
public <Y extends Target<TranscodeType>> Y into(Y target) {
//Request是用來發出加載圖片請求的,是Glide中非常關鍵的一個組件
Request request = buildRequest(target);
//...
requestTracker.runRequest(request);
return target;
}
上面是加載圖片請求的入口,先初始化buildRequest,再執行runRequest,我們分開看這兩步源代碼,即(3)(4)。
(3)GenericRequestBuilder的buildRequest
private Request buildRequest(Target<TranscodeType> target) {
if (priority == null) {
priority = Priority.NORMAL;
}
return buildRequestRecursive(target, null);
}
private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {
if (thumbnailRequestBuilder != null) {
//...
} else if (thumbSizeMultiplier != null) {
//...
} else {
// Base case: no thumbnail.
return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
}
}
private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,
RequestCoordinator requestCoordinator) {
return GenericRequest.obtain(...);
}
buildRequest最後裏調用了GenericRequest的obtain()方法,返回一個Request對象。注意這個obtain()方法需要傳入非常多的參數,如placeholderId、errorPlaceholder等等。我們可以猜測,剛纔load()方法所在類DrawableRequestBuilder中封裝的API參數,如placeholder
、error
,其實都是在這裏組裝到Request對象當中的。
(4)requestTracker.runRequest(request)
//RequestTracker.java
public void runRequest(Request request) {
//...
if (!isPaused) {
request.begin();
} else {
//...
}
}
//GenericRequest.java
public void begin() {
//...
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);//執行onSizeReady方法
} else {
target.getSize(this);//最後也會回調onSizeReady方法
}
//...
}
@Override
public void onSizeReady(int width, int height) {
//...
ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
//...
ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);//最後UI的顯示會回調this的onResourceReady接口方法
//...
}
以上方法中大部分參數是第(1)步DrawableTypeRequest
類的構造函數中傳入的,例如:
loadProvider 對應 DrawableTypeRequest構造函數中buildProvider返回的FixedLoadProvider。
modelLoader 對應 ImageVideoModelLoader。
dataFetcher 對應 new ImageVideoFetcher(streamFetcher, fileDescriptorFetcher),用於數據流的加載。
transcoder 對應 GifBitmapWrapperDrawableTranscoder,用於數據的轉碼。
接下來繼續看engine.load
方法
(5)Engine的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 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);
EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
engineJob.addCallback(cb);
engineJob.start(runnable);
return new LoadStatus(cb, engineJob);
}
class EngineRunnable implements Runnable, Prioritized {
//...
@Override
public void run() {
//...
resource = decode();
//...
if (resource == null) {
onLoadFailed(exception);
} else {
onLoadComplete(resource);
}
}
//...
}
load
主要流程是engineJob.start(runnable)
,然後執行EngineRunnable
裏的run方法。
在run方法中,先執行decode
方法,返回結果是Resource<?>
對象,然後通過onLoadComplete(resource)
顯示在ImageView上。
因此,這裏的關鍵代碼是decode
,下面繼續分析。
(6)EngineRunnable的decode
方法
//EngineRunnable.java
private Resource<?> decode() throws Exception {
//...
return decodeFromSource();
}
private Resource<?> decodeFromSource() throws Exception {
return decodeJob.decodeFromSource();
}
//DecodeJob.java
public Resource<Z> decodeFromSource() throws Exception {
Resource<T> decoded = decodeSource();
return transformEncodeAndTranscode(decoded);
}
EngineRunnable.decode
方法最終會調用DecodeJob.decodeSource
方法獲取Resource<T>
對象,然後執行transformEncodeAndTranscode(decoded)
進行編碼轉換,最後返回結果也是Resource<Z>
對象,返回上一步(5)的onLoadComplete(resource)
顯示使用。
因此,這裏的關鍵代碼是DecodeJob.decodeSource
,下面繼續分析。
(7)DecodeJob的decodeSource
方法
//DecodeJob.java
private Resource<T> decodeSource() throws Exception {
Resource<T> decoded = null;
try {
//...
final A data = fetcher.loadData(priority);
//...
decoded = decodeFromSourceData(data);
} finally {
fetcher.cleanup();
}
return decoded;
}
這裏fetcher是第(4)步的ImageVideoFetcher
,這裏源碼先執行fetcher.loadData
加載數據,
然後再執行decodeFromSourceData
解析數據,這兩步都很重要,下面(8)(9)分別分析。最後返回結果是Resource<T>
對象,返回給上一步(6)的transformEncodeAndTranscode(decoded)
編碼轉換使用。
(8)ImageVideoFetcher的loadData
方法 (網絡請求方法)
//ImageVideoFetcher.java
public ImageVideoWrapper loadData(Priority priority) throws Exception {
InputStream is = null;
if (streamFetcher != null) {
//...
is = streamFetcher.loadData(priority);
}
//...
return new ImageVideoWrapper(is, fileDescriptor);
}
這裏的streamFetcher
就是(1)中提到的HttpUrlFetcher
,從這個類的實現可以看出,這個是網絡請求的類,採用HttpURLConnection
請求API。最後返回的是數據流InputStream
,提供下面使用。
(9)DecodeJob的decodeFromSourceData
方法
//DecodeJob.java
private Resource<T> decodeFromSourceData(A data) throws IOException {
final Resource<T> decoded;
if (diskCacheStrategy.cacheSource()) {
// ...
} else {
//getSourceDecoder對應的是GifBitmapWrapperResourceDecoder類
decoded = loadProvider.getSourceDecoder().decode(data, width, height);
}
return decoded;
}
//GifBitmapWrapperResourceDecoder.java
public Resource<GifBitmapWrapper> decode(ImageVideoWrapper source, int width, int height) throws IOException {
//...
GifBitmapWrapper wrapper = null;
//...
wrapper = decode(source, width, height, tempBytes);
return wrapper != null ? new GifBitmapWrapperResource(wrapper) : null;
}
private GifBitmapWrapper decode(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException {
final GifBitmapWrapper result;
if (source.getStream() != null) {
result = decodeStream(source, width, height, bytes);
} else {
result = decodeBitmapWrapper(source, width, height);
}
return result;
}
private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes)
throws IOException {
//獲取第(8)步的InputStream
InputStream bis = streamFactory.build(source.getStream(), bytes);
bis.mark(MARK_LIMIT_BYTES);
ImageHeaderParser.ImageType type = parser.parse(bis);
bis.reset();
GifBitmapWrapper result = null;
//...
if (result == null) {
//...
ImageVideoWrapper forBitmapDecoder = new ImageVideoWrapper(bis, source.getFileDescriptor());
result = decodeBitmapWrapper(forBitmapDecoder, width, height);
}
return result;
}
private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
GifBitmapWrapper result = null;
// ImageVideoBitmapDecoder,開始解析InputStream數據
Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height);
if (bitmapResource != null) {
result = new GifBitmapWrapper(bitmapResource, null);
}
return result;
}
//ImageVideoBitmapDecoder.java
public Resource<Bitmap> decode(InputStream source, int width, int height) {
Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);
return BitmapResource.obtain(bitmap, bitmapPool);
}
//Downsampler.java
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);
Bitmap rotated = null;
if (downsampled != null) {
rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation);
if (!downsampled.equals(rotated) && !pool.put(downsampled)) {
downsampled.recycle();
}
}
return rotated;
}
private Bitmap downsampleWithSize(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream,
BitmapFactory.Options options, BitmapPool pool, int inWidth, int inHeight, int sampleSize,
DecodeFormat decodeFormat) {
//...
return decodeStream(is, bufferedStream, options);
}
private static Bitmap decodeStream(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream,
BitmapFactory.Options options) {
//...
final Bitmap result = BitmapFactory.decodeStream(is, null, options);
return result;
}
從最後跟蹤的代碼可以看出,最後調用BitmapFactory.decodeStream
解析上一步(8)的InputStream,返回Bitmap,再封裝成Resource<Bitmap>、 GifBitmapWrapper、Resource<GifBitmapWrapper>,即第(7)步的返回結果。
這就是Glide的網絡請求流程。
3、Glide獲取的資源怎麼顯示出來?
在上述第(5)步提到了顯示UI的回調函數onLoadComplete(resource)
.
class EngineRunnable implements Runnable, Prioritized {
//...
@Override
public void run() {
//...
resource = decode();
//...
if (resource == null) {
onLoadFailed(exception);
} else {
onLoadComplete(resource);
}
}
//...
private void onLoadComplete(Resource resource) {
//manager即EngineJob對象
manager.onResourceReady(resource);
}
}
//EngineJob.java
public void onResourceReady(final Resource<?> resource) {
this.resource = resource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
private void handleResultOnMainThread() {
//...
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource);
}
}
}
因此,最後顯示UI會切換到主線程,然後通過回調函數顯示,這裏的ResourceCallback回調是在GenericRequest類中,執行engine.load(..., this)
通過this傳入的,源碼如下。
public void onSizeReady(int width, int height) {
//...
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
priority, isMemoryCacheable, diskCacheStrategy, this);
//...
}
@Override
public void onResourceReady(Resource<?> resource) {
//...
onResourceReady(resource, (R) received);
}
private void onResourceReady(Resource<?> resource, R result) {
//...
if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
isFirstResource)) {
GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
target.onResourceReady(result, animation);
}
//...
}
這裏的target就是into方法傳入的參數,glide.buildImageViewTarget最後得到的是GlideDrawableImageViewTarget對象,源碼如下。
//GenericRequestBuilder.java
public Target<TranscodeType> into(ImageView view) {
//...
return into(glide.buildImageViewTarget(view, transcodeClass));
}
在GlideDrawableImageViewTarget類中,onResourceReady接口實現如下:
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
//...
super.onResourceReady(resource, animation);
}
//父類
@Override
public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
if (glideAnimation == null || !glideAnimation.animate(resource, this)) {
setResource(resource);
}
}
@Override
protected void setResource(GlideDrawable resource) {
view.setImageDrawable(resource);
}
Glide獲取的資源就這樣顯示ImageView控件上。
擴展小點:
1、 Glide緩存的實現方式是什麼?
詳細的分析可以參考郭霖的Android圖片加載框架最全解析(三),深入探究Glide的緩存機制,文章講解原理後,介紹了一個Glide緩存的高級用法。
Glide使用緩存的方式有兩種:內存緩存和硬盤緩存。
內存緩存的實現是常見的LruCache算法,加上弱引用的機制,共同完成的。具體是,正在使用中的圖片使用弱引用來進行緩存,不再使用中的圖片使用LruCache來進行緩存。
LruCache算法(Least Recently Used),即近期最少使用算法。它的主要算法原理就是把最近使用的對象用強引用存儲在LinkedHashMap中,並且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除。
硬盤緩存的實現也是使用的LruCache算法,具體使用是Google的工具類DiskLruCache。
2、與Picasso的對比(參考這篇文章)
Glide和Picasso有90%的相似度,準確的說,就是Picasso的克隆版本。但是在細節上還是有不少區別的。
(1)Pisson默認加載Bitmap格式是RGB_8888,而Glide是RGB_565。
結果是Glide的內存開銷
比Picasso小。
原因在於Picasso是加載了全尺寸的圖片到內存,然後讓GPU來實時重繪大小。而Glide加載的大小和ImageView的大小是一致的,因此更小。
(2)磁盤緩存策略不同。Picasso緩存的是全尺寸的,而Glide緩存的是跟ImageView尺寸相同的。
結果是Glide加載顯示比Picasso快。
(3)Glide可以加載GIF動態圖,而Picasso不能。