這么久以來雖然經(jīng)常用到一些圖庫,但是自己從來沒有真正整理過我們使用過的這些東西有什么不同點,我們?yōu)槭裁匆x擇這個圖庫作為項目的加載圖片的框架(由于本人資歷尚淺,經(jīng)驗也不豐富,所以從沒有試過自己做一個圖片加載庫),今天突發(fā)奇想,也算是讓自己思路更清晰一點,來寫一些關(guān)于這些圖庫的異同之處.
作為一個應(yīng)用豐富的App,圖片加載是必不可少的,甚至對于圖片的質(zhì)量和數(shù)量都是要求很高的,然而對于圖片的加載是一個耗時操作,在UI線程為了避免ANR異常不能做耗時操作,是一個Android開發(fā)人員的基本常識,所以異步加載圖片是必然的,這就讓我們面臨了一個怎么選擇圖片加載框架的問題?市面上比較成熟的圖庫有: Universal-Image-Loader(UIL)、Picasso猜憎、Glide娩怎、Fresco。其實任何一個圖片加載框架都可以當(dāng)做一個普通的下載文件流程胰柑,一般都包含這么幾個步驟:初始化配置->構(gòu)造請求->執(zhí)行請求->處理請求結(jié)果峦树。
1.Universal-Image-Loader(UIL)
這是一個很早就出現(xiàn)的圖片加載框架了,作者是nostra13旦事,UIL使用很方便魁巩,而且自帶多種緩存策略,如最大尺寸先刪除姐浮、時間最久刪除 等谷遂,使用它,基本上不需要考慮太多的問題卖鲤。另外肾扰,UIL還支持圖片下載進(jìn)度的監(jiān)聽畴嘶,如果你有特殊需求,則可以在 圖片開始下載前集晚、剛開始下載等各個時間段來做一些額外的事情窗悯,非常方便。而且UIL可以在View滾動的過程中暫停圖片的加載偷拔,有利于提升界面的流暢度蒋院。但由于作者在兩年前宣布不再維護(hù)這個項目了,也就是說這個項目將不再更新,所以如果你將開發(fā)一個新項目,本人不推薦你使用此框架,因為市面上還有其它跟它一樣強(qiáng)大甚至更好的圖庫可以使用,但如果你現(xiàn)在的項目是一個老牌項目,那也沒必要著急更換它,因為它還是很強(qiáng)大的,沒有到跟不上時代的地步,到了真需要更換的時候再換也來得及
2.Picasso
這是一個來自于開源界名氣很大的Square公司開發(fā)的,總體來看,它屬于輕量級別的圖片加載庫莲绰,但它也有著一些自己的特色欺旧。比如,很特別的擁有統(tǒng)計功能蛤签,可以知道使用了多少內(nèi)存辞友、緩存命中如何,另外它本身沒有什么緩存策略震肮,而是依賴所用的網(wǎng)絡(luò)庫的緩存策略——其實就是依賴了OkHttp称龙。Picasso使用起來也是比較簡單的,不過對于新項目來說戳晌,也不是很推薦茵瀑,原因就在于,Glide比它更優(yōu)秀躬厌,而且使用起來幾乎 是一樣的……
緩存路徑:
這里講一下關(guān)于Picasso緩存路徑:
data/data/your package name/cache/picasso-cache/(默認(rèn)路徑)
有時候根據(jù)需求,我們需要更改其緩存路徑,這里我們分析一下啊,Picasso 底層其實是使用OkHttp去下載圖片马昨,同時在設(shè)置Picasso的時候,有一個.downloader(Downloader downloader)方法扛施,我們可以傳遞進(jìn)去一個OkHttpDownloader( OkHttpClient client).
Picasso picasso = new Picasso.Builder(Context)
.downloader(new OkHttpDownloader(client))
.build();
看到這里我們應(yīng)該想到,如果給OkHttpClient設(shè)置Cache是不是就可以改變緩存路徑呢?只需要給OkHttpClient設(shè)置.cache(new Cache(file, maxSize))就可以實現(xiàn)修改緩存路徑了鸿捧。代碼就是:
File file = new File("緩存文件所在路徑");
if (!file.exists()) {
file.mkdirs();
}
long maxSize = Runtime.getRuntime().maxMemory() / 8; //設(shè)置圖片緩存大小為運(yùn)行時緩存的八分之一
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(file, maxSize))
.build();
Picasso picasso = new Picasso.Builder(this)
.downloader(new OkHttpDownloader(client))
.build();
這里需要注意的就是:當(dāng)把OkHttp升級到OkHttp3時,給downloader設(shè)置OkHttpDownloader()并不支持OkHttp3.如果想使用OkHttp3,需要使用 OkHttp3Downloader來替代OkHttpDownloader,OkHttp3Downloader庫是jakewharton專為為了能使用OkHttp3作為下載器而寫的,使用姿勢也很簡單:在Module dependencies添加依賴:
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
然后上面的代碼改成:
Picasso picasso = new Picasso.Builder(this)
.downloader(new OkHttp3Downloader(client)) //注意此處替換為 OkHttp3Downloader
.build();
關(guān)于Picasso實例化對象(兩種方式)
- Picasso.with(context)
此方法提供默認(rèn)方式疙渣,生成單例的Picasso對象. - new Picasso.Builder(context).build()
此方式提供自定義線程池匙奴、緩存、下載器等方法.
關(guān)于Picasso源碼簡單分析
- Picasso.with(context),在源碼中我們很容易就可以看出這是在構(gòu)造一個單例的對象,而且是通過new Builder(context).build()建造者模式構(gòu)建.
1.通過Builder(context)往下看就會發(fā)現(xiàn) this.context = context.getApplicationContext();得到的是全局的上下文,這是為了讓Picasso下載器同步應(yīng)用的生命周期的,然后我們的重點就可以放在build()上了.
2.在build()方法里面我們會發(fā)現(xiàn)有六個方法:
(源代碼)
(第一個方法) if (downloader == null) {
downloader = Utils.createDefaultDownloader(context); //創(chuàng)建一個默認(rèn)的下載器.
1.其中Downloader是一個用于從網(wǎng)絡(luò)上加載圖片的接口妄荔,需要實現(xiàn)load和shutdown方法泼菌。load用于加載圖片,shutdown用于關(guān)閉一些操作.
2.Picasso的線程池是經(jīng)過優(yōu)化過的啦租,可以根據(jù)當(dāng)前設(shè)備網(wǎng)絡(luò)狀況設(shè)置其ThreadCount哗伯。
在網(wǎng)絡(luò)良好的條件下,線程池持有較多線程篷角,保證下載速度夠快焊刹。在網(wǎng)絡(luò)較差的條件下(2G網(wǎng)絡(luò)等),線程池減少持有線程,保證帶寬不會被多個連接阻塞虐块。
}
(第二個方法) if (cache == null) {
cache = new LruCache(context); //初始化緩存,創(chuàng)建內(nèi)存緩存
}
(第三個方法) if (service == null) {
service = new PicassoExecutorService(); //初始化線程池
1.默認(rèn)啟動了3個核心線程俩滥,采用了PriorityBlockingQueue優(yōu)先級阻塞隊列,也就是說Picasso支持優(yōu)先級調(diào)度.(對網(wǎng)絡(luò)狀態(tài)進(jìn)行線程的優(yōu)化)
}
(第四個方法) if (transformer == null) {
transformer = RequestTransformer.IDENTITY; // 初始化轉(zhuǎn)換器,請求的前置處理贺奠,在請求發(fā)出去之前執(zhí)行霜旧,類似于攔截器
1.默認(rèn)RequestTransformer.IDENTITY表示不作處理.
}
(第五個方法) Stats stats = new Stats(cache); //狀態(tài)控制類,統(tǒng)計一些狀態(tài)信息,用來發(fā)送各種消息儡率,例如查找圖片緩存的命中率挂据,下載是否完成等
(第六個方法) Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); //最后創(chuàng)建調(diào)度器,用來分發(fā)任務(wù)
1.從build方法中可以看出,大多數(shù)參數(shù)直接傳進(jìn)了這個類的構(gòu)造方法中喉悴,可見這個類不容小覷棱貌。
Dispatcher主要是來調(diào)度任務(wù)的玖媚,比如提交任務(wù)箕肃,取消任務(wù),暫停加載今魔,恢復(fù)加載勺像,重試,加載完成错森,監(jiān)聽網(wǎng)絡(luò)等等吟宦。
同樣,里面也用了一個HandlerThread和Handler來分發(fā)任務(wù)涩维。通過一系列的dispatchXXX殃姓,由Handler發(fā)送消息,Handler接收消息后瓦阐,通過performXXX來調(diào)度任務(wù)蜗侈。
- 關(guān)于Picasso中任務(wù)調(diào)度器Dispatcher的簡單分析:
翻看源碼我們會看到其Dispatcher類的構(gòu)造方法有六個參數(shù),這里主要分析其中兩個,一個ExecutorService,另一個就是Handler,
第一,我們首先講一下Handler
public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
super(looper);
this.dispatcher = dispatcher;
}
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: { //提交請求
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
case REQUEST_CANCEL: { //取消請求
Action action = (Action) msg.obj;
dispatcher.performCancel(action);
break;
}
case TAG_PAUSE: { //暫停請求
Object tag = msg.obj;
dispatcher.performPauseTag(tag);
break;
}
case TAG_RESUME: { //恢復(fù)請求
Object tag = msg.obj;
dispatcher.performResumeTag(tag);
break;
}
case HUNTER_COMPLETE: { //捕獲完成
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
case HUNTER_RETRY: { //重試
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performRetry(hunter);
break;
}
case HUNTER_DECODE_FAILED: { //解碼失敗
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performError(hunter, false);
break;
}
}
}
第二,關(guān)于ExecutorService
if (service instanceof PicassoExecutorService) {
((PicassoExecutorService) service).adjustThreadCount(info);
}
//在adjustThreadCount(info)方法里面就是下面這段代碼,很明可以看出,這段代碼是根據(jù)網(wǎng)絡(luò)狀態(tài)信息info來決定線程池個數(shù)的,默認(rèn)是3條線程
if (info == null || !info.isConnectedOrConnecting()) {
setThreadCount(DEFAULT_THREAD_COUNT);
return;
}
switch (info.getType()) {
case ConnectivityManager.TYPE_WIFI: //wife狀態(tài)下
case ConnectivityManager.TYPE_WIMAX: //802·16無線城域網(wǎng),類似于wife
case ConnectivityManager.TYPE_ETHERNET: //以太網(wǎng)數(shù)據(jù)連接
setThreadCount(4);
break;
case ConnectivityManager.TYPE_MOBILE:
switch (info.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_LTE: // 4G狀態(tài)下
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_EHRPD:
setThreadCount(3);
break;
case TelephonyManager.NETWORK_TYPE_UMTS: // 3G狀態(tài)下
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
setThreadCount(2);
break;
case TelephonyManager.NETWORK_TYPE_GPRS: // 2G狀態(tài)下
case TelephonyManager.NETWORK_TYPE_EDGE:
setThreadCount(1);
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT);
}
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT); //默認(rèn)狀態(tài)下是3條
- 接下來我們講一下Picasso中的load("image src url")方法,源碼中l(wèi)oad()方法有四個
(源代碼如下)
//通過uri參數(shù)獲得RequestCreator對象
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
//通過請求路徑path,獲取其uri參數(shù)獲得RequestCreator對象
public RequestCreator load(String path) {
if (path == null) {
return new RequestCreator(this, null, 0);
}
if (path.trim().length() == 0) {
throw new IllegalArgumentException("Path must not be empty.");
}
return load(Uri.parse(path));
}
//通過文件file獲得其uri參數(shù),從而獲取RequestCreator對象
public RequestCreator load(File file) {
if (file == null) {
return new RequestCreator(this, null, 0);
}
return load(Uri.fromFile(file));
}
//通過指定的請求id來獲取RequestCreator對象
public RequestCreator load(int resourceId) {
if (resourceId == 0) {
throw new IllegalArgumentException("Resource ID must not be zero.");
}
return new RequestCreator(this, null, resourceId);
}
(從上面的四個方法可以看出,其實又可以分為兩類:uri和resourceId。uri又分為file和net睡蟋。)
從上面的代碼可以看出load()方法最終都是需要得到RequestCreator對象,那RequestCreator又有什么作用呢?
RequestCreator是用來配置加載參數(shù)的踏幻。RequestCreator有兩個功能
- 配置加載參數(shù)。
包括placeHolder與error圖片戳杀,加載圖片的大小该面、旋轉(zhuǎn)、居中等屬性信卡。 - 執(zhí)行加載隔缀。
通過調(diào)用into(object)方法進(jìn)行加載。
- 說完load()方法,接下來得說說into()方法了
into()可以說是Picasso中比較復(fù)雜的方法,方法有五個,方法體也比較長,代碼我這里就不貼了,大家可以自行翻看,這個方法在RequestCreator里面
通過源碼分析,其邏輯還是比較清晰的,這里總結(jié)一下:
- into會檢查當(dāng)前是否是在主線程上執(zhí)行傍菇。
long started = System.nanoTime();
checkMain();
- 如果我們沒有提供一個圖片資源并且有設(shè)置placeholder蚕泽,那么就會把我們設(shè)置的placeholder顯示出來,并中斷執(zhí)行。(下面的代碼)
Drawable drawable =
placeholderResId != 0 ? picasso.context.getResources().getDrawable(placeholderResId)
: placeholderDrawable;
if (!data.hasImage()) {
picasso.cancelRequest(target);
target.onPrepareLoad(drawable);
return;
}
- defered屬性我們一般情況下不需要關(guān)注须妻,只有當(dāng)我們調(diào)用了RequestCreator的fit方法時defered才為true仔蝌,但我們幾乎不會這樣做。
if (deferred) {
throw new IllegalStateException("Fit cannot be used with get.");
}
- 接下來就是創(chuàng)建了一個Request對象荒吏,我們在前面做得一些設(shè)置都會被封裝到這個Request對象里面敛惊。
Request finalData = createRequest(started);
String key = createKey(finalData, new StringBuilder());
- 檢查我們要顯示的圖片是否可以直接在緩存中獲取,如果有就直接顯示出來好了绰更。
if (!skipMemoryCache) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
picasso.cancelRequest(target);
target.onBitmapLoaded(bitmap, MEMORY);
return;
}
}
- 緩存沒命中瞧挤,那就只能費點事把源圖片down下來了。這個過程是異步的儡湾,并且通過一個Action來完成請求前后的銜接工作特恬。
Action action =
new TargetAction(picasso, target, request, skipMemoryCache, errorResId, errorDrawable,
requestKey);
picasso.enqueueAndSubmit(action); //異步提交請求action
3.Glide
Google官方推薦圖庫,在許多Android的原生應(yīng)用中都采用了Glide來加載圖片徐钠。其實Glide與Picasso的使用姿勢是驚人的相似的
Picasso.with(context).load("image src url").into(ImageView);
Glide.with(context).load("image src url").into(ImageView);(當(dāng)然這只是它們常用的)
從某種程度上說癌刽,Glide可以看作是Picasso的增強(qiáng)版,所以它有著自己獨特的優(yōu)勢,Glide不僅支持常見的jpg和png格式,還能顯示gif動畫尝丐,甚至是視頻显拜,或者說它已經(jīng)不僅僅是一個普通的圖片加載庫了,而是一個多媒體庫爹袁。另外一個優(yōu)勢是远荠,Glide在內(nèi)存方面的表現(xiàn)相當(dāng)出色,首先它的圖片默認(rèn)格式是RGB565失息,要比Picasso默認(rèn)的ARGB8888節(jié)省更多內(nèi)存譬淳,而且它緩存的不是原始圖片,而是緩存了圖片的實際大小——比如加載的圖片是 19201080的大小盹兢,而在你的App中邻梆,顯示該圖片的ImageView控件大小只有1280720,那么Glide就會很聰明的自動緩存 1280*720大小的圖片蛤迎。
關(guān)于Glide的源碼簡單解析
在Glide中我們經(jīng)常用的一種姿勢就是:
Glide.with(context).load("image src url").into(ImageView);
那么這里解析也是從這段代碼開始
1.關(guān)于with(context)方法
在源碼中這個方法是靜態(tài)的,重載方法有五個,
//第一個方法傳入一個上下文,根據(jù)上下文的所屬生命周期來確定需要獲取對象的生命周期
public static RequestManager with(Context context) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(context);
}
//第二個方法傳入一個activity
public static RequestManager with(Activity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
//第三個方法傳入一個FragmentActivity
public static RequestManager with(FragmentActivity activity) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(activity);
}
//第四個方法傳入一個app.Fragment
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static RequestManager with(android.app.Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}
//第五個方法傳入一個V4兼容包下的Fragment
public static RequestManager with(Fragment fragment) {
RequestManagerRetriever retriever = RequestManagerRetriever.get();
return retriever.get(fragment);
}
這幾個方法不長,邏輯也很清晰,都是通過一個RequestManagerRetriever的靜態(tài)get()方法得到一個RequestManagerRetriever對象确虱,其實這個靜態(tài)get()方法就是一個單例的具體實現(xiàn),然后再調(diào)用RequestManagerRetriever的實例get()方法,去獲取RequestManager對象替裆。這里需要注意的是實例get()方法中傳入的參數(shù)類型,不同的參數(shù)類型對應(yīng)不同的生命周期,下面代碼就是具體的實現(xiàn)
(源代碼)
private RequestManager getApplicationManager(Context context) {
// Either an application context or we're on a background thread.
if (applicationManager == null) {
synchronized (this) {
if (applicationManager == null) {
// Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
// However, in this case since the manager attached to the application will not receive lifecycle
// events, we must force the manager to start resumed using ApplicationLifecycle.
applicationManager = new RequestManager(context.getApplicationContext(),
new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
}
}
}
return applicationManager;
}
//第一種,傳入全局的上下文,根據(jù)不同類型的上下文執(zhí)行不同的方法
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);
}
//第二種,傳入一個FragmentActivity,根據(jù)情況的不同調(diào)用的方法也不同,這里需要注意一下if (Util.isOnBackgroundThread())這種情形表示在子線程執(zhí)行Glide加載圖片,最終會執(zhí)行最上面那個方法
public RequestManager get(FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm);
}
}
//第三種,傳入一個Fragment,也是根據(jù)情況的不同調(diào)用的方法也不同,注意 if (Util.isOnBackgroundThread())這種情形表示在子線程執(zhí)行Glide加載圖片,最終也會執(zhí)行最上面的方法
public RequestManager get(Fragment fragment) {
if (fragment.getActivity() == null) {
throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
}
if (Util.isOnBackgroundThread()) {
return get(fragment.getActivity().getApplicationContext());
} else {
FragmentManager fm = fragment.getChildFragmentManager();
return supportFragmentGet(fragment.getActivity(), fm);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
RequestManagerFragment current = getRequestManagerFragment(fm); //獲取隱藏的app包下的Fragment
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
RequestManager supportFragmentGet(Context context, FragmentManager fm) {
SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm); //獲取隱藏的V4包下的Fragment
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
current.setRequestManager(requestManager);
}
return requestManager;
}
其實通過上面的代碼看下來,邏輯上還是比較清晰的,RequestManagerRetriever類中看似有很多個get()方法的重載校辩,什么Context參數(shù),Activity參數(shù)辆童,F(xiàn)ragment參數(shù)等等宜咒,實際上只有兩種情況而已,即傳入Application類型的參數(shù)把鉴,和傳入非Application類型的參數(shù)故黑。
- 先看傳入Application參數(shù)的情況儿咱。如果在Glide.with()方法中傳入的是一個Application對象,那么這里就會調(diào)用帶有Context參數(shù)的get()方法重載场晶,然后會調(diào)用getApplicationManager()方法來獲取一個RequestManager對象混埠。Application對象的生命周期即應(yīng)用程序的生命周期,因此Glide并不需要做什么特殊的處理诗轻,它自動就是和應(yīng)用程序的生命周期是同步的钳宪,如果應(yīng)用程序關(guān)閉的話,Glide的加載也會同時終止扳炬。
- 再看傳入非Application參數(shù)的情況吏颖。不管你在Glide.with()方法中傳入的是Activity、FragmentActivity恨樟、v4包下的Fragment半醉、還是app包下的Fragment,最終的流程都是一樣的劝术,那就是會向當(dāng)前的Activity當(dāng)中添加一個隱藏的Fragment缩多。具體添加的邏輯是在上述代碼都有注釋說明,分別對應(yīng)的app包和v4包下的兩種Fragment的情況夯尽。那么這里為什么要添加一個隱藏的Fragment呢瞧壮?因為Glide需要知道加載的生命周期登馒。比如說:如果你在某個Activity上正在加載著一張圖片匙握,結(jié)果圖片還沒加載出來,Activity就被用戶關(guān)掉了陈轿,但是如果圖片請求還在繼續(xù),當(dāng)請求的數(shù)據(jù)回來之后沒有界面可以進(jìn)行渲染,這就會造成內(nèi)存泄漏,所以這種情況下肯定是需要取消Glide的網(wǎng)絡(luò)的請求的圈纺。可是Glide并沒有辦法知道Activity的生命周期麦射,于是Glide就使用了添加隱藏Fragment的技巧蛾娶,因為Fragment的生命周期和Activity是同步的,如果Activity被銷毀了潜秋,F(xiàn)ragment是可以監(jiān)聽到的蛔琅,這樣Glide就可以捕獲這個事件并停止網(wǎng)絡(luò)請求了。這里需要注意的一點就是:如果我們是在非主線程當(dāng)中使用的Glide峻呛,那么不管你是傳入的Activity還是Fragment罗售,都會被強(qiáng)制當(dāng)成Application來處理。
總體來說钩述,第一個with()方法其實就是為了得到一個RequestManager對象而已寨躁,然后Glide會根據(jù)我們傳入with()方法的參數(shù)來確定圖片加載的生命周期,接下來我們就分析一下load("image src url")這個方法
2.關(guān)于load("image src url")方法
通過上面的with(context)方法返回的都是RequestManager對象,那么load()方法肯定在RequestManager這個類里面,我們知道Glide是支持圖片URL字符串牙勘、圖片本地路徑等等加載形式的,load重載的方法有很多,我們常用的一般都是load(String string),關(guān)于URL字符串加載形式,下面我們分析一下這種情形:
(源代碼:)
/**
* Returns a request builder to load the given {@link java.lang.String}.
* signature.
*
* @see #fromString()
* @see #load(Object)
*
* @param string A file path, or a uri or url handled by {@link com.bumptech.glide.load.model.UriLoader}.
*/
public DrawableTypeRequest<String> load(String string) {
return (DrawableTypeRequest<String>) fromString().load(string);
}
/ * @see #from(Class)
* @see #load(String)
*/
public DrawableTypeRequest<String> fromString() {
return loadGeneric(String.class);
}
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");
}
//傳入StreamStringLoader對象,獲取DrawableTypeRequest對象
return optionsApplier.apply(
new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
glide, requestTracker, lifecycle, optionsApplier));
}
RequestManager類的代碼是非常多的职恳,關(guān)于load(String string)簡化之后比較重要的方法就只剩下上述代碼中的這三個方法。
先來看load()方法,這個方法中的邏輯是非常簡單的放钦,只有一行代碼色徘,就是先調(diào)用了fromString()方法,再調(diào)用load()方法操禀,然后把傳入的圖片URL地址傳進(jìn)去贺氓。而fromString()方法也極為簡單,就是調(diào)用了loadGeneric()方法床蜘,并且指定參數(shù)為String.class.
執(zhí)行l(wèi)oadGeneric()方法時辙培,分別調(diào)用了Glide.buildStreamModelLoader()方法和Glide.buildFileDescriptorModelLoader()方法來獲得ModelLoader對象。ModelLoader對象是用于加載圖片的邢锯,而我們給load()方法傳入不同類型的參數(shù)扬蕊,這里也會得到不同的ModelLoader對象。由于我們剛才傳入的參數(shù)是String.class丹擎,因此最終得到的是StreamStringLoader對象尾抑,它是實現(xiàn)了ModelLoader接口的。
最后可以看到蒂培,loadGeneric()方法是要返回一個DrawableTypeRequest對象的再愈,因此在loadGeneric()方法的最后又new了一個DrawableTypeRequest對象,然后把剛才獲得的ModelLoader對象(StreamStringLoader對象)护戳,還有其他的一些參數(shù)傳進(jìn)去翎冲。
這里就可以看到如果得到一個DrawableTypeRequest對象,那這里面肯定是有所作為的(源碼如下:)
/**
* Attempts to always load the resource as a {@link android.graphics.Bitmap}, even if it could actually be animated.
*
* @return A new request builder for loading a {@link android.graphics.Bitmap}
*/
public BitmapTypeRequest<ModelType> asBitmap() {
return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
fileDescriptorModelLoader, optionsApplier));
}
public GifTypeRequest<ModelType> asGif() {
return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier));
}
這里就可以看到我們經(jīng)常使用的 asBitmap() 和 asGif(),這兩個方法分別是用于強(qiáng)制指定加載靜態(tài)圖片和動態(tài)圖片,而從源碼中可以看出,它們分別又創(chuàng)建了一個BitmapTypeRequest和GifTypeRequest媳荒,如果沒有進(jìn)行強(qiáng)制指定的話抗悍,那默認(rèn)就是使用DrawableTypeRequest。
上面的fromString()方法會返回一個DrawableTypeRequest對象钳枕,接下來會調(diào)用這個對象的load()方法缴渊,把圖片的URL地址傳進(jìn)去。通過對DrawableTypeRequest類的查看,沒有找到load()方法,所以在DrawableTypeRequest的父類DrawableRequestBuilder中可以看到load()方法.
@Override
public DrawableRequestBuilder<ModelType> load(ModelType model) {
super.load(model);
return this;
}
/**
* {@inheritDoc}
*
* <p>
* Note - If no transformation is set for this load, a default transformation will be applied based on the
* value returned from {@link android.widget.ImageView#getScaleType()}. To avoid this default transformation,
* use {@link #dontTransform()}.
* </p>
*
* @param view {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public Target<GlideDrawable> into(ImageView view) {
return super.into(view);
}
DrawableRequestBuilder中有很多個方法鱼炒,這些方法其實就是Glide絕大多數(shù)的API了衔沼。通過源碼,我們可以看到load()和into()這兩個方法了,所以我們總結(jié)一下:最終load()方法返回的其實就是一個DrawableTypeRequest對象。那么接下來分析into()方法中的邏輯昔瞧。
3.關(guān)于into()方法的分析
從上面的代碼我們可以看到DrawableRequestBuilder類里面的into()方法只有
return super.into(view);
這說明into()真正的實現(xiàn)邏輯是在DrawableRequestBuilder的父類GenericRequestBuilder,所以into()方法需要在這個類進(jìn)行分析
/**
* Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into the view, and frees
* any resources Glide may have previously loaded into the view so they may be reused.
*
* @see Glide#clear(android.view.View)
*
* @param view The view to cancel previous loads for and load the new resource into.
* @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
*/
public Target<TranscodeType> into(ImageView view) {
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
return into(glide.buildImageViewTarget(view, transcodeClass));
}
在上面代碼中into(ImageView view)方法里面最后一行代碼先是調(diào)用glide.buildImageViewTarget()方法,這個方法會構(gòu)建出一個Target對象指蚁,Target對象則是用來最終展示圖片用的,如果我們跟進(jìn)去的話會看到如下代碼:
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
這里可以看到會通過imageViewTargetFactory調(diào)用buildTarget(imageView, transcodedClass)這個方法,如果繼續(xù)查看源碼,會發(fā)現(xiàn)在buildTarget()方法中會根據(jù)傳入的class參數(shù)來構(gòu)建不同的Target對象硬爆。這個class參數(shù)其實基本上只有兩種情況欣舵,如果你在使用Glide加載圖片的時候調(diào)用了asBitmap()方法,那么這里就會構(gòu)建出BitmapImageViewTarget對象缀磕,否則的話構(gòu)建的都是GlideDrawableImageViewTarget對象缘圈。
這里glide.buildImageViewTarget(view, transcodeClass),我們得到一個GlideDrawableImageViewTarget對象,然后回到
return into(glide.buildImageViewTarget(view, transcodeClass));
我們可以看到into(GlideDrawableImageViewTarget對象)的源碼
/**
* Set the target the resource will be loaded into.
*
* @see Glide#clear(com.bumptech.glide.request.target.Target)
*
* @param target The target to load the resource into.
* @return The given target.
*/
public <Y extends Target<TranscodeType>> Y into(Y target) {
Util.assertMainThread();
if (target == null) {
throw new IllegalArgumentException("You must pass in a non null Target");
}
if (!isModelSet) {
throw new IllegalArgumentException("You must first set a model (try #load())");
}
Request previous = target.getRequest();
if (previous != null) {
previous.clear();
requestTracker.removeRequest(previous);
previous.recycle();
}
Request request = buildRequest(target); //調(diào)用buildRequest()方法構(gòu)建出了一個Request對象
target.setRequest(request);
lifecycle.addListener(target);
requestTracker.runRequest(request); //執(zhí)行Request對象
return target;
}
上面代碼構(gòu)建出來的Request對象是用來發(fā)出加載圖片請求的劣光,它是Glide中非常關(guān)鍵的一個組件.這就是我們經(jīng)常用的with(),load(),into()最表面的意思,里面還有很多很多內(nèi)容沒有寫出來,我也是參考郭霖大神的文章以及自己的拙見才寫這么點東西的,也算是做了一次筆記吧!
關(guān)于Glide優(yōu)化
- 配置使用Volley和OkHttp來加載圖片
Volley和OkHttp是項目中使用最廣泛的兩個網(wǎng)絡(luò)庫,也是兩個相對來說速度比較快的糟把,Glide默認(rèn)使用的是HttpUrlConnection的方式請求網(wǎng)絡(luò)绢涡,其效率是比較低的,可以使用Volley或者OkHttp(和項目使用的網(wǎng)絡(luò)請求庫一致)作為Glide的網(wǎng)絡(luò)請求方式.
Gradle配置
//使用volley
dependencies {
compile 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
//compile 'com.mcxiaoke.volley:library:1.0.8'
}
//使用okhttp
dependencies {
compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
//compile 'com.squareup.okhttp:okhttp:2.2.0'
}
當(dāng)在Library庫中使用aar的時候遣疯,Library中的GlideModule會自動合并 到主項目中mainfest文件中雄可,當(dāng)使用jar包導(dǎo)入時,需要手動去合并Library合并GlideModule或者使用自己配置的GlideModule缠犀。
Maven配置
//使用volley
<dependency>
<groupId>com.github.bumptech.glide</groupId>
<artifactId>volley-integration</artifactId>
<version>1.4.0</version>
<type>aar</type>
</dependency>
<dependency>
<groupId>com.mcxiaoke.volley</groupId>
<artifactId>library</artifactId>
<version>1.0.8</version>
<type>aar</type>
</dependency>
//使用okhttp
<dependency>
<groupId>com.github.bumptech.glide</groupId>
<artifactId>okhttp-integration</artifactId>
<version>1.4.0</version>
<type>aar</type>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.2.0</version>
<type>jar</type>
</dependency>
jar的形式引入
如果通過Maven数苫,Ant 或者其它系統(tǒng)工具來構(gòu)建的話,是不支持manifest 文件合并的辨液,你必須手動在AndroidManifest.xml添加GlideModule metadata 屬性虐急。
//使用volley
<meta-data
android:name="com.bumptech.glide.integration.volley.VolleyGlideModule"
android:value="GlideModule" />
//使用okhttp
<meta-data
android:name="com.bumptech.glide.integration.okhttp.OkHttpGlideModule"
android:value="GlideModule" />
//添加混淆配置
-keep class com.bumptech.glide.integration.volley.VolleyGlideModule
-keep class com.bumptech.glide.integration.okhttp.OkHttpGlideModule
其實如果我們想使用OkHttp3作為Glide網(wǎng)絡(luò)請求方式,可以自行查看相關(guān)文檔.
4.Fresco
這個號稱是Android平臺上目前最為強(qiáng)大的圖片加載庫,由Facebook公司開發(fā)滔迈。與Glide一樣止吁,F(xiàn)resco也是支持gif動畫顯示,而且在內(nèi)存方面的表現(xiàn)更加優(yōu)秀燎悍。由于將圖片放在Ashmem(匿名共享內(nèi)存)中敬惦,大大降低了App的內(nèi)存占用(因為Ashmem沒有被統(tǒng)計到App的內(nèi)存使用里),再加上各種級別優(yōu)化谈山,使得Fresco基本上告別了OOM俄删,而且Fresco的圖片直接顯示為ARGB8888這種最高質(zhì)量級別,即使是在這種高質(zhì)量的情況下依然保證了比其他庫更少的內(nèi)存占用勾哩,這就是Fresco最吸引人的地方抗蠢。而且類似于進(jìn)度監(jiān)聽举哟、緩存策略等思劳,也是非常完善的,總之作為一個圖片加載庫妨猩,F(xiàn)resco在功能和性能方面已經(jīng)趨于完美了潜叛。
Picasso,Glide,Fresco的區(qū)別
Picasso :和Square的網(wǎng)絡(luò)庫一起能發(fā)揮最大作用,因為Picasso可以選擇將網(wǎng)絡(luò)請求的緩存部分交給了okhttp實現(xiàn)壶硅。使用4.0+系統(tǒng)上的HTTP緩存來代替磁盤緩存.
Picasso 底層是使用OkHttp去下載圖片,所以Picasso底層網(wǎng)絡(luò)協(xié)議為Http.Glide:模仿了Picasso的API威兜,而且在它的基礎(chǔ)上加了很多的擴(kuò)展(比如gif等支持),Glide默認(rèn)的Bitmap格式是RGB_565庐椒,比Picasso默認(rèn)的ARGB_8888格式的內(nèi)存開銷要小一半椒舵;Picasso緩存的是全尺寸的(只緩存一種),而Glide緩存的是跟ImageView尺寸相同的(即5656和128128是兩個緩存) 约谈。
Glide 底層默認(rèn)使用的是HttpUrlConnection的方式請求網(wǎng)絡(luò),所以Glide的底層網(wǎng)絡(luò)協(xié)議也為Http.-
Fresco:最大的優(yōu)勢在于5.0以下(最低2.3)的bitmap加載笔宿。在5.0以下系統(tǒng)犁钟,F(xiàn)resco將圖片放到一個特別的內(nèi)存區(qū)域(Ashmem區(qū),這個區(qū)域沒有被統(tǒng)計到App的內(nèi)存使用里)。當(dāng)然泼橘,在圖片不顯示的時候涝动,占用的內(nèi)存會自動被釋放。這會使得APP更加流暢炬灭,減少因圖片內(nèi)存占用而引發(fā)的OOM醋粟。為什么說是5.0以下,因為在5.0以后系統(tǒng)默認(rèn)就是存儲在Ashmem區(qū)了重归。
Image pipeline 默認(rèn)使用HttpURLConnection米愿。應(yīng)用可以根據(jù)自己需求使用不同的網(wǎng)絡(luò)庫。Fresco的Image Pipeline負(fù)責(zé)圖片的獲取和管理鼻吮。圖片可以來自遠(yuǎn)程服務(wù)器吗货,本地文件,或者Content Provider狈网,本地資源宙搬。壓縮后的文件緩存在本地存儲中,Bitmap數(shù)據(jù)緩存在內(nèi)存中.功能性 Fresco > Glide > Picasso
包大小 Fresco > Glide > Picasso
主要功能:
共有的功能:根據(jù)content生命周期進(jìn)行圖片加載或暫停和恢復(fù)拓哺,緩存圖片到本地勇垛。
加載圖片格式及大小:
- Picasso:下載全尺寸圖片,load全尺寸圖片到imageview上士鸥,圖片使用ARGB-8888格式闲孤。
- Glide:包含Picasso功能,默認(rèn)下載不同圖片至本地烤礁,load 對應(yīng)imageview尺寸的圖片讼积,圖片使用ARGB-565格式。
可加載gif脚仔、縮略圖勤众、視頻靜態(tài)圖片、轉(zhuǎn)換字節(jié)數(shù)組鲤脏、顯示動畫们颜。 - Fresco:結(jié)合Picasso、Glide優(yōu)點猎醇,更適用于加載大量圖片窥突。另支持漸進(jìn)式顯示圖片、WebP格式圖片硫嘶。
對圖片轉(zhuǎn)換
- Picasso: picasso-transformations,:結(jié)合picasso,支持將圖片轉(zhuǎn)換為其他形狀后顯示阻问。
- Glide: glide-transformations:結(jié)合glide,支持將圖片轉(zhuǎn)換為其他形狀后顯示。
- Fresco: android-gpuimage:支持將圖片變幻為各種濾鏡效果沦疾。
總結(jié):
- Picasso所能實現(xiàn)的功能称近,Glide都能做贡蓖,無非是所需的設(shè)置不同。但是Picasso體積比起Glide小太多,如果項目中網(wǎng)絡(luò)請求本身用的就是okhttp或者retrofit(本質(zhì)還是okhttp)煌茬,那么建議用Picasso斥铺,體積會小很多(Square全家桶的干活)。
- Glide的好處是大型的圖片流坛善,比如gif晾蜘、Video,如果你們是做美拍眠屎、愛拍這種視頻類應(yīng)用剔交,建議使用。
- Fresco在5.0以下的內(nèi)存優(yōu)化非常好改衩,代價就是體積也非常的大岖常,按體積算Fresco>Glide>Picasso不過在使用起來也有些不便(小建議:它只能用內(nèi)置的一個ImageView來實現(xiàn)這些功能,用起來比較麻煩葫督,我們通常是根據(jù)Fresco自己改改竭鞍,直接使用他的Bitmap層).
該如何選擇圖片加載庫?
如果你手中的項目比較老舊橄镜,而且代碼量較大偎快,你又沒什么時間去大改,那么繼續(xù)維持當(dāng)前的選擇是比較穩(wěn)妥的辦法洽胶。如果是新上馬的項目晒夹,那么UIL由于不再維護(hù)、Picasso基本被Glide全方位超越姊氓,我推薦使用Glide或Fresco丐怯。如果你的App里,圖片特別多翔横,而且都是很大读跷、質(zhì)量很高的圖 片,而且你不太在乎App的體積(可能性不大)棕孙,那么Fresco就是很好的選擇了舔亭,而Glide相比較Fresco,Glide要輕量一些,而且是Google官方推薦蟀俊,所以在多數(shù)時候,會是開發(fā)者的首選订雾。話說回來肢预,如果你對這些圖庫都不滿意,那可以自己寫一個,如果可以的話!!