在Android
中圖片載入的框架很多,例如:Fresco
、Picasso
、Glide
與Imageloader
。它們都有各自的優點,但總的來說,使用起來方便簡單、可配置性高與提供良好的快取機制。由於平常主要用的還是Fresco
,所以這裡有必要對Fresco
的原理進行深入研究。這樣對於以後的使用與理解將會得到巨大的幫助。
Fresco
是專注於對圖片載入而設計的框架,所以對於以圖片為主的App
強烈推薦使用。Fresco
對於圖片的展示支援多種情況:backgroud image
(背景圖)、placeholder image
(佔點陣圖)、actual image
(載入的圖片)、progress bar image
(進度條)、retry image
(重新載入的圖片)、failure image
(失敗圖片)與overlay image
(疊加圖)。Fresco
既然支援這麼多圖片展示情況,那麼它對這次圖層的管理模式又是怎麼樣的呢?Fresco
對於這些圖層的管理都交給了Hierarchy
,而這些圖層的資料都通過Controller
來設定。先不分析這些,這些後續文章會詳細分析,今天先從Fresco
的基本元件開始。
SimpleDraweeView
首先這是對
Fresco
的原始碼分析,所以在看這篇文章之前你應該要有使用Fresco
的基礎,如果沒有的強烈推薦看下Fresco官方文件。
我們使用Fresco
進行圖片載入,使用最多的還是已經封裝好的SimpleDraweeView
,而在SimpleDraweeView
的構造方法中會呼叫init()
方法,它的原始碼如下:
private void init(Context context, @Nullable AttributeSet attrs) {
if (isInEditMode()) {
return;
}
Preconditions.checkNotNull(
sDraweeControllerBuilderSupplier,
"SimpleDraweeView was not initialized!");
mSimpleDraweeControllerBuilder = sDraweeControllerBuilderSupplier.get();
if (attrs != null) {
TypedArray gdhAttrs = context.obtainStyledAttributes(
attrs,
R.styleable.SimpleDraweeView);
try {
if (gdhAttrs.hasValue(R.styleable.SimpleDraweeView_actualImageUri)) {
setImageURI(
Uri.parse(gdhAttrs.getString(R.styleable.SimpleDraweeView_actualImageUri)),
null);
} else if (gdhAttrs.hasValue((R.styleable.SimpleDraweeView_actualImageResource))) {
int resId = gdhAttrs.getResourceId(
R.styleable.SimpleDraweeView_actualImageResource,
NO_ID);
if (resId != NO_ID) {
setActualImageResource(resId);
}
}
} finally {
gdhAttrs.recycle();
}
}
}
複製程式碼
這個方法做的事情很簡單,但我們要注意的是它會對sDraweeControllerBuilderSupplier
進行null
判斷,如果為null
將會丟擲異常。sDraweeControllerBuilderSupplier
是供應類,通過它的get
方法來獲取DraweeControllerBuilder
,這個是controller
構造器,這個以後的章節會詳細說明。空判斷的目的就是在使用SimpleDraweeView
之前必須初始化sDraweeControllerBuilderSupplier
。在SimpleDraweeView
中我們能找到它的初始化方法
public static void initialize(
Supplier<? extends SimpleDraweeControllerBuilder> draweeControllerBuilderSupplier) {
sDraweeControllerBuilderSupplier = draweeControllerBuilderSupplier;
}
複製程式碼
它是一個static
方法,在SimpleDraweeView
初始化之前載入一次即可。這是SimpleDraweeView
的關鍵。它還有一個關鍵方法
public void setImageURI(Uri uri, @Nullable Object callerContext) {
//通過controller 來儲存Uri 等相關資訊
DraweeController controller = mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build();
setController(controller);
}
複製程式碼
使用mSimpleDraweeControllerBuilder
來構建一個Controller
,而Controller
是由build
模式所建立,這裡我們能看到uri
也交由Controller
管理。其實最終uri
會封裝成一個ImageRequest
,Controller
真正持有的是uri
的封裝體ImageRequest
。在SimpleDraweeView
中它會重寫setImageURI
方法,最終也就是將ImageView
中的原生方法給覆蓋掉。還有其它的類似的setImageResource
與setImageBitmap
等,Fresco
都在這些方法上加了@Deprecated
,意思就是說不推薦使用,如果使用的話就跟直接使用ImageView
沒什麼區別,這就無法體驗到Fresco
的強大的特性。在Fresco
中統一由setController
來替代。
關於
Controller
後續文章會詳細分析。
Fresco
如果我們根據Fresco官方文件的正常步驟來使用的話就無需擔心這一步,因為在我們在使用Fresco
之前都要先呼叫Fresco.initialize(context)
public static void initialize(
Context context,
@Nullable ImagePipelineConfig imagePipelineConfig,
@Nullable DraweeConfig draweeConfig) {
if (sIsInitialized) {
FLog.w(
TAG,
"Fresco has already been initialized! `Fresco.initialize(...)` should only be called " +
"1 single time to avoid memory leaks!");
} else {
sIsInitialized = true;
}
// we should always use the application context to avoid memory leaks
context = context.getApplicationContext();
if (imagePipelineConfig == null) {
//初始化ImagePipeline工廠,包含ImagePipelineConfig 相關初始化配置資訊
// (三級快取、圖片解碼/編碼、轉化、漸變、bitmap配置、四種executor 分別為 io、decode、background、lightweight background)等
ImagePipelineFactory.initialize(context);
} else {
ImagePipelineFactory.initialize(imagePipelineConfig);
}
//初始化Drawee相關配置資訊
initializeDrawee(context, draweeConfig);
}
複製程式碼
除了初始化ImagePipeline
之外,最後還會呼叫initializeDrawee (context, draweeConfig)
,我們來看下initializeDrawee
做了什麼
private static void initializeDrawee(
Context context,
@Nullable DraweeConfig draweeConfig) {
//構建PipelineDraweeControllerBuilderSupplier,
//其中ImagePipeline、PipelineDraweeControllerFactory、ControllerListener set集合
sDraweeControllerBuilderSupplier =
new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
//初始化SimpleDrawee
//初始化時通過呼叫PipelineDraweeControllerBuilderSupplier實現的Supplier的get()方法
// 返回配置資訊的封裝體PipelineDraweeControllerBuilder implements SimpleDraweeControllerBuilder
SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}
複製程式碼
在這裡我們會看到之前所提到的sDraweeControllerBuilderSupplier
與SimpleDraweeView
中必須優先呼叫的initialize
方法。相信現在應該明白的為什麼在使用Fresco
之前必須呼叫它的initialize
方法了。因為它必須要初始化一些必要的配置資訊,其中就包括使用的控制元件SimpleDraweeView
的配置資訊。
GenericDraweeView
上面所說的SimpleDraweeView
的父類是GenericDraweeView
,它做的事情很簡單,處理xml
相關的屬性。它會通過inflateHierarchy
方法進行初始化。
protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
GenericDraweeHierarchyBuilder builder =
GenericDraweeHierarchyInflater.inflateBuilder(context, attrs);
setAspectRatio(builder.getDesiredAspectRatio());
setHierarchy(builder.build());
}
複製程式碼
它是由GenericDraweeHierarchyBuilder
來統一封裝這些屬性。最終通過build
方法來構建GenericDraweeHierarchy
,這就是Fresco
的圖層。然後通過setHierarchy
將圖層傳遞給DraweeHolder
。DraweeHolder
是用來管理Hierarchy
與Controller
的。而DraweeHolder
是在最底層的DraweeView
中,這也是GenericDraweeView
的父類。下面我們進入DraweeView
,來看看它到底做了什麼。
DraweeView
DraweeView
是Fresco
最底層的控制元件,也是我們使用它展示圖片的基礎,它繼承於ImageView
,所以它能做的事也無非於在原生ImageView
上做擴充套件或者方法重寫,從而來實現自己的一套邏輯。先看下它的構造方法
public DraweeView(Context context) {
super(context);
init(context);
}
複製程式碼
沒什麼特別的邏輯,就一個init
方法,那麼就進入init
看看
/** This method is idempotent so it only has effect the first time it`s called */
private void init(Context context) {
if (mInitialised) {
return;
}
mInitialised = true;
mDraweeHolder = DraweeHolder.create(null, context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ColorStateList imageTintList = getImageTintList();
if (imageTintList == null) {
return;
}
setColorFilter(imageTintList.getDefaultColor());
}
// In Android N and above, visibility handling for Drawables has been changed, which breaks
// activity transitions with DraweeViews.
mLegacyVisibilityHandlingEnabled = sGlobalLegacyVisibilityHandlingEnabled &&
context.getApplicationInfo().targetSdkVersion >= 24; //Build.VERSION_CODES.N
}
複製程式碼
我們可以看到它會通過mInitialised
來判斷是否需要初始化,原始碼註釋也說明的該方法只會呼叫一次。這是因為建立Hierarchy
的代價太大,所以只會建立一次,以後都會使用同一個mDraweeHolder
中的Hierarchy
,所以會看到這裡就必須初始化一個mDraweeHolder
。在DraweeView
中還有以下幾個主要方法:
void setHierarchy(DH hierarchy)
設定Hierarchy
,同時會將Hierarchy
交由mDraweeHolder
管理,最後還會呼叫super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
來將Hierarchy
中的圖層樹顯示出來。Drawable getTopLevelDrawable()
會通過mDraweeHolder
中的Hierarchy
來獲取圖層樹。void setController(@Nullable DraweeController draweeController)
設定Controller
,同時也會將Hierarchy
中的圖層樹顯示出來。void onAttachedToWindow()
、void onDetachedFromWindow()
、void onStartTemporaryDetach()
與void onFinishTemporaryDetach()
是來控制圖層的顯示與隱藏、繫結與解綁的回撥函式,它們都分別會呼叫mDraweeHolder
的onAttach()
與onDetach()
。其實最終呼叫的還是Controller
中的onAttach()
與onDetach()
。boolean onTouchEvent(MotionEvent event)
控制控制元件的觸控,如果Controller
有效的話會呼叫Controller
中的onTouchEvent
。void setAspectRatio(float aspectRatio)
用來設定DraweeView
顯示的寬高比例。setImageDrawable
、setImageBitmap
、setImageResource
與setImageURI
這些方法都是原生ImageView
的方法,但在DraweeView
中這些方法都被加上了@Deprecated
標記。標明不推薦使用,如果一定使用的話,那麼DraweeView
將會退化成一個普通的ImageView
。因為在DraweeView
中都是通過Controller
來體現它的快取、載入機制等特性。
上面這些就是DraweeView
的主要涉及到的方法與特性,不過在DraweeView
中基本上每一個方法都涉及到了DraweeHolder
,那它到底是幹什麼的呢?別急下面就輪到它了。
DraweeHolder
A holder class for Drawee controller and hierarchy.
複製程式碼
上面的是官方註釋,說明DraweeHolder
是用來管理Hierarchy
與Controller
的,同時也是它們之間的聯絡的橋樑。DraweeView
以及它的子類都是通過它來間接操作Controller
與Hierarchy
。
public static <DH extends DraweeHierarchy> DraweeHolder<DH> create(
@Nullable DH hierarchy,
Context context) {
DraweeHolder<DH> holder = new DraweeHolder<DH>(hierarchy);
holder.registerWithContext(context);
return holder;
}
複製程式碼
它是通過公有的靜態方法來建立自身例項的。在上面的DraweeView
的init
方法中會呼叫。在其內部的DraweeEventTracker
,是用來記錄事件的傳遞,方便dubug
的除錯。如果不需要的話,可以在Fresco.initialize()
之前呼叫DraweeEventTracker.disable()
。那麼剩下的方法其實基本上在DraweeView
中都說過。
onAttach()
與onDetach()
,都會呼叫attachOrDetachController()
,根據情況分別呼叫attachController()
與detachController()
,最終呼叫的就是Controller
的onAttach()
與onDetach()
Drawable getTopLevelDrawable()
呼叫mHierarchy.getTopLevelDrawable()
獲取圖層樹。void setController(@Nullable DraweeController draweeController)
設定Controller
,在設定之前會先判斷是否已經wasAttached
,如果是的話就先呼叫detachController()
,然後清除老的Controller
,再將Hierarchy
設定到新的Controller
中。最後再attachController()
進行繫結顯示圖層。void setHierarchy(DH hierarchy)
設定Hierarchy
,如果Controller
有效的話就與Hierarchy
建立連結,將Hierarchy
設定到Controller
中。
以上就是DraweeHolder
的主要方法,都跟Controller
與Hierarchy
相關。而DraweeHolder
又與DraweeView
相連,所以最終還是要回到Controller
與Hierarchy
中。
End
這次主要是分析了Fresco
中的基本元件DraweeView
與它的子類。如果你還想進一步瞭解Hierarchy
與Controller
的原理,下篇文章將會詳細分析相關的原理,敬請期待!