這么久以來雖然經常用到一些圖庫,但是自己從來沒有真正整理過我們使用過的這些東西有什么不同點,我們為什么要選擇這個圖庫作為項目的加載圖片的框架(由于本人資歷尚淺,經驗也不豐富,所以從沒有試過自己做一個圖片加載庫),今天突發奇想,也算是讓自己思路更清晰一點,來寫一些關于這些圖庫的異同之處.
作為一個應用豐富的App,圖片加載是必不可少的,甚至對于圖片的質量和數量都是要求很高的,然而對于圖片的加載是一個耗時操作,在UI線程為了避免ANR異常不能做耗時操作,是一個Android開發人員的基本常識,所以異步加載圖片是必然的,這就讓我們面臨了一個怎么選擇圖片加載框架的問題?市面上比較成熟的圖庫有: Universal-Image-Loader(UIL)、Picasso、Glide、Fresco。其實任何一個圖片加載框架都可以當做一個普通的下載文件流程,一般都包含這么幾個步驟:初始化配置->構造請求->執行請求->處理請求結果。
1.Universal-Image-Loader(UIL)
這是一個很早就出現的圖片加載框架了,作者是nostra13,UIL使用很方便,而且自帶多種緩存策略,如最大尺寸先刪除、時間最久刪除 等,使用它,基本上不需要考慮太多的問題。另外,UIL還支持圖片下載進度的監聽,如果你有特殊需求,則可以在 圖片開始下載前、剛開始下載等各個時間段來做一些額外的事情,非常方便。而且UIL可以在View滾動的過程中暫停圖片的加載,有利于提升界面的流暢度。但由于作者在兩年前宣布不再維護這個項目了,也就是說這個項目將不再更新,所以如果你將開發一個新項目,本人不推薦你使用此框架,因為市面上還有其它跟它一樣強大甚至更好的圖庫可以使用,但如果你現在的項目是一個老牌項目,那也沒必要著急更換它,因為它還是很強大的,沒有到跟不上時代的地步,到了真需要更換的時候再換也來得及
2.Picasso
這是一個來自于開源界名氣很大的Square公司開發的,總體來看,它屬于輕量級別的圖片加載庫,但它也有著一些自己的特色。比如,很特別的擁有統計功能,可以知道使用了多少內存、緩存命中如何,另外它本身沒有什么緩存策略,而是依賴所用的網絡庫的緩存策略——其實就是依賴了OkHttp。Picasso使用起來也是比較簡單的,不過對于新項目來說,也不是很推薦,原因就在于,Glide比它更優秀,而且使用起來幾乎 是一樣的……
緩存路徑:
這里講一下關于Picasso緩存路徑:
data/data/your package name/cache/picasso-cache/(默認路徑)
有時候根據需求,我們需要更改其緩存路徑,這里我們分析一下啊,Picasso 底層其實是使用OkHttp去下載圖片,同時在設置Picasso的時候,有一個.downloader(Downloader downloader)方法,我們可以傳遞進去一個OkHttpDownloader( OkHttpClient client).
Picasso picasso = new Picasso.Builder(Context)
.downloader(new OkHttpDownloader(client))
.build();
看到這里我們應該想到,如果給OkHttpClient設置Cache是不是就可以改變緩存路徑呢?只需要給OkHttpClient設置.cache(new Cache(file, maxSize))就可以實現修改緩存路徑了。代碼就是:
File file = new File("緩存文件所在路徑");
if (!file.exists()) {
file.mkdirs();
}
long maxSize = Runtime.getRuntime().maxMemory() / 8; //設置圖片緩存大小為運行時緩存的八分之一
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(file, maxSize))
.build();
Picasso picasso = new Picasso.Builder(this)
.downloader(new OkHttpDownloader(client))
.build();
這里需要注意的就是:當把OkHttp升級到OkHttp3時,給downloader設置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();
關于Picasso實例化對象(兩種方式)
- Picasso.with(context)
此方法提供默認方式,生成單例的Picasso對象. - new Picasso.Builder(context).build()
此方式提供自定義線程池、緩存、下載器等方法.
關于Picasso源碼簡單分析
- Picasso.with(context),在源碼中我們很容易就可以看出這是在構造一個單例的對象,而且是通過new Builder(context).build()建造者模式構建.
1.通過Builder(context)往下看就會發現 this.context = context.getApplicationContext();得到的是全局的上下文,這是為了讓Picasso下載器同步應用的生命周期的,然后我們的重點就可以放在build()上了.
2.在build()方法里面我們會發現有六個方法:
(源代碼)
(第一個方法) if (downloader == null) {
downloader = Utils.createDefaultDownloader(context); //創建一個默認的下載器.
1.其中Downloader是一個用于從網絡上加載圖片的接口,需要實現load和shutdown方法。load用于加載圖片,shutdown用于關閉一些操作.
2.Picasso的線程池是經過優化過的,可以根據當前設備網絡狀況設置其ThreadCount。
在網絡良好的條件下,線程池持有較多線程,保證下載速度夠快。在網絡較差的條件下(2G網絡等),線程池減少持有線程,保證帶寬不會被多個連接阻塞。
}
(第二個方法) if (cache == null) {
cache = new LruCache(context); //初始化緩存,創建內存緩存
}
(第三個方法) if (service == null) {
service = new PicassoExecutorService(); //初始化線程池
1.默認啟動了3個核心線程,采用了PriorityBlockingQueue優先級阻塞隊列,也就是說Picasso支持優先級調度.(對網絡狀態進行線程的優化)
}
(第四個方法) if (transformer == null) {
transformer = RequestTransformer.IDENTITY; // 初始化轉換器,請求的前置處理,在請求發出去之前執行,類似于攔截器
1.默認RequestTransformer.IDENTITY表示不作處理.
}
(第五個方法) Stats stats = new Stats(cache); //狀態控制類,統計一些狀態信息,用來發送各種消息,例如查找圖片緩存的命中率,下載是否完成等
(第六個方法) Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); //最后創建調度器,用來分發任務
1.從build方法中可以看出,大多數參數直接傳進了這個類的構造方法中,可見這個類不容小覷。
Dispatcher主要是來調度任務的,比如提交任務,取消任務,暫停加載,恢復加載,重試,加載完成,監聽網絡等等。
同樣,里面也用了一個HandlerThread和Handler來分發任務。通過一系列的dispatchXXX,由Handler發送消息,Handler接收消息后,通過performXXX來調度任務。
- 關于Picasso中任務調度器Dispatcher的簡單分析:
翻看源碼我們會看到其Dispatcher類的構造方法有六個參數,這里主要分析其中兩個,一個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: { //恢復請求
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;
}
}
}
第二,關于ExecutorService
if (service instanceof PicassoExecutorService) {
((PicassoExecutorService) service).adjustThreadCount(info);
}
//在adjustThreadCount(info)方法里面就是下面這段代碼,很明可以看出,這段代碼是根據網絡狀態信息info來決定線程池個數的,默認是3條線程
if (info == null || !info.isConnectedOrConnecting()) {
setThreadCount(DEFAULT_THREAD_COUNT);
return;
}
switch (info.getType()) {
case ConnectivityManager.TYPE_WIFI: //wife狀態下
case ConnectivityManager.TYPE_WIMAX: //802·16無線城域網,類似于wife
case ConnectivityManager.TYPE_ETHERNET: //以太網數據連接
setThreadCount(4);
break;
case ConnectivityManager.TYPE_MOBILE:
switch (info.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_LTE: // 4G狀態下
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_EHRPD:
setThreadCount(3);
break;
case TelephonyManager.NETWORK_TYPE_UMTS: // 3G狀態下
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狀態下
case TelephonyManager.NETWORK_TYPE_EDGE:
setThreadCount(1);
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT);
}
break;
default:
setThreadCount(DEFAULT_THREAD_COUNT); //默認狀態下是3條
- 接下來我們講一下Picasso中的load("image src url")方法,源碼中load()方法有四個
(源代碼如下)
//通過uri參數獲得RequestCreator對象
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
//通過請求路徑path,獲取其uri參數獲得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參數,從而獲取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是用來配置加載參數的。RequestCreator有兩個功能
- 配置加載參數。
包括placeHolder與error圖片,加載圖片的大小、旋轉、居中等屬性。 - 執行加載。
通過調用into(object)方法進行加載。
- 說完load()方法,接下來得說說into()方法了
into()可以說是Picasso中比較復雜的方法,方法有五個,方法體也比較長,代碼我這里就不貼了,大家可以自行翻看,這個方法在RequestCreator里面
通過源碼分析,其邏輯還是比較清晰的,這里總結一下:
- into會檢查當前是否是在主線程上執行。
long started = System.nanoTime();
checkMain();
- 如果我們沒有提供一個圖片資源并且有設置placeholder,那么就會把我們設置的placeholder顯示出來,并中斷執行。(下面的代碼)
Drawable drawable =
placeholderResId != 0 ? picasso.context.getResources().getDrawable(placeholderResId)
: placeholderDrawable;
if (!data.hasImage()) {
picasso.cancelRequest(target);
target.onPrepareLoad(drawable);
return;
}
- defered屬性我們一般情況下不需要關注,只有當我們調用了RequestCreator的fit方法時defered才為true,但我們幾乎不會這樣做。
if (deferred) {
throw new IllegalStateException("Fit cannot be used with get.");
}
- 接下來就是創建了一個Request對象,我們在前面做得一些設置都會被封裝到這個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的原生應用中都采用了Glide來加載圖片。其實Glide與Picasso的使用姿勢是驚人的相似的
Picasso.with(context).load("image src url").into(ImageView);
Glide.with(context).load("image src url").into(ImageView);(當然這只是它們常用的)
從某種程度上說,Glide可以看作是Picasso的增強版,所以它有著自己獨特的優勢,Glide不僅支持常見的jpg和png格式,還能顯示gif動畫,甚至是視頻,或者說它已經不僅僅是一個普通的圖片加載庫了,而是一個多媒體庫。另外一個優勢是,Glide在內存方面的表現相當出色,首先它的圖片默認格式是RGB565,要比Picasso默認的ARGB8888節省更多內存,而且它緩存的不是原始圖片,而是緩存了圖片的實際大小——比如加載的圖片是 19201080的大小,而在你的App中,顯示該圖片的ImageView控件大小只有1280720,那么Glide就會很聰明的自動緩存 1280*720大小的圖片。
關于Glide的源碼簡單解析
在Glide中我們經常用的一種姿勢就是:
Glide.with(context).load("image src url").into(ImageView);
那么這里解析也是從這段代碼開始
1.關于with(context)方法
在源碼中這個方法是靜態的,重載方法有五個,
//第一個方法傳入一個上下文,根據上下文的所屬生命周期來確定需要獲取對象的生命周期
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的靜態get()方法得到一個RequestManagerRetriever對象,其實這個靜態get()方法就是一個單例的具體實現,然后再調用RequestManagerRetriever的實例get()方法,去獲取RequestManager對象。這里需要注意的是實例get()方法中傳入的參數類型,不同的參數類型對應不同的生命周期,下面代碼就是具體的實現
(源代碼)
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;
}
//第一種,傳入全局的上下文,根據不同類型的上下文執行不同的方法
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,根據情況的不同調用的方法也不同,這里需要注意一下if (Util.isOnBackgroundThread())這種情形表示在子線程執行Glide加載圖片,最終會執行最上面那個方法
public RequestManager get(FragmentActivity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm);
}
}
//第三種,傳入一個Fragment,也是根據情況的不同調用的方法也不同,注意 if (Util.isOnBackgroundThread())這種情形表示在子線程執行Glide加載圖片,最終也會執行最上面的方法
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參數,Activity參數,Fragment參數等等,實際上只有兩種情況而已,即傳入Application類型的參數,和傳入非Application類型的參數。
- 先看傳入Application參數的情況。如果在Glide.with()方法中傳入的是一個Application對象,那么這里就會調用帶有Context參數的get()方法重載,然后會調用getApplicationManager()方法來獲取一個RequestManager對象。Application對象的生命周期即應用程序的生命周期,因此Glide并不需要做什么特殊的處理,它自動就是和應用程序的生命周期是同步的,如果應用程序關閉的話,Glide的加載也會同時終止。
- 再看傳入非Application參數的情況。不管你在Glide.with()方法中傳入的是Activity、FragmentActivity、v4包下的Fragment、還是app包下的Fragment,最終的流程都是一樣的,那就是會向當前的Activity當中添加一個隱藏的Fragment。具體添加的邏輯是在上述代碼都有注釋說明,分別對應的app包和v4包下的兩種Fragment的情況。那么這里為什么要添加一個隱藏的Fragment呢?因為Glide需要知道加載的生命周期。比如說:如果你在某個Activity上正在加載著一張圖片,結果圖片還沒加載出來,Activity就被用戶關掉了,但是如果圖片請求還在繼續,當請求的數據回來之后沒有界面可以進行渲染,這就會造成內存泄漏,所以這種情況下肯定是需要取消Glide的網絡的請求的。可是Glide并沒有辦法知道Activity的生命周期,于是Glide就使用了添加隱藏Fragment的技巧,因為Fragment的生命周期和Activity是同步的,如果Activity被銷毀了,Fragment是可以監聽到的,這樣Glide就可以捕獲這個事件并停止網絡請求了。這里需要注意的一點就是:如果我們是在非主線程當中使用的Glide,那么不管你是傳入的Activity還是Fragment,都會被強制當成Application來處理。
總體來說,第一個with()方法其實就是為了得到一個RequestManager對象而已,然后Glide會根據我們傳入with()方法的參數來確定圖片加載的生命周期,接下來我們就分析一下load("image src url")這個方法
2.關于load("image src url")方法
通過上面的with(context)方法返回的都是RequestManager對象,那么load()方法肯定在RequestManager這個類里面,我們知道Glide是支持圖片URL字符串、圖片本地路徑等等加載形式的,load重載的方法有很多,我們常用的一般都是load(String string),關于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類的代碼是非常多的,關于load(String string)簡化之后比較重要的方法就只剩下上述代碼中的這三個方法。
先來看load()方法,這個方法中的邏輯是非常簡單的,只有一行代碼,就是先調用了fromString()方法,再調用load()方法,然后把傳入的圖片URL地址傳進去。而fromString()方法也極為簡單,就是調用了loadGeneric()方法,并且指定參數為String.class.
執行loadGeneric()方法時,分別調用了Glide.buildStreamModelLoader()方法和Glide.buildFileDescriptorModelLoader()方法來獲得ModelLoader對象。ModelLoader對象是用于加載圖片的,而我們給load()方法傳入不同類型的參數,這里也會得到不同的ModelLoader對象。由于我們剛才傳入的參數是String.class,因此最終得到的是StreamStringLoader對象,它是實現了ModelLoader接口的。
最后可以看到,loadGeneric()方法是要返回一個DrawableTypeRequest對象的,因此在loadGeneric()方法的最后又new了一個DrawableTypeRequest對象,然后把剛才獲得的ModelLoader對象(StreamStringLoader對象),還有其他的一些參數傳進去。
這里就可以看到如果得到一個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));
}
這里就可以看到我們經常使用的 asBitmap() 和 asGif(),這兩個方法分別是用于強制指定加載靜態圖片和動態圖片,而從源碼中可以看出,它們分別又創建了一個BitmapTypeRequest和GifTypeRequest,如果沒有進行強制指定的話,那默認就是使用DrawableTypeRequest。
上面的fromString()方法會返回一個DrawableTypeRequest對象,接下來會調用這個對象的load()方法,把圖片的URL地址傳進去。通過對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絕大多數的API了。通過源碼,我們可以看到load()和into()這兩個方法了,所以我們總結一下:最終load()方法返回的其實就是一個DrawableTypeRequest對象。那么接下來分析into()方法中的邏輯。
3.關于into()方法的分析
從上面的代碼我們可以看到DrawableRequestBuilder類里面的into()方法只有
return super.into(view);
這說明into()真正的實現邏輯是在DrawableRequestBuilder的父類GenericRequestBuilder,所以into()方法需要在這個類進行分析
/**
* 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)方法里面最后一行代碼先是調用glide.buildImageViewTarget()方法,這個方法會構建出一個Target對象,Target對象則是用來最終展示圖片用的,如果我們跟進去的話會看到如下代碼:
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
}
這里可以看到會通過imageViewTargetFactory調用buildTarget(imageView, transcodedClass)這個方法,如果繼續查看源碼,會發現在buildTarget()方法中會根據傳入的class參數來構建不同的Target對象。這個class參數其實基本上只有兩種情況,如果你在使用Glide加載圖片的時候調用了asBitmap()方法,那么這里就會構建出BitmapImageViewTarget對象,否則的話構建的都是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); //調用buildRequest()方法構建出了一個Request對象
target.setRequest(request);
lifecycle.addListener(target);
requestTracker.runRequest(request); //執行Request對象
return target;
}
上面代碼構建出來的Request對象是用來發出加載圖片請求的,它是Glide中非常關鍵的一個組件.這就是我們經常用的with(),load(),into()最表面的意思,里面還有很多很多內容沒有寫出來,我也是參考郭霖大神的文章以及自己的拙見才寫這么點東西的,也算是做了一次筆記吧!
關于Glide優化
- 配置使用Volley和OkHttp來加載圖片
Volley和OkHttp是項目中使用最廣泛的兩個網絡庫,也是兩個相對來說速度比較快的,Glide默認使用的是HttpUrlConnection的方式請求網絡,其效率是比較低的,可以使用Volley或者OkHttp(和項目使用的網絡請求庫一致)作為Glide的網絡請求方式.
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'
}
當在Library庫中使用aar的時候,Library中的GlideModule會自動合并 到主項目中mainfest文件中,當使用jar包導入時,需要手動去合并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 或者其它系統工具來構建的話,是不支持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網絡請求方式,可以自行查看相關文檔.
4.Fresco
這個號稱是Android平臺上目前最為強大的圖片加載庫,由Facebook公司開發。與Glide一樣,Fresco也是支持gif動畫顯示,而且在內存方面的表現更加優秀。由于將圖片放在Ashmem(匿名共享內存)中,大大降低了App的內存占用(因為Ashmem沒有被統計到App的內存使用里),再加上各種級別優化,使得Fresco基本上告別了OOM,而且Fresco的圖片直接顯示為ARGB8888這種最高質量級別,即使是在這種高質量的情況下依然保證了比其他庫更少的內存占用,這就是Fresco最吸引人的地方。而且類似于進度監聽、緩存策略等,也是非常完善的,總之作為一個圖片加載庫,Fresco在功能和性能方面已經趨于完美了。
Picasso,Glide,Fresco的區別
Picasso :和Square的網絡庫一起能發揮最大作用,因為Picasso可以選擇將網絡請求的緩存部分交給了okhttp實現。使用4.0+系統上的HTTP緩存來代替磁盤緩存.
Picasso 底層是使用OkHttp去下載圖片,所以Picasso底層網絡協議為Http.Glide:模仿了Picasso的API,而且在它的基礎上加了很多的擴展(比如gif等支持),Glide默認的Bitmap格式是RGB_565,比Picasso默認的ARGB_8888格式的內存開銷要小一半;Picasso緩存的是全尺寸的(只緩存一種),而Glide緩存的是跟ImageView尺寸相同的(即5656和128128是兩個緩存) 。
Glide 底層默認使用的是HttpUrlConnection的方式請求網絡,所以Glide的底層網絡協議也為Http.-
Fresco:最大的優勢在于5.0以下(最低2.3)的bitmap加載。在5.0以下系統,Fresco將圖片放到一個特別的內存區域(Ashmem區,這個區域沒有被統計到App的內存使用里)。當然,在圖片不顯示的時候,占用的內存會自動被釋放。這會使得APP更加流暢,減少因圖片內存占用而引發的OOM。為什么說是5.0以下,因為在5.0以后系統默認就是存儲在Ashmem區了。
Image pipeline 默認使用HttpURLConnection。應用可以根據自己需求使用不同的網絡庫。Fresco的Image Pipeline負責圖片的獲取和管理。圖片可以來自遠程服務器,本地文件,或者Content Provider,本地資源。壓縮后的文件緩存在本地存儲中,Bitmap數據緩存在內存中.功能性 Fresco > Glide > Picasso
包大小 Fresco > Glide > Picasso
主要功能:
共有的功能:根據content生命周期進行圖片加載或暫停和恢復,緩存圖片到本地。
加載圖片格式及大小:
- Picasso:下載全尺寸圖片,load全尺寸圖片到imageview上,圖片使用ARGB-8888格式。
- Glide:包含Picasso功能,默認下載不同圖片至本地,load 對應imageview尺寸的圖片,圖片使用ARGB-565格式。
可加載gif、縮略圖、視頻靜態圖片、轉換字節數組、顯示動畫。 - Fresco:結合Picasso、Glide優點,更適用于加載大量圖片。另支持漸進式顯示圖片、WebP格式圖片。
對圖片轉換
- Picasso: picasso-transformations,:結合picasso,支持將圖片轉換為其他形狀后顯示。
- Glide: glide-transformations:結合glide,支持將圖片轉換為其他形狀后顯示。
- Fresco: android-gpuimage:支持將圖片變幻為各種濾鏡效果。
總結:
- Picasso所能實現的功能,Glide都能做,無非是所需的設置不同。但是Picasso體積比起Glide小太多,如果項目中網絡請求本身用的就是okhttp或者retrofit(本質還是okhttp),那么建議用Picasso,體積會小很多(Square全家桶的干活)。
- Glide的好處是大型的圖片流,比如gif、Video,如果你們是做美拍、愛拍這種視頻類應用,建議使用。
- Fresco在5.0以下的內存優化非常好,代價就是體積也非常的大,按體積算Fresco>Glide>Picasso不過在使用起來也有些不便(小建議:它只能用內置的一個ImageView來實現這些功能,用起來比較麻煩,我們通常是根據Fresco自己改改,直接使用他的Bitmap層).
該如何選擇圖片加載庫?
如果你手中的項目比較老舊,而且代碼量較大,你又沒什么時間去大改,那么繼續維持當前的選擇是比較穩妥的辦法。如果是新上馬的項目,那么UIL由于不再維護、Picasso基本被Glide全方位超越,我推薦使用Glide或Fresco。如果你的App里,圖片特別多,而且都是很大、質量很高的圖 片,而且你不太在乎App的體積(可能性不大),那么Fresco就是很好的選擇了,而Glide相比較Fresco,Glide要輕量一些,而且是Google官方推薦,所以在多數時候,會是開發者的首選。話說回來,如果你對這些圖庫都不滿意,那可以自己寫一個,如果可以的話!!