Picasso粗糙分析
Posted Sun_TTTT
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Picasso粗糙分析相关的知识,希望对你有一定的参考价值。
Picasso
1.在第一步可以设置BitmapConfig(图片配置),Downloader(下载器,如果网络库引用了okHttp,则使用okhttp进行下载),Executor(线程池),MemoryCache(缓存), Listener(回调),Transformer(对请求进行转换),RequestHandler(对请求进行处理),Debugging,Log,Indicator.
public static class Builder
private final Context context;
private Downloader downloader;
private ExecutorService service;
private Cache cache;
private Listener listener;
private RequestTransformer transformer;
private List<RequestHandler> requestHandlers;
private Bitmap.Config defaultBitmapConfig;
private boolean indicatorsEnabled;
private boolean loggingEnabled;
/** Start building a new @link Picasso instance. */
public Builder(@NonNull Context context)
if (context == null)
throw new IllegalArgumentException("Context must not be null.");
this.context = context.getApplicationContext();
/**
* Specify the default @link Bitmap.Config used when decoding images. This can be overridden
* on a per-request basis using @link RequestCreator#config(Bitmap.Config) config(..).
*/
public Builder defaultBitmapConfig(@NonNull Bitmap.Config bitmapConfig)
if (bitmapConfig == null)
throw new IllegalArgumentException("Bitmap config must not be null.");
this.defaultBitmapConfig = bitmapConfig;
return this;
/** Specify the @link Downloader that will be used for downloading images. */
public Builder downloader(@NonNull Downloader downloader)
if (downloader == null)
throw new IllegalArgumentException("Downloader must not be null.");
if (this.downloader != null)
throw new IllegalStateException("Downloader already set.");
this.downloader = downloader;
return this;
/**
* Specify the executor service for loading images in the background.
* <p>
* Note: Calling @link Picasso#shutdown() shutdown() will not shutdown supplied executors.
*/
public Builder executor(@NonNull ExecutorService executorService)
if (executorService == null)
throw new IllegalArgumentException("Executor service must not be null.");
if (this.service != null)
throw new IllegalStateException("Executor service already set.");
this.service = executorService;
return this;
/** Specify the memory cache used for the most recent images. */
public Builder memoryCache(@NonNull Cache memoryCache)
if (memoryCache == null)
throw new IllegalArgumentException("Memory cache must not be null.");
if (this.cache != null)
throw new IllegalStateException("Memory cache already set.");
this.cache = memoryCache;
return this;
/** Specify a listener for interesting events. */
public Builder listener(@NonNull Listener listener)
if (listener == null)
throw new IllegalArgumentException("Listener must not be null.");
if (this.listener != null)
throw new IllegalStateException("Listener already set.");
this.listener = listener;
return this;
/**
* Specify a transformer for all incoming requests.
* <p>
* <b>NOTE:</b> This is a beta feature. The API is subject to change in a backwards incompatible
* way at any time.
*/
public Builder requestTransformer(@NonNull RequestTransformer transformer)
if (transformer == null)
throw new IllegalArgumentException("Transformer must not be null.");
if (this.transformer != null)
throw new IllegalStateException("Transformer already set.");
this.transformer = transformer;
return this;
/** Register a @link RequestHandler. */
public Builder addRequestHandler(@NonNull RequestHandler requestHandler)
if (requestHandler == null)
throw new IllegalArgumentException("RequestHandler must not be null.");
if (requestHandlers == null)
requestHandlers = new ArrayList<RequestHandler>();
if (requestHandlers.contains(requestHandler))
throw new IllegalStateException("RequestHandler already registered.");
requestHandlers.add(requestHandler);
return this;
/**
* @deprecated Use @link #indicatorsEnabled(boolean) instead.
* Whether debugging is enabled or not.
*/
@Deprecated public Builder debugging(boolean debugging)
return indicatorsEnabled(debugging);
/** Toggle whether to display debug indicators on images. */
public Builder indicatorsEnabled(boolean enabled)
this.indicatorsEnabled = enabled;
return this;
/**
* Toggle whether debug logging is enabled.
* <p>
* <b>WARNING:</b> Enabling this will result in excessive object allocation. This should be only
* be used for debugging purposes. Do NOT pass @code BuildConfig.DEBUG.
*/
public Builder loggingEnabled(boolean enabled)
this.loggingEnabled = enabled;
return this;
/** Create the @link Picasso instance. */
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;
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、cache、executor、transformer。并且在其之中还调用了一个调度器dispatcher,这个接着就会提到他。
2.调用load()方法生成RequestCreator,RequestCteator包含了对本次请求的处理,包括对图片的处理、设置占位图、网络策略、缓存策略等。
load()可加载Uri、Path、File、ResourceId等图片资源,除了ResourceId外,其余最后都会调用Uri的方法
/**
* Start an image request using the specified URI.
* <p>
* Passing @code null as a @code uri will not trigger any request but will set a placeholder,
* if one is specified.
*
* @see #load(File)
* @see #load(String)
* @see #load(int)
*/
public RequestCreator load(@Nullable Uri uri)
return new RequestCreator(this, uri, 0);
我们可以把RequestCreator视为对这个请求的一系列操作,它能够设置占位图、加载失败图、对本次请求添加tag(以便对本次请求进行暂停、恢复、甚至取消操作)、fit(按照ImageView的大小设置图片,所以仅能当Target为ImageView时才能进行)、unfit、resize(设置图片大小,与fit冲突,不可同时设置)、centerCrop、centerInside、onlyScaleDown(只有当原图的尺寸比target大时才进行resize)、rotate、config(再解码图片时使用自己的配置)、stableKey(取代地址或者是Uri当缓存的key,这个可以避免重名)、priority(可以对request的级别进行提升以便尽快执行,但是这个并不完全保证能够按照级别进行执行)、transform(自定义一个对图片的处理)、memoryPolicy(缓存策略)、networkPolicy(网络请求策略)、purgeable(在decode 图片的时候开启 inPurgeable 和 inInputShareable)、noFade、get(同步获取图片并且不能缓存在内存中,不可再main thread使用)、fetch(预先下载图片但并不需要填充到控件,起到预热缓存的作用)、into(需要在主线程调用)
/**
* Asynchronously fulfills the request into the specified @link Target. In most cases, you
* should use this when you are dealing with a custom @link android.view.View View or view
* holder which should implement the @link Target interface.
* <p>
* Implementing on a @link android.view.View View:
* public class ProfileView extends FrameLayout implements Target
* @literal @Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from)
* setBackgroundDrawable(new BitmapDrawable(bitmap));
*
*
* @literal @Override public void onBitmapFailed()
setBackgroundResource(R.drawable.profile_error);
*
* @literal @Override public void onPrepareLoad(Drawable placeHolderDrawable)
* frame.setBackgroundDrawable(placeHolderDrawable);
*
*
* Implementing on a view holder object for use inside of an adapter:
* public class ViewHolder implements Target
* public FrameLayout frame;
* public TextView name;
* @literal @Override public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from)
* frame.setBackgroundDrawable(new BitmapDrawable(bitmap));
*
* @literal @Override public void onBitmapFailed()
* frame.setBackgroundResource(R.drawable.profile_error);
*
*
* @literal @Override public void onPrepareLoad(Drawable placeHolderDrawable)
* frame.setBackgroundDrawable(placeHolderDrawable);
*
*
* </pre></blockquote>
* <p>
* <em>Note:</em> This method keeps a weak reference to the @link Target instance and will be
* garbage collected if you do not keep a strong reference to it. To receive callbacks when an
* image is loaded use @link #into(android.widget.ImageView, Callback).
*/
public void into(@NonNull 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.");
if (!data.hasImage())
picasso.cancelRequest(target);
target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);
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.enqueueAndSubmit(action);
在into这个方法中,显示判断一下是否在主线程,在判断是否设置图片资源,再看看是否调用了fit()方法设置脱险大小,如果设置了fit 则不能再使用热死则方法调整大小。在检查一下内存缓存看是否有匹配,成功则设置图片,不成功则设置占位图,在组织action,调用picasso的enqueueAndSubmit执行这个action。
void enqueueAndSubmit(Action action)
Object target = action.getTarget();
if (target != null && targetToAction.get(target) != action)
// This will also check we are on the main thread.
cancelExistingRequest(target);
targetToAction.put(target, action);
submit(action);
在picasso里面维护了一个即将执行的map,先会判断一下这个action是否之前添加过,如果添加过,则将之前添加的取消,再将这个Action添加进这个map,然后调用submit方法执行action。
void submit(Action action)
dispatcher.dispatchSubmit(action);
在dispatcher中通过handler最终调用了performsubmit方法
void performSubmit(Action action, boolean dismissFailed)
if (pausedTags.contains(action.getTag()))
pausedActions.put(action.getTarget(), action);
if (action.getPicasso().loggingEnabled)
log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
return;
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null)
hunter.attach(action);
return;
if (service.isShutdown())
if (action.getPicasso().loggingEnabled)
log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
return;
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
if (dismissFailed)
failedActions.remove(action.getTarget());
if (action.getPicasso().loggingEnabled)
log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
```
在这个方法中,先判断了这个action是否在暂停列表中,在判断huntermap是否有对应的BitmapHunter,这个BitmapHunter是继承了Runnable接口、执行下载并进行图片转换的地方。然后添加进线程池执行,我们看一下BitmapHunter的run方法
@Override public void run()
try
updateThreadName(data);
if (picasso.loggingEnabled)
log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
result = hunt();
if (result == null)
dispatcher.dispatchFailed(this);
else
dispatcher.dispatchComplete(this);
catch (Downloader.ResponseException e)
if (!e.localCacheOnly || e.responseCode != 504)
exception = e;
dispatcher.dispatchFailed(this);
catch (NetworkRequestHandler.ContentLengthException e)
exception = e;
dispatcher.dispatchRetry(this);
catch (IOException e)
exception = e;
dispatcher.dispatchRetry(this);
catch (OutOfMemoryError e)
StringWriter writer = new StringWriter();
stats.createSnapshot().dump(new PrintWriter(writer));
exception = new RuntimeException(writer.toString(), e);
dispatcher.dispatchFailed(this);
catch (Exception e)
exception = e;
dispatcher.dispatchFailed(this);
finally
Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
整个run方法中只有一句关键的就是hunt()方法,看一下
Bitmap hunt() throws IOException
Bitmap bitmap = null;
if (shouldReadFromMemoryCache(memoryPolicy))
bitmap = cache.get(key);
if (bitmap != null)
stats.dispatchCacheHit();
loadedFrom = MEMORY;
if (picasso.loggingEnabled)
log(OWNER_HUNTER, VERB_DECODED, data.logId(), “from cache”);
return bitmap;
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null)
loadedFrom = result.getLoadedFrom();
exifOrientation = result.getExifOrientation();
bitmap = result.getBitmap();
// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null)
InputStream is = result.getStream();
try
bitmap = decodeStream(is, data);
finally
Utils.closeQuietly(is);
if (bitmap != null)
if (picasso.loggingEnabled)
log(OWNER_HUNTER, VERB_DECODED, data.logId());
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifOrientation != 0)
synchronized (DECODE_LOCK)
if (data.needsMatrixTransform() || exifOrientation != 0)
bitmap = transformResult(data, bitmap, exifOrientation);
if (picasso.loggingEnabled)
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
if (data.hasCustomTransformations())
bitmap = applyCustomTransformations(data.transformations, bitmap);
if (picasso.loggingEnabled)
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
if (bitmap != null)
stats.dispatchBitmapTransformed(bitmap);
return bitmap;
在requestHandler.load(data, networkPolicy)这个方法中,RequestHandler通过Downloader下载了图片,然后对bitmap的处理过程添加了线程锁,大概是为了避免同时处理图片出现问题。
在回到run方法当我们下载成功后,在用dispatcher通知dispatchComplete
然后通过handler不断调用,最终调用performComplete方法之中
void performComplete(BitmapHunter hunter)
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy()))
cache.set(hunter.getKey(), hunter.getResult());
hunterMap.remove(hunter.getKey());
batch(hunter);
if (hunter.getPicasso().loggingEnabled)
log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), “for completion”);
“`
在这个方法中,添加进缓存,再将hunter移出huntermap
在调用batch方法,将hunter添加进列表里。
这个batch的作用是将200ms内完成的请求一次性的交给主线程去执行。
batch最终会调用mainHandlerde complete方法,complete将图片设置给target,调用listener 完成。
在dispatcher中完成了各项请求从mainThread到dispaTcherthread再到downloadThread的跳转。
下面是几个有意思的点
1.picasso只提供了内存缓存,并没有提供硬盘缓存,那为什么说有两级缓存呢,因为picasso将硬盘缓存使用了下载器的硬盘缓存。并且如果项目中引进了okhttp,就会优先使用okhttp,我们看一下picasso的build方法中,默认实现了一个downloader
downloader = Utils.createDefaultDownloader(context);
在Utils中
“` static Downloader createDefaultDownloader(Context context)
if (SDK_INT >= GINGERBREAD)
try
Class.forName(“okhttp3.OkHttpClient”);
return OkHttp3DownloaderCreator.create(context);
catch (ClassNotFoundException ignored)
try
Class.forName(“com.squareup.okhttp.OkHttpClient”);
return OkHttpDownloaderCreator.create(context);
catch (ClassNotFoundException ignored)
return new UrlConnectionDownloader(context);
2.在picasso 的build方法中,线程池的建立
```if (service == null)
service = new PicassoExecutorService();
在这个PicassoExecutorService中
``` void adjustThreadCount(NetworkInfo info)
if (info == null || !info.isConnectedOrConnecting())
setThreadCount(DEFAULT_THREAD_COUNT);
return;
switch (info.getType())
case ConnectivityManager.TYPE_WIFI:
case ConnectivityManager.TYPE_WIMAX:
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可以为所有实现target接口的view设置图片,
4.picasso不断判断是否之前加入过此view可以修正在滚动列表中图片错乱的毛病。
以上是关于Picasso粗糙分析的主要内容,如果未能解决你的问题,请参考以下文章