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可以修正在滚动列表中图片错乱的毛病。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值