Glide簡介
Glide是Google推薦的一套快速高效的圖片加載框架,作者是bumptech,功能強大且使用方便,實際的android應用開發中,有不少的開發者在使用它,今天,老衲就帶大家來講解下Glide的使用及實現的邏輯流程。
Glide的使用
Glide的使用與前一篇的Picasso類似,都是鏈式調用,極其方便。但是,與其他的圖片加載框架不同的是,Glide支持GIF的加載與解碼。這是該框架的一個亮點,以下為常用API
//設置默認和出錯時的圖片
Glide.with(this).load(url).placeholder(resId).error(resId).into(mImageView)
//普通的圖片加載
Glide.with(this).load(url).into(mImageView);
//可理解為加載動態圖的第一幀的Bitmap,比如Gif
Glide.with(this).load(url).asBitmap().into(imageView);
//GIF加載,URL指向的資源必須是gif,如果是普通圖片則不顯示。
//相反,如果指向正確但沒有執行asGif方法,則只是作為普通圖片展示
Glide.with(this).asGif().load(url).into(mImageView);
//縮略圖的加載
Glide.with(yourFragment).load(yourUrl).thumbnail(0.1f).into(yourView)
Glide的核心思想:
第一條是多數人認可的觀點,其他則是老衲自己在分析源碼時對該框架的一些感悟。如有不對請指出
- 對象池:
Glide原理的核心是為bitmap維護一個對象池。對象池的主要目的是通過減少大對象內存的分配以重用來提高性能。對象池的概念參見對象池的使用 - 生命周期綁定:
圖片的加載任務會與activity或者Fragment的生命周期綁定,當界面執行onStop的使用自動暫定,而當執行onStart的時候又會自動重新開啟,同樣的,動態Gif圖的加載也是如此,以用來節省電量,同時Glide會對網絡狀態做監聽,當網絡狀態發生改變時,會重啟失敗的任務,以減少任務因網絡連接問題而失敗的概率。 - 預覽圖的使用
為加快加載速度,提高體驗,優先加載預覽圖 - AbsListView內圖片的預加載:
Glide的代碼流程分析
按照慣例,首先介紹一下業務邏輯中需要用到的類。有印象即可
RequestManager
Glide用來管理和開始請求的類,實現了LifecycleListener接口并重寫了如下方法,可以使用Activity和Fragment的生命周期事件機制的開啟,停止及重啟請求任務。
/**
* 開始圖片加載請求,一般在Activity或者Fragment的onStart方法內執行,用來重啟失敗或暫停的任務。
*/
@Override
public void onStart() {
resumeRequests();
}
/**
* 暫停圖片加載請求,一般在Activity或Fragment的onStop方法內執行,用來暫停任務。
*/
@Override
public void onStop() {
pauseRequests();
}
/**
* 取消正在執行的請求,以及釋放已完成請求的資源。
*/
@Override
public void onDestroy() {
requestTracker.clearRequests();
}
RequestManagerFragment
沒有視圖的fragment,簡單的來講,就是在每一個Activity或者Fragment上又添加了一個Fragment,該Fragment沒有View,僅僅用來存儲RequestManager并管理Glide請求
RequestManagerRetriever
用來創建并從Activity或者Fragment檢索已存在的RequestManager
Engine
負責開始加載任務,以及管理活躍的,已緩存的資源
BitmapPool(bitmap對象的緩存池)
Bitmap內存池,用來復用對象
LruBitmapPool//基于LruPoolStrategy策略的BitmapPool
BitmapPoolAdapter//該實現類拒絕了對象的復用,get方法總是返回null
LruPoolStrategy
對象池內對象的匹配策略,根據不同的標準,有如下三種匹配策略
//校驗bitmap內存大小和圖片格式,內部的實現基于數組
1. SizeConfigStrategy
//僅要求bitmap尺寸完全匹配,內部的實現基于HashMap
2. AttributeStrategy
//校驗bitmap尺寸和圖片格式,內部實現基于TreeMap
3. SizeStrategy
RequestTracker
用來跟蹤,取消和重啟正在進行中,或者已完成,失敗的請求
ConnectivityMonitor
網絡狀態改變的監聽,本質是一個BroadcastReceiver,值得一提的是,該類也是LifecycleListener的實現類,所以,也會有onStart,onStop的方法,而內部的邏輯,就是對網絡狀態監聽廣播的注冊于反注冊
//注冊網絡狀態改變的監聽廣播
private void register() {
if (isRegistered) {
return;
}
isConnected = isConnected(context);
context.registerReceiver(connectivityReceiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
isRegistered = true;
}
//取消注冊
private void unregister() {
if (!isRegistered) {
return;
}
context.unregisterReceiver(connectivityReceiver);
isRegistered = false;
}
//是否有網絡鏈接
private boolean isConnected(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
@Override
public void onStart() {
register();
}
@Override
public void onStop() {
unregister();
}
Resource
支持復用及池存儲功能的特殊類型的接口,以下是其部分實現類
EngineResource //支持引用計數的Resource
BitmapResource //Bitmap的包裝類
DrawableResource //抽象類,根據ConstantState返回一個依賴于自身ConstantState的drawable的副本
BitmapDrawableResource //BitmapDrawable的包裝類
GifDrawableResource //GifDrawable的包裝類
Target:
LifecycleListener接口的子類,Glide用來加載資源并在加載時通知相關聲明周期事件的接口。ViewTarget是它的抽象實現類。典型的生命周期是onLoadStarted -> onResourceReady or onLoadFailed -> onLoadCleared,然而并不保證一定按照此順序執行,比如:如果資源已經在內存中,則onLoadStarted就不會被調用,同樣的,如果Target如果永遠不被清除,則onLoadCleared永遠不會被調用。
//加載開始時調用
void onLoadStarted(@Nullable Drawable placeholder);
//加載失敗是調用
void onLoadFailed(@Nullable Drawable errorDrawable);
//加載結束時調用
void onResourceReady(R resource, Transition<? super R> transition);
//加載任務取消并且資源被釋放時調用
void onLoadCleared(@Nullable Drawable placeholder);
//取回目標大小,Callback的實現類為SizeDeterminer,在ViewTarget.class中
void getSize(SizeReadyCallback cb);
ViewTarget
加載資源的基類。Target的部分實現類。根據參數的類型,有不同的實現方法,并能通過ViewTreeObserver.OnDrawListener來決定View的大小。
在需要檢測任意涉及到復用View的ViewGroup時(比如listview),該類用setTag方法來存儲一些標志,當檢測到復用時,之前的加載任務和對應的資源文件會被取消或復用。
ImageViewTarget:在ImageView中展示圖片的基類,有如下兩個子類
DrawableImageViewTarget:當參數是drawable的使用使用
//核心方法
@Override
protected void setResource(Bitmap resource) {
view.setImageBitmap(resource);
}
BitmapImageViewTarget:當參數是bitmap 的時候使用
//核心方法
@Override
protected void setResource(Bitmap resource) {
view.setImageBitmap(resource);
}
LifecycleListener
Fragment和Activity生命周期方法的監聽類,主要用來監聽onStart,onStop,onDestroy三個方法。實現類如下RequestTracker
RequestManager:負責監聽Fragment和Activity中對應的方法
@Override
public void onStart() {
resumeRequests(); //重啟暫停或者失敗的任務
targetTracker.onStart();
}
@Override
public void onStop() {
pauseRequests(); //暫停正在執行的任務
targetTracker.onStop();
}
DefaultConnectivityMonitor:負責網絡狀態監聽廣播的注冊于反注冊
@Override
public void onStart() {
register();
}
@Override
public void onStop() {
unregister();
}
BaseTarget:空實現,真正的實現者是其子類ImageViewTarget,用來開始與暫停動畫
@Override
public void onStart() {
if (animatable != null) {
animatable.start();
}
}
@Override
public void onStop() {
if (animatable != null) {
animatable.stop();
}
}
TargetTracker:該類調用的,其實是Target類中對應的方法
@Override
public void onStart() {
for (Target<?> target : Util.getSnapshot(targets)) {
target.onStart();
}
}
@Override
public void onStop() {
for (Target<?> target : Util.getSnapshot(targets)) {
target.onStop();
}
}
RequestFutureTarget:空實現,忽略
NullConnectivityMonitor:空實現,忽略
DataFetcher
數據提取的抽象接口,根據資源的來源有不同的實現,例如
HttpUrlFetcher //加載網絡圖片數據
AssetPathFetcher //加載Asset圖片數據
LocalUriFetcher //加載本地圖片數據
ThumbFetcher //加載MediaStore中的縮略圖數據
DiskCacheStrategy
緩存策略的抽象類,只有Glide提供的固定的幾個對象,分別對應不同的策略
ALL:遠程數據同時緩存Data和Resource,本地數據僅緩存Resource
NONE:不緩存任何數據
DATA:解碼之前直接將數據寫入硬盤
RESOURCE:解碼之后寫入硬盤
AUTOMATIC:默認策略,根據DataFetcher以及ResourceEncoder的編碼策略(EncodeStrategy)智能選擇
Glide圖片加載的業務流程
1. Glide的初始化
public static Glide get(Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
//查找manifest文件中注冊的懶加載的配置信息,下面會介紹到
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
GlideBuilder builder = new GlideBuilder(applicationContext);
for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
//創建Glide實例
glide = builder.createGlide();
for (GlideModule module : modules) {
module.registerComponents(applicationContext, glide.registry);
}
}
}
}
return glide;
}
創建Glide實例對象
Glide createGlide() {
//依賴于優先級的線程池,用來執行Glide的加載,解碼和轉換任務(當從緩存中沒有找到對應的對象時)
//線程數量取決于當前喚醒的CPU核數,而不是CPU的總數
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}
//依賴于優先級的線程池,用來執行Glide的加載,解碼和轉換任務(當從緩存中有對應的對象時)
//線程數量為1
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
//內存大小的計算器,計算結果取決于一些常量和當前設備的信息(寬,高,像素密度),最后會與介紹
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
//網絡活動監視器,用來檢測網絡狀態
if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}
//bitmap對象池,用來存儲bitmap對象
if (bitmapPool == null) {
//3.0以上使用基于LRU算法的bitmap對象池
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
int size = memorySizeCalculator.getBitmapPoolSize();
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
//基于LRU算法的數組緩存池
if (arrayPool == null) {
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
}
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
//硬盤緩存
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
//負責開啟加載任務以及管理活躍的或者緩存的圖片資源
if (engine == null) {
engine = new Engine(memoryCache, diskCacheFactory, diskCacheExecutor, sourceExecutor);
}
return new Glide(context,
engine,
memoryCache,
bitmapPool,
arrayPool,
connectivityMonitorFactory,
logLevel,
defaultRequestOptions.lock());
}
Glide真正的構造方法
Glide(Context context,
Engine engine,
MemoryCache memoryCache,
BitmapPool bitmapPool,
ArrayPool arrayPool,
ConnectivityMonitorFactory connectivityMonitorFactory,
int logLevel,
RequestOptions defaultRequestOptions) {
this.engine = engine;
this.bitmapPool = bitmapPool;
this.arrayPool = arrayPool;
this.memoryCache = memoryCache;
this.connectivityMonitorFactory = connectivityMonitorFactory;
//圖片的加載格式(ARGB_8888或RGB_565),默認ARGB_8888,判斷規則如下
//如果支持透明或者使用了透明則使用ARGB_8888
//如果不支持透明則使用ARGB_565
DecodeFormat decodeFormat = defaultRequestOptions.getOptions().get(Downsampler.DECODE_FORMAT);
//BitmapPool的預填充器,最后面有介紹
bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat);
Resources resources = context.getResources();
//從給定的inputstream中解碼圖片
Downsampler downsampler = new Downsampler(resources.getDisplayMetrics(), bitmapPool, arrayPool);
//Gif圖片資源的解碼器
ByteBufferGifDecoder byteBufferGifDecoder = new ByteBufferGifDecoder(context, bitmapPool, arrayPool);
...
//包含了加載圖片資源所需要的類,比如Registry,Engine,等等
glideContext = new GlideContext(context, registry, imageViewTargetFactory, defaultRequestOptions, engine, this, logLevel);
}
2. 創建請求管理器RequestManager
首先根據context的類型,拿到FragmentManager
public RequestManager get(Context context) {
if (context == null) {
...
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
//FragmentActivity
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
//Activity
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
//如果不屬于以上兩種,則遞歸查找父類Context
return get(((ContextWrapper) context).getBaseContext());
}
}
//返回ApplicationManager
return getApplicationManager(context);
}
...//get()中會調用fragmentGet方法
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context,
android.app.FragmentManager fm, android.app.Fragment parentHint) {
//獲取RequestManagerFragment
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
//獲取RequestManagerFragment對應的RequestManager(請求管理器)
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager = new RequestManager(context, current.getLifecycle(),
current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
RequestManagerRetriever中使用兩個map用來分別存放RequestManagerFragment和SupportRequestManagerFragment,Key都是FragmentManager。說白了就是每一個Activity或者FragmentActivity都有一個唯一的FragmentManager,通過這個FragmentManager作為key就可以找到該Activity對應的RequestManagerFragment,Glide就是通過這個Fragment用來實現在生命周期中圖片加載的控制,比如Paused狀態在暫停加載,在Resumed的時候又自動重新加載
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm
, android.app.Fragment parentHint) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
//pendingRequestManagerFragments是一個
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
2. 填充資源路徑(網絡路徑或本地路徑)
/**
* 利用默認的請求參數創建RequestBuilder
* 默認的參數包括,是否啟動硬盤緩存,優先級,錯誤及加載中的默認圖片等
* 詳情請看BaseRequestOptions.java類
* @return A new request builder for loading a { Drawable} using the given model.
*/
public RequestBuilder<Drawable> load(@Nullable Object model) {
return asDrawable().load(model);
}
3. 設置要加載圖片的Target(ImageView)
public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
Preconditions.checkNotNull(view);
if (!requestOptions.isTransformationSet()
&& requestOptions.isTransformationAllowed()
&& view.getScaleType() != null) {
...
return into(context.buildImageViewTarget(view, transcodeClass));
}
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) { //是否設置了url
...
}
//獲取該Target之前的請求任務(如果有的話)
Request previous = target.getRequest();
//如果有,取消該Target之前所有的任務并釋放資源(例如bitmap)以備復用
if (previous != null) {
requestManager.clear(target);
}
requestOptions.lock();
//創建請求
Request request = buildRequest(target);
target.setRequest(request);
// TODO:下載圖片的任務
requestManager.track(target, request);
return target;
}
Glide的縮略圖加載思想,為了更快的展示圖片,一般來說,一張圖片的加載任務分為全尺寸圖片和縮略圖兩部分,因為縮略圖更小,所以一般來說相對于全尺寸圖片會加載更快,但不絕對,如果縮略圖先加載完則先展示縮略圖,然后等全尺寸圖片加載完成后再加載全尺寸圖片,但是,如果全尺寸圖片先于縮略圖下載完成,則縮略圖則不會展示。
private Request buildRequestRecursive(Target<TranscodeType> target,
@Nullable ThumbnailRequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight) {
//默認的該thumbnailBuilder對象為null,除非手動調用RequestBuilder類的thumbnail方法,
//否則該if代碼永遠不會執行
if (thumbnailBuilder != null) {
// Recursive case: contains a potentially recursive thumbnail request builder.
if (isThumbnailBuilt) {
throw new IllegalStateException("You cannot use a request as both the main request and a thumbnail, consider using clone() on the request(s) passed to thumbnail()");
}
TransitionOptions<?, ? super TranscodeType> thumbTransitionOptions = thumbnailBuilder.transitionOptions;
if (DEFAULT_ANIMATION_OPTIONS.equals(thumbTransitionOptions)) {
thumbTransitionOptions = transitionOptions;
}
//縮略圖權限
Priority thumbPriority = thumbnailBuilder.requestOptions.isPrioritySet() ? thumbnailBuilder.requestOptions.getPriority() : getThumbnailPriority(priority);
//縮略圖寬高
int thumbOverrideWidth = thumbnailBuilder.requestOptions.getOverrideWidth();
int thumbOverrideHeight = thumbnailBuilder.requestOptions.getOverrideHeight();
//寬高校驗
if (Util.isValidDimensions(overrideWidth, overrideHeight) && !thumbnailBuilder.requestOptions.isValidOverride()) {
thumbOverrideWidth = requestOptions.getOverrideWidth();
thumbOverrideHeight = requestOptions.getOverrideHeight();
}
//縮略圖請求協調器,用來同時協調縮略圖和原始圖片的請求
ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
//原始圖片請求
Request fullRequest = obtainRequest(target, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight);
isThumbnailBuilt = true;
// Recursively generate thumbnail requests
//遞歸生成縮略圖請求
Request thumbRequest = thumbnailBuilder.buildRequestRecursive(target, coordinator, thumbTransitionOptions, thumbPriority, thumbOverrideWidth, thumbOverrideHeight);
isThumbnailBuilt = false;
coordinator.setRequests(fullRequest, thumbRequest);
return coordinator;
} else if (thumbSizeMultiplier != null) { //根據指定縮放系數加載縮略圖
ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);
Request fullRequest = obtainRequest(target, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight);
BaseRequestOptions<?> thumbnailOptions = requestOptions.clone().sizeMultiplier(thumbSizeMultiplier);
Request thumbnailRequest = obtainRequest(target, thumbnailOptions, coordinator, transitionOptions, getThumbnailPriority(priority), overrideWidth, overrideHeight); coordinator.setRequests(fullRequest, thumbnailRequest);
return coordinator;
} else {
// 只加載原始圖片
return obtainRequest(target, requestOptions, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight);
}
}
至此,Glide的流程就分析完了,但是!!!和我們之前了解到的Picasso或者Imageloader等圖片加載框架不同,我們完全沒有看到他的網絡請求已經緩存查找的業務邏輯,那這段邏輯究竟在哪呢。我們繼續往下看
圖片的加載
在上面的最后一步,buildRequestRecursive方法,不管是否有縮略圖,我們都會返回一個Request,這個Request會和Target一起被RequestManager接收
void track(Target<?> target, Request request) {
targetTracker.track(target);
//runRequest最終執行的是Request的begin方法,下面以SingleRequest為例講解下流程
requestTracker.runRequest(request);
}
@Override
public void begin() {
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
//如果圖片的來源沒有設置,則加載失敗,來源是指網絡,本地硬盤或者資源文件等。
if (model == null) {
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
width = overrideWidth;
height = overrideHeight;
}
...
onLoadFailed(new GlideException("Received null model"), logLevel);
return;
}
status = Status.WAITING_FOR_SIZE;
//如果Target的寬高已經獲取并且合法,則開始進行下一步
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else { //手動獲取Target的寬高
target.getSize(this);
}
if ((status == Status.RUNNING
|| status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
...
}
}
@Override
public void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled();
...
status = Status.RUNNING;
//計算縮略圖的尺寸
float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = Math.round(sizeMultiplier * width);
this.height = Math.round(sizeMultiplier * height);
...
//加載任務
loadStatus = engine.load(glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
this);
...
}
加載資源
Glide圖片的資源加載與其他圖片加載框架的加載邏輯類似,都是按照內存,硬盤及網絡的順序來加載圖片,但是Glide得加載又稍顯不同,他是的邏輯如下:
/**
* 1. 檢查內存緩存
* 2. 檢查最近的活躍資源
* 3. 檢查最近的加載任務
* 活躍資源指的是那些不止一次被加載并沒有進行過資源釋放的圖片,一旦被釋放,
* 那么該資源則會從近期活躍資源中刪除并進入到內存緩存中,
* 但是如果該資源再次從內存緩存中讀取,則會重新添加到活躍資源中
*/
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
Options options,
boolean isMemoryCacheable,
ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
//內存緩存的唯一鍵值
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
//首先從緩存中查找
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
...
return null;
}
//如果緩存中沒有找到,則去活躍資源中加載
//memCache中該bitmap則會被remove掉bitmap并進入activeResource中
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
...
}
return null;
}
//如果該任務之前已經在隊列中,則添加新的callback,然后返回
EngineJob current = jobs.get(key);
if (current != null) {
current.addCallback(cb);
...
return new LoadStatus(cb, current);
}
//如果是新的加載任務,先創建EngineJob和DecodeJob,然后開始任務
EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable);
DecodeJob<R> decodeJob = decodeJobFactory.build(glideContext,model,key,signature,width,height,resourceClass,transcodeClass, priority,diskCacheStrategy,transformations, isTransformationRequired,options,engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb);
//開始任務
engineJob.start(decodeJob);
...
return new LoadStatus(cb, engineJob);
}
/**
* DecodeJob 類中run方法的實現,DecodeJob是一個Runnable的實現類
* 該方法的作用是,
* 1.確定數據的加載來源(Resource,Data,Source)
* 2.創建對應來源的DataFetcherGenerator
* 3.執行DataFetcherGenerator
*/
private void runWrapped() {
switch (runReason) {
case INITIALIZE://首次提交
stage = getNextStage(Stage.INITIALIZE); //確定資源的加載來源
currentGenerator = getNextGenerator();
runGenerators();
break;
case SWITCH_TO_SOURCE_SERVICE://從硬盤獲取資源失敗 ,嘗試重新獲取
runGenerators();
break;
case DECODE_DATA://獲取數據成功,但不在同一線程
decodeFromRetrievedData();
break;
default:
throw new IllegalStateException("Unrecognized run reason: " + runReason);
}
}
/**
* 根據當前階段獲取下一階段
* Data和Resource的區別:
* Data:原始的圖片(或gif)數據
* Resource:經過處理(旋轉,縮放)后的數據
* 該方法的大致邏輯如下
* 1.如果是初始狀態,則判斷是否解碼已緩存的Resource,true是解碼Resource。
* false的話則會通過遞歸進入第二個判斷分支
* 2.判斷是否解碼已緩存的Data,true是解碼Data
* false的話則會通過遞歸進入第三個判斷分支
* 3.該階段則需要從數據源去解碼。
* 簡單的來說,就是根據Resource--->Data--->source的順序去解碼加載數據
* 該階段Stage的確定,影響著下一階段DataFetcherGenerator相應子類的實例創建
*/
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
return Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
/**
* 根據不同的階段創建不同的DataFetcherGenerator,該類使用已注冊的ModelLoaders和Model
* 來生成一系列的DataFetcher。有如下實現類
* DataFetcherGenerator:經過處理的資源數據緩存文件(采樣轉換等處理)
* ResourceCacheGenerator:未經處理的資源數據緩存文件
* SourceGenerator:源數據的生成器,包含了根據來源創建的ModelLoader和Model(文件路徑,URL...)
*/
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
/**
* 根據不同的狀態來選擇并執行生成器
* 從當前Generator 獲取數據,如果獲取成功則直接回調onDataFetcherReady,
* 如果失敗則通過reschedule重新調度
*/
private void runGenerators() {
...
boolean isStarted = false;
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
// 加載失敗
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}
如果是首次加載一張圖片資源,最終會來到SourceGenerator的startNext來執行。
/**
* SourceGenerator
* DataFetcher的簡介:Fetcher的意思是抓取,所以該類可以稱為數據抓取器
* 作用就是根據不同的數據來源(本地,網絡,Asset等)
* 以及讀取方式(Stream,ByteBuffer等)來提取并解碼數據資源,實現類如下
* AssetPathFetcher:加載Asset數據
* HttpUrlFetcher:加載網絡數據
* LocalUriFetcher:加載本地數據
* 其他實現類...
*
*/
@Override
public boolean startNext() {
...
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
sourceCacheGenerator = null;
loadData = null;
boolean started = false;
//是否有更多的ModelLoader
while (!started && hasNextModelLoader()) {
loadData = helper.getLoadData().get(loadDataListIndex++);
if (loadData != null&& (helper.getDiskCacheStrategy()
.isDataCacheable(loadData.fetcher.getDataSource())
|| helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
started = true;
//選擇合適的LoadData,并使用LoadData中的fetcher來抓取數據
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
當走完上面的流程,接下來就是我們最熟悉的網絡數據請求的模塊了,因為該模塊在整個的圖片加載流程中并不是很重要,所以就簡單介紹一下,不過有幾個知識點還是比較有意思的。
/**
* HttpUrlFetcher
* HttpUrlFetcher的簡介:網絡數據抓取器,通俗的來講就是去服務器上下載圖片,支持地址重定向(最多5次)
*
*/
@Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
final InputStream result;
try {
result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
} catch (IOException e) {
...
callback.onLoadFailed(e);
return;
}
...
callback.onDataReady(result);
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map<String, String> headers) throws IOException {
//重定向次數過多
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
//通過URL的equals方法來比較會導致NetworkI/O開銷,一般會有問題,
//有興趣的同學可以看下下面的鏈接或者直接閱讀URL里equals方法的源碼注釋,一目了然
//http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
//HttpUrlConnection下載圖片
...
}
Glide的知識點
接下來,老衲帶大家看下Glide中有什么我們在日常開發的時候能用得上的技術
1.線程池內線程的個數的計算方式
/**
* 根據/sys/devices/system/cpu/下的文件來決定線程池內線程的數量
* 決定線程數量的不是一共得CPU核數,而是喚醒的CPU核數
*
* See http://goo.gl/8H670N.
*/
public static int calculateBestThreadCount() {
File[] cpus = null;
try {
File cpuInfo = new File(CPU_LOCATION);
final Pattern cpuNamePattern = Pattern.compile(CPU_NAME_REGEX);
cpus = cpuInfo.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String s) {
return cpuNamePattern.matcher(s).matches();
}
});
} catch (Throwable t) {
...
}
int cpuCount = cpus != null ? cpus.length : 0;
int availableProcessors = Math.max(1, Runtime.getRuntime().availableProcessors());
return Math.min(MAXIMUM_AUTOMATIC_THREAD_COUNT, Math.max(availableProcessors, cpuCount));
}
2. 權限判斷
以網絡請求的權限為例
final int res = context
.checkCallingOrSelfPermission("android.permission.ACCESS_NETWORK_STATE");
final boolean hasPermission = res == PackageManager.PERMISSION_GRANTED;
Glide的懶加載配置
解析Manifest文件中注冊的懶加載配置信息
public List<GlideModule> parse() {
List<GlideModule> modules = new ArrayList<>();
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
if (appInfo.metaData != null) {
for (String key : appInfo.metaData.keySet()) {
if (GLIDE_MODULE_VALUE.equals(appInfo.metaData.get(key))) {
modules.add(parseModule(key));
}
}
}
} catch (PackageManager.NameNotFoundException e) {
...
}
return modules;
}
3. Glide給出的懶加載示例(混淆代碼的話請讀者自己查看GlideModule類的注釋)
public class FlickrGlideModule implements GlideModule {
Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888);
}
public void registerComponents(Context context, Glide glide) {
glide.register(Model.class, Data.class, new MyModelLoader());
}
}
4. Glide的預填充機制
Glide最核心的部分,就是Bitmap池的使用,
Glide類中有一個preFillBitmapPool方法,用來預填充bitmap對象池,他的注釋如下
/**
* 作用:根據給定的尺寸預填充Bitmap對象池
* 缺陷:太多的資源釋放會導致GC頻繁執行,這樣就失去了Glide本身存在的意義。此方法要慎重使用。
* 若太多的Bitmap被添加到對象池使其完全被填滿,會導致大多數甚至全部最近被添加的bitmap被驅逐(釋放)
* bitmap會根據給定的尺寸的權重來分配,每一種尺寸只會填充對象池所占內存的一定比例。
* 比例的計算公式為weight / prefillWeightSum
*/
public void preFillBitmapPool(PreFillType.Builder... bitmapAttributeBuilders) {
bitmapPreFiller.preFill(bitmapAttributeBuilders);
}
public void preFill(PreFillType.Builder... bitmapAttributeBuilders) {
if (current != null) {
current.cancel();
}
//PreFillType中保存著圖片的寬高,Config以及weight等信息
PreFillType[] bitmapAttributes = new PreFillType[bitmapAttributeBuilders.length];
for (int i = 0; i < bitmapAttributeBuilders.length; i++) {
PreFillType.Builder builder = bitmapAttributeBuilders[i];
if (builder.getConfig() == null) {
builder.setConfig(defaultFormat == DecodeFormat.ALWAYS_ARGB_8888
|| defaultFormat == DecodeFormat.PREFER_ARGB_8888? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
}
bitmapAttributes[i] = builder.build();
}
//根據PreFillType中保存的圖片信息創建預填充隊列
PreFillQueue allocationOrder = generateAllocationOrder(bitmapAttributes);
//創建用來填充對象池的線程對象,
//該類通過Handler發布到主線程中盡量避免GC因高比例的Bitmap觸發垃圾回收所導致的ANR,
//通過延時減少GC線程的垃圾回收的次數
current = new BitmapPreFillRunner(bitmapPool, memoryCache, allocationOrder);
handler.post(current);
}
// Visible for testing.
PreFillQueue generateAllocationOrder(PreFillType[] preFillSizes) {
//剩余內存
final int maxSize = memoryCache.getMaxSize() - memoryCache.getCurrentSize() + bitmapPool.getMaxSize();
int totalWeight = 0;
//計算圖片總權重
for (PreFillType size : preFillSizes) {
totalWeight += size.getWeight();
}
//計算每一份權重占用的字節
final float bytesPerWeight = maxSize / (float) totalWeight;
Map<PreFillType, Integer> attributeToCount = new HashMap<PreFillType, Integer>();
for (PreFillType size : preFillSizes) {
//根據權重計算出的圖片的內存占用量
int bytesForSize = Math.round(bytesPerWeight * size.getWeight());
//根據寬高和Config計算出的圖片的內存占用量
int bytesPerBitmap = getSizeInBytes(size);
int bitmapsForSize = bytesForSize / bytesPerBitmap;
attributeToCount.put(size, bitmapsForSize);
}
return new PreFillQueue(attributeToCount);
}
5. DefaultConnectivityMonitor 網絡狀態監視器
平常的開發中使用頻率還是比較高的,可以直接拿來用,完整代碼請參考源碼
private final BroadcastReceiver connectivityReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
boolean wasConnected = isConnected;
isConnected = isConnected(context);
if (wasConnected != isConnected) {
listener.onConnectivityChanged(isConnected);
}
}
};
public DefaultConnectivityMonitor(Context context, ConnectivityListener listener) {
this.context = context.getApplicationContext();
this.listener = listener;
}
private void register() {
if (isRegistered) {
return;
}
isConnected = isConnected(context);
context.registerReceiver(connectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
isRegistered = true;}private void unregister() {
if (!isRegistered) {
return;
}
context.unregisterReceiver(connectivityReceiver);
isRegistered = false;
}
private boolean isConnected(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
@Override
public void onStart() {
register();
}
@Override
public void onStop() {
unregister();
}