在Android
中圖片加載的框架不少,例如:Fresco
、Picasso
、Glide
與Imageloader
。它們都有各自的優勢,但總的來講,使用起來方便簡單、可配置性高與提供良好的緩存機制。因爲日常主要用的仍是Fresco
,因此這裏有必要對Fresco
的原理進行深刻研究。這樣對於之後的使用與理解將會獲得巨大的幫助。html
Fresco
是專一於對圖片加載而設計的框架,因此對於以圖片爲主的App
強烈推薦使用。Fresco
對於圖片的展現支持多種狀況:backgroud image
(背景圖)、placeholder image
(佔位圖)、actual image
(加載的圖片)、progress bar image
(進度條)、retry image
(從新加載的圖片)、failure image
(失敗圖片)與overlay image
(疊加圖)。Fresco
既然支持這麼多圖片展現狀況,那麼它對此次圖層的管理模式又是怎麼樣的呢?Fresco
對於這些圖層的管理都交給了Hierarchy
,而這些圖層的數據都經過Controller
來設置。先不分析這些,這些後續文章會詳細分析,今天先從Fresco
的基本組件開始。git
首先這是對
Fresco
的源碼分析,因此在看這篇文章以前你應該要有使用Fresco
的基礎,若是沒有的強烈推薦看下Fresco官方文檔。github
咱們使用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
中咱們能找到它的初始化方法bash
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
來替代。app
關於
Controller
後續文章會詳細分析。框架
若是咱們根據Fresco官方文檔的正常步驟來使用的話就無需擔憂這一步,由於在咱們在使用Fresco
以前都要先調用Fresco.initialize(context)
ide
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
的配置信息。
上面所說的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
是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
,那它究竟是幹什麼的呢?別急下面就輪到它了。
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
中。
此次主要是分析了Fresco
中的基本組件DraweeView
與它的子類。若是你還想進一步瞭解Hierarchy
與Controller
的原理,下篇文章將會詳細分析相關的原理,敬請期待!