picasso 是一個(gè)強(qiáng)大的圖片加載緩存框架
1.首先看下picasso 如何使用:
Picasso.with(this)
.load("url")
.placeholder(R.drawable.leak_canary_icon)
.error(R.drawable.leak_canary_icon)
.resize(480,800)
.centerCrop()
.rotate(360)
.priority(Picasso.Priority.HIGH)
.tag("picasso listview")
.memoryPolicy(MemoryPolicy.NO_CACHE)
.networkPolicy(NetworkPolicy.NO_CACHE)
.into(imageView);
Picasso和Glide相似都是使用鏈?zhǔn)秸{(diào)用 我們可以把Picasso類視為一個(gè)管理類,管理圖片的加載,轉(zhuǎn)換,緩存的工作.
.with()獲取Picasso的單例,參數(shù)是上下文.
.load()方法完成加載圖片的方法.參數(shù)是url或file路徑
.resize()方法 參數(shù)單位像素 因?yàn)樵陧?xiàng)目中要考慮網(wǎng)絡(luò)帶寬,手機(jī)的內(nèi)存使用,下載速度等情況.結(jié)合考慮.這時(shí)候有一種場景服務(wù)器給我們的圖片的寬和高與我們實(shí)際的imageview的寬和高是不一致的.服務(wù)器給我們一些奇奇怪怪的圖片,這時(shí)我們就要調(diào)用resize方法.對圖片的寬高進(jìn)行設(shè)置.
.centerCrop() 將整個(gè)圖片充滿imageview邊界 裁減掉多余部分
.rotate() 對圖片加載進(jìn)行旋轉(zhuǎn)設(shè)置.旋轉(zhuǎn)點(diǎn)坐標(biāo)0.0
.priority(Picasso.Priority.HIGH) 設(shè)置圖片加載優(yōu)先級 不是100%保證 因?yàn)閜icasso內(nèi)部只是向高的優(yōu)先級靠攏.但并不會保證
.tag() Picasso 允許我們?yōu)槊總€(gè)請求設(shè)置一個(gè)tag 為什么用到tag呢 比如listview 快速滑動這樣的場景,如果不去設(shè)置響應(yīng)事件就會加載所有的圖片,浪費(fèi)性能,造成體驗(yàn)不好.UI卡頓,而我們?yōu)槊恳粋€(gè)加載事件做標(biāo)記tag,監(jiān)聽響應(yīng)事件.做出處理操作.
mlsitview.setOnScrollStateChanged(AbsListView view,int scrollState){
final Picasso picasso = Picasso.with(MainActivity.this);
if(scrollState == SCROLL_STATE_IDLE){//停止?fàn)顟B(tài)加載圖片
picasso.resumeTag("picasso listview");
}else{
picasso.pauseTag("picasso listview");
}
}
.memoryPolicy(MemoryPolicy.NO_CACHE) picasso的緩存策略 內(nèi)存緩存
.networkPolicy(NetworkPolicy.NO_CACHE)文件緩存
2.picasso源碼分析
1)with方法:內(nèi)存緩存Lrucache和線程池調(diào)度
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
.build()方法,生成Picasso對象,返回該對象實(shí)例,按照需求配置下載器,緩存,線程池,轉(zhuǎn)換器等.
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);//下載器
}
if (cache == null) {
cache = new LruCache(context);//緩存
}
if (service == null) {
service = new PicassoExecutorService();//線程池
}
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;//轉(zhuǎn)換器
}
Stats stats = new Stats(cache);
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
.build()方法.-->downloader = Utils.createDefaultDownloader(context);
-->
static Downloader createDefaultDownloader(Context context) {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");//通過反射查找是否引用了okhttp,如果引用了采用okhttp進(jìn)行網(wǎng)絡(luò)連接
return OkHttpLoaderCreator.create(context);
} catch (ClassNotFoundException ignored) {
}
return new UrlConnectionDownloader(context);//沒有引用,采用httpurlConnection進(jìn)行網(wǎng)絡(luò)請求
}
繼續(xù)看build方法中
if (cache == null) {
cache = new LruCache(context);//lruCache主要采用linkedhashmap 將圖片添加到鏈表尾部//( final LinkedHashMap<String, Bitmap> map;)
}
if (service == null) {
service = new PicassoExecutorService();
}
接著來看PicassoExecutorService
PicassoExecutorService 是一個(gè)線程并發(fā)池
PicassoExecutorService extends ThreadPoolExecutor
ThreadPoolExecutor是java并發(fā)庫中的內(nèi)容 無論是圖片庫還是網(wǎng)絡(luò)庫都離不開這個(gè)線程池
public ThreadPoolExecutor(int corePoolSize, //核心線程數(shù)目
int maximumPoolSize,//線程池中允許的最大線程數(shù)
long keepAliveTime,//線程池中線程數(shù)目比核心線程數(shù)目多的時(shí)候,超過這個(gè)時(shí)間,就會將多余的線程回收
TimeUnit unit,//時(shí)間單位
BlockingQueue<Runnable> workQueue,//阻塞隊(duì)列 線程安全隊(duì)列
ThreadFactory threadFactory,//線程工廠 產(chǎn)生線程
RejectedExecutionHandler handler) {}//當(dāng)線程池 由于線程數(shù)目和隊(duì)列導(dǎo)致任務(wù)阻塞時(shí) ,線程池的處理方式
1.如果線程池中的線程數(shù)目少于corePoolSize , 這時(shí)候線程池是會重新創(chuàng)建線程的,直到線程數(shù)目達(dá)到corePoolSize.
2.如果線程池中線程數(shù)目大于或者等于corePoolSize,但是工作隊(duì)列workQueue沒有滿, 這時(shí)新的任務(wù)還是會放進(jìn)隊(duì)列中的,按照先進(jìn)先出的原則來進(jìn)行執(zhí)行.
3.如果線程池中的線程數(shù)目大于等于corePoolSize,并且工作隊(duì)列workQueue滿了,但是總線程數(shù)目小于maximumPoolSzie 這時(shí)候還是可以直接創(chuàng)建線程 來處理添加的任務(wù)
4.如果工作隊(duì)列滿了,并且線程池中線程的數(shù)目達(dá)到了最大數(shù)目maximumPoolSize 這時(shí)就會由RejectedExecutionHandler來處理
默認(rèn)的處理方式就是 丟棄到任務(wù),同時(shí)拋出異常
繼續(xù)看
非常重要的 Dispatcher 是來管理非常重要的線程之間的切換.
dispatcher如何完成線程切換
首先我們進(jìn)入Dispatcher這個(gè)類,Dispatcher類中首先開啟線程DispatcherThread
final DispatcherThread dispatcherThread;
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();
首先DispatcherThread extends HandlerThread
static class DispatcherThread extends HandlerThread {
DispatcherThread() {
super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
}
}
所以先分析下HandlerThread
HandlerThread extends Thread 注釋中這樣寫到他會開啟一個(gè)新的線程,線程內(nèi)部會有一個(gè)Looper對象,通過這個(gè)Looper對象可以去循環(huán)遍歷我們的消息隊(duì)列.
看下run()方法
run方法的作用就是完成Looper的創(chuàng)建 通過Looper的循環(huán)方法構(gòu)造一個(gè)循環(huán)的線程.HandlerThread也要調(diào)用start方法,完成線程的運(yùn)行的.
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();//完成Looper對象的創(chuàng)建
synchronized (this) {//通過同步鎖機(jī)制鎖住當(dāng)前對象
mLooper = Looper.myLooper();//mLooper在我們的同步代碼塊中
notifyAll();//喚醒等待線程 和notifyAll()同步出現(xiàn)的就是wait()方法.
}
Process.setThreadPriority(mPriority);//設(shè)置線程優(yōu)先級
onLooperPrepared();//內(nèi)部是個(gè)空實(shí)現(xiàn) 留給我們自己去重寫
Looper.loop();//完成整個(gè)的線程消息啟動工作
mTid = -1;
}
//我們回到同步代碼塊中 我們?yōu)槭裁匆ㄟ^同步代碼塊中的方法去喚醒等待線程呢?有在哪里去wait的操作呢?
在getLooper方法中
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {//這里同樣用同步鎖機(jī)制鎖住當(dāng)前對象
while (isAlive() && mLooper == null) {//判斷線程存貨且不為空的情況下
try {
wait();//線程等待
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
繼續(xù)看Dispatcher構(gòu)造方法中的其他參數(shù)
Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
Downloader downloader, Cache cache, Stats stats) {
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();
Utils.flushStackLocalLeaks(dispatcherThread.getLooper());
this.context = context;
this.service = service;
this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
this.failedActions = new WeakHashMap<Object, Action>();
this.pausedActions = new WeakHashMap<Object, Action>();
this.pausedTags = new HashSet<Object>();
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);//dispatherThread線程內(nèi)部的handler(因?yàn)閐ispatcherThread.getLooper()參數(shù))
this.downloader = downloader;
this.mainThreadHandler = mainThreadHandler;//主線程的handler
this.cache = cache;
this.stats = stats;
this.batch = new ArrayList<BitmapHunter>(4);
this.airplaneMode = Utils.isAirplaneModeOn(this.context);
this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);
this.receiver = new NetworkBroadcastReceiver(this);//廣播接收者,主要用于監(jiān)聽網(wǎng)絡(luò)變化的操作的,這也是比較重要的
receiver.register();
}
回到picasso的build()方法中
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
HANDLER是picasso內(nèi)部定義的
static final Handler HANDLER = new Handler(Looper.getMainLooper()){//getMainLooper是主線程
}//定義為靜態(tài)內(nèi)部類,就是靜態(tài)內(nèi)部類不持有外部類的引用
3.NetworkRequestHandler處理圖片請求與回調(diào)
繼續(xù)看build()方法
build()方法最后 還是返回一個(gè)picasso對象
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
我們進(jìn)入這個(gè)方法看一下
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
this.context = context;
this.dispatcher = dispatcher;
this.cache = cache;
this.listener = listener;
this.requestTransformer = requestTransformer;
this.defaultBitmapConfig = defaultBitmapConfig;
int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
List<RequestHandler> allRequestHandlers =
new ArrayList<RequestHandler>(builtInHandlers + extraCount);
// ResourceRequestHandler needs to be the first in the list to avoid
// forcing other RequestHandlers to perform null checks on request.uri
// to cover the (request.resourceId != 0) case.
allRequestHandlers.add(new ResourceRequestHandler(context));
if (extraRequestHandlers != null) {
allRequestHandlers.addAll(extraRequestHandlers);//將extraRequestHandlers集合加入到allRequesthandlers集合中,看下
allRequestHandler這個(gè)ArrayList集合.
}
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
allRequestHandlers.add(new MediaStoreRequestHandler(context));
allRequestHandlers.add(new ContentStreamRequestHandler(context));//集合中加入處理contentprovider中圖片的handler
allRequestHandlers.add(new AssetRequestHandler(context));//處理asset中圖片
allRequestHandlers.add(new FileRequestHandler(context));//處理file中圖片
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));//處理網(wǎng)絡(luò)請求圖片的handler
requestHandlers = Collections.unmodifiableList(allRequestHandlers);
this.stats = stats;
this.targetToAction = new WeakHashMap<Object, Action>();
this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();
this.indicatorsEnabled = indicatorsEnabled;
this.loggingEnabled = loggingEnabled;
this.referenceQueue = new ReferenceQueue<Object>();
this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
this.cleanupThread.start();
}
我們主要看下處理網(wǎng)絡(luò)請求圖片的handler:NetworkRequestHandler.主要看其中的load()方法:
@Override public Result load(Request request, int networkPolicy) throws IOException {
Response response = downloader.load(request.uri, request.networkPolicy);
if (response == null) {
return null;
}
Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
Bitmap bitmap = response.getBitmap();
if (bitmap != null) {
return new Result(bitmap, loadedFrom);
}
InputStream is = response.getInputStream();
if (is == null) {
return null;
}
// Sometimes response content length is zero when requests are being replayed. Haven't found
// root cause to this but retrying the request seems safe to do so.
if (loadedFrom == DISK && response.getContentLength() == 0) {
Utils.closeQuietly(is);
throw new ContentLengthException("Received response with 0 content-length header.");
}
if (loadedFrom == NETWORK && response.getContentLength() > 0) {//請求響應(yīng)長度>0
stats.dispatchDownloadFinished(response.getContentLength());//stats派發(fā)響應(yīng)完成結(jié)果
}
return new Result(is, loadedFrom);
}
看下stats的dispatchDownloadFinished方法:
void dispatchDownloadFinished(long size) {
handler.sendMessage(handler.obtainMessage(DOWNLOAD_FINISHED, size));
}
發(fā)現(xiàn)了熟悉的sendMessage()方法
其中的handler是StatsHandler 看其中handleMessage()方法:
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case CACHE_HIT:
stats.performCacheHit();
break;
case CACHE_MISS:
stats.performCacheMiss();
break;
case BITMAP_DECODE_FINISHED:
stats.performBitmapDecoded(msg.arg1);
break;
case BITMAP_TRANSFORMED_FINISHED:
stats.performBitmapTransformed(msg.arg1);
break;
case DOWNLOAD_FINISHED:
stats.performDownloadFinished((Long) msg.obj);
break;
default:
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new AssertionError("Unhandled stats message." + msg.what);
}
});
}
}
Stats中的performDownloadFinished()方法
void performDownloadFinished(Long size) {
downloadCount++;
totalDownloadSize += size;
averageDownloadSize = getAverage(downloadCount, totalDownloadSize);
}
4.picasso源碼load方法
load 方法return load(Uri.parse(path)).
該函數(shù)有四種重載方法,其中Uri江场,String垂谢,F(xiàn)ile最終都轉(zhuǎn)化為Uri進(jìn)行請求片效,而int則是app內(nèi)部的資源訪問尽楔。
load()函數(shù)返回RequestCreator對象繁涂,
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
再看RequestCreator();
RequestCreator的成員變量里有一個(gè)重要的對象是Request.Builder(其有一個(gè)內(nèi)部類Builder)盹靴,RequestCreator里很多函數(shù)都是橋 接到該Builder炸茧。
RequestCreator(Picasso picasso, Uri uri, int resourceId) {
if (picasso.shutdown) {
throw new IllegalStateException(
"Picasso instance already shut down. Cannot submit new requests.");
}
this.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
看最后一行是創(chuàng)建了 Request.Builder對象,通過Request.Builder對象我們可以配置更多對象.
picasso源碼into方法:Action&BitmapHunter
with()方法完成了Picasso對象的創(chuàng)建,內(nèi)部通過單例的形式進(jìn)行構(gòu)建,這樣就保證了在app使用Picasso對象的時(shí)候,只會存在一個(gè)
picasso對象,同時(shí)他也是我們整個(gè)Picasso加載的入口.同時(shí)with()方法還做了一些基礎(chǔ)的配置工作.比如說Dispatcher這個(gè)類,在picasso框架中主要完成線程的切換工作,其內(nèi)部實(shí)現(xiàn)原理是handler.
load()方法主要完成了RequsetCreate()方法的創(chuàng)建.同時(shí)獲取到了我們要加載的一些資源的路徑
into()方法完成圖片的加載工作.
DeferredRequestCreator類
當(dāng)我們創(chuàng)建請求的時(shí)候,我們新建一個(gè)圖片加載請求,但是我們還不能獲取到當(dāng)imageview的寬高,這時(shí)我們會創(chuàng)建DeferredRequestCreator,通過這個(gè)類會我們的imageview的target去進(jìn)行監(jiān)聽,直到獲取到當(dāng)前imageview的寬高,這時(shí)候我們重新執(zhí)行我們的請求創(chuàng)建.所以刪除請求的時(shí)候,同時(shí)我們還應(yīng)該刪除DeferredRequestCreator對target的監(jiān)聽事件.
public void into(Target target) {
long started = System.nanoTime();
//線程檢查
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
if (deferred) {
throw new IllegalStateException("Fit cannot be used with a Target.");
}
//沒設(shè)置url以及resId則取消請求
if (!data.hasImage()) {
picasso.cancelRequest(target);//取消請求
target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);//取消后設(shè)置占位符.
return;
}
Request request = createRequest(started);
String requestKey = createKey(request);
if (shouldReadFromMemoryCache(memoryPolicy)) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
picasso.cancelRequest(target);
target.onBitmapLoaded(bitmap, MEMORY);
return;
}
}
target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);
Action action =
new TargetAction(picasso, target, request, memoryPolicy, networkPolicy, errorDrawable,
requestKey, tag, errorResId);
//提交請求,Picasso內(nèi)部維護(hù)了一個(gè)map稿静,key是imageView梭冠,value是Action
//提交時(shí)做判斷,如果當(dāng)前imageView已經(jīng)在任務(wù)隊(duì)列里了改备。判斷當(dāng)前任務(wù)與之前的任務(wù)是否相同控漠,
//如果不相同則取消之前的任務(wù)并將新的key-value加入到map
picasso.enqueueAndSubmit(action);
}
RequestCreator最重要的一個(gè)方法是into(),在into()方法調(diào)用之前所調(diào)用的一切配置函數(shù)都只是把配置信息儲 存起來绍妨,而沒有做網(wǎng)絡(luò)請求润脸,當(dāng)調(diào)用into()函數(shù)后才開始網(wǎng)絡(luò)請求。into()函數(shù)有五個(gè)重載方法他去,用于把請求結(jié)果存 放或顯示到指定位置毙驯。
submit()函數(shù):用于把RequestCreator提交上來的Action添加到隊(duì)列里,該函數(shù)其實(shí)是把提交任務(wù)交給Dispatcher
而Dispatcher最后則通過handler把任務(wù)切換到Dispatcher所在的線程(后臺線程灾测,因?yàn)橐M(jìn)行網(wǎng)絡(luò)訪問).
RequestCreator最重要的一個(gè)方法是into()爆价,在into()方法調(diào)用之前所調(diào)用的一切配置函數(shù)都只是把配置信息儲 存起來垦巴,而沒有做網(wǎng)絡(luò)請求,當(dāng)調(diào)用into()函數(shù)后才開始網(wǎng)絡(luò)請求铭段。into()函數(shù)有五個(gè)重載方法骤宣,用于把請求結(jié)果存 放或顯示到指定位置。
這里分析into到ImageView里序愚,該函數(shù)首先會查看緩存里是否有請求的Bitmap憔披,如果有那最好,都不用進(jìn)行網(wǎng)絡(luò)請求爸吮, 直接把Bitmap顯示到ImageView里芬膝。如果緩存里沒有,則會把請求加入到請求隊(duì)列里形娇,之后進(jìn)行網(wǎng)絡(luò)請求锰霜。
到這里,又把控制權(quán)交給Picasso類桐早。
底層分析:
Action 類:是一個(gè)request請求類包裝類,最終把包裝好的類,交給線程還執(zhí)行.(一個(gè)沒有Set的Bean癣缅,包含各種動作信息,如網(wǎng)絡(luò)請求策略哄酝,內(nèi)存策略友存,請求配置等。)
BitmapHuntyer 就是一個(gè)runnable,是一個(gè)開啟子線程的工具.是一個(gè)Runnable的子類陶衅,用來進(jìn)行Bitmap的獲取(網(wǎng)絡(luò)爬立,硬盤,內(nèi)存等)万哪,處理(角度,大小等)抡秆,然后執(zhí)行分發(fā)器(dispatcher)的回調(diào)處理
picasso源碼into方法:線程池&PicassoFutureTask
Stats:對請求整個(gè)過程的一個(gè)記錄奕巍,如命中(hit)緩存的次數(shù),不命中(miss)緩存的次數(shù)儒士,Bitmap下載解析次數(shù)的止, 下載完成次數(shù)等。
ImageViewAction:Action的子類着撩,內(nèi)部有complete()和error()函數(shù)诅福,用于把請求結(jié)果顯示到ImageView上
其實(shí)Picasso里用來在指定目標(biāo)上顯示結(jié)果都是通過PicassoDrawable類來實(shí)現(xiàn)的。
再次 強(qiáng)調(diào)下最重要的類
RequestHandler
//抽象類拖叙,由不同的子類來實(shí)現(xiàn)不同來源的圖片的獲取與加載氓润,比如:
//AssetRequestHandler:加載asset里的圖片
//FileRequestHandler:加載硬盤里的圖片
//ResourceRequestHandler:加載資源圖片
//NetworkRequestHandler:加載網(wǎng)絡(luò)圖片
BitmapHunter
//是一個(gè)Runnable的子類,用來進(jìn)行Bitmap的獲取(網(wǎng)絡(luò)薯鳍,硬盤咖气,內(nèi)存等),處理(角度,大小等)崩溪,
//然后執(zhí)行分發(fā)器(dispatcher)的回調(diào)處理
PicassoDrawable
//實(shí)現(xiàn)了引入圖片漸變功能和debug狀態(tài)的標(biāo)識的Drawable浅役,用來在最后bitmap轉(zhuǎn)換成PicassoDrawable
//然后設(shè)置給ImageView,根據(jù)圖片的來源可以在圖片的左上角顯示一個(gè)不同顏色的三角形色塊
MEMORY(Color.GREEN) //內(nèi)存加載
DISK(Color.BLUE) //本地加載
NETWORK(Color.RED) //網(wǎng)絡(luò)加載
DeferredRequestCreator
//ViewTreeObserver.OnPreDrawListener的實(shí)現(xiàn)類伶唯,即將繪制視圖樹時(shí)執(zhí)行的回調(diào)函數(shù)觉既。
//這時(shí)所有的視圖都測量完成并確定了框架。 客戶端可以使用該方法來調(diào)整滾動邊框乳幸,
//甚至可以在繪制之前請求新的布局瞪讼,這里用來實(shí)現(xiàn)當(dāng)開發(fā)者修改圖片尺寸時(shí)的邏輯
Action
//Action代表了一個(gè)具體的加載任務(wù),主要用于圖片加載后的結(jié)果回調(diào)反惕,有兩個(gè)抽象方法尝艘,complete和error,
//并保存了每個(gè)請求的各種信息()姿染,具體的實(shí)現(xiàn)類如下
//GetAction:同步執(zhí)行請求時(shí)使用背亥。
//FetchAction:當(dāng)不需要ImageView來安置bitmap時(shí)的異步請求,通常用來預(yù)熱緩存
//RemoteViewsAction:用來更新遠(yuǎn)程圖片(notification等)的抽象類悬赏。
//TargetAction:一般在View(不只是ImageView)或者ViewHolder中用來加載圖片,需要實(shí)現(xiàn)Target接口
//ImageViewAction:最常用的Action狡汉,主要用來給ImageView加載圖片