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粗糙分析的主要内容,如果未能解决你的问题,请参考以下文章

Picasso源码解析 一

Picasso源码解析 一

Picasso源码分析:into方法追本溯源和责任链模式创建BitmapHunter

Picasso源码解析之Lrucache算法源码解析

Picasso源码解析之Lrucache算法源码解析

Picasso,Glide,Fresco对比分析