Picasso 源码解读

原创 2016年05月31日 09:06:23

转载请注明本文出自maplejaw的博客http://blog.csdn.net/maplejaw_

基本概念

使用说明

Picasso,一个强大的图片下载与缓存库,出自Square公司。基本使用如下:

Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);

也可以进行全局配置(只能配置一次)。

 Picasso  picasso=new Picasso.Builder(this)
                 .addRequestHandler()//自定义请求处理,默认已支持Drawable,Content,File等等
                 .defaultBitmapConfig()//bitmap配置
                 .downloader()//下载器,可选okhttp或urlconnection,也可自定义
                 .executor()//线程池
                 .indicatorsEnabled()//提示器
                 .loggingEnabled()//日志
                 .memoryCache()//自定义内存缓存策略
                 .requestTransformer()//Request预处理
                 .listener()//下载失败监听
                 .build();

使用疑问

虽然这个加载框架已经被人熟知,但是自己在使用的时候难免会有点疑问。

  • 如果开启磁盘缓存会不会对本地图片也进行缓存?
  • 内部是怎么支持Drawable等其他类型的。
  • 怎么取消一个请求?
  • Picasso是怎么做到adapter中自动取消请求的?

现在让我们带着疑问,去源码里遨游吧。

源码解读

Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);

简约而不简单,相信你也非常好奇Picasso内部的工作原理吧。其实任何一个图片加载框架都可以当做一个普通的下载文件流程,一般都包含这么几个步骤:初始化配置->构造请求->执行请求->处理请求结果。
那么,就按照这个思路开始探索源码吧。

初始化配置

还是按照老规矩,从with(context)入手,源码如下。

  public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }

可以看出保持了一个单例。


 public Builder(Context context) {
      if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
      }
      this.context = context.getApplicationContext();
    }


    //构建一个Picasso对象
  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();
      }
       //Request转换器,用于在提交任务之前做一些处理,默认不做处理
      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中的方法我们可以了解到,初始化一个Picasso对象,需要的一些工作。

  • 初始化下载器
    Utils.createDefaultDownloader用于创建一个默认的下载器。
 static Downloader createDefaultDownloader(Context context) {
    try {
      Class.forName("com.squareup.okhttp.OkHttpClient");
      return OkHttpLoaderCreator.create(context);
    } catch (ClassNotFoundException ignored) {
    }
    return new UrlConnectionDownloader(context);
  }

Downloader是一个用于从网络上加载图片的接口,需要实现loadshutdown方法。load用于加载图片,shutdown用于关闭一些操作。从上面可以看出,会判断是否已经集成Okhttp,如果检测到就优先使用OkHttpDownloader,否则使用Urlconnection,我们来看一下OkHttpDownloader的源码,了解下如何自定义一个Downloader。

   @Override
   public Response load(Uri uri, int networkPolicy) throws IOException {
    CacheControl cacheControl = null;
    if (networkPolicy != 0) {
      //配置缓存策略
      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        cacheControl = CacheControl.FORCE_CACHE;//强制磁盘缓存
      } else {
        CacheControl.Builder builder = new CacheControl.Builder();
        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
          builder.noCache();//不从磁盘读
        }
        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
          builder.noStore();//不写入磁盘
        }
        cacheControl = builder.build();
      }
    }

   //okhttp Request
    Request.Builder builder = new Request.Builder().url(uri.toString());
    if (cacheControl != null) {
      builder.cacheControl(cacheControl);
    }

    //okhttp Response
    com.squareup.okhttp.Response response = client.newCall(builder.build()).execute();
    int responseCode = response.code();
    if (responseCode >= 300) {
      response.body().close();
      throw new ResponseException(responseCode + " " + response.message(), networkPolicy,
          responseCode);
    }

    boolean fromCache = response.cacheResponse() != null;

    ResponseBody responseBody = response.body();
    return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
  }

  @Override 
 public void shutdown() {
    //关闭缓存
    com.squareup.okhttp.Cache cache = client.getCache();
    if (cache != null) {
      try {
        cache.close();
      } catch (IOException ignored) {
      }
    }
  }

都是一些很常规的代码,根据缓存策略做了缓存,使用过okhttp的一定不会陌生。下载完毕后,返回的是一个Response对象,但是此Response非Okhttp自带的那个Response。Picasso中的Response是一个用来包装返回结果的类,可以看出responseBody.byteStream()传入了一个InputStream对象。
Response的构造方法如下。

    /**
     * Response stream and info.
     *
     * @param stream Image data stream.
     * @param loadedFromCache {@code true} if the source of the stream is from a local disk cache.
     * @param contentLength The content length of the response, typically derived by the
     * {@code Content-Length} HTTP header.
     */
    public Response(InputStream stream, boolean loadedFromCache, long contentLength) {
      if (stream == null) {
        throw new IllegalArgumentException("Stream may not be null.");
      }
      this.stream = stream;
      this.bitmap = null;
      this.cached = loadedFromCache;
      this.contentLength = contentLength;
    }
  • 初始化缓存
    缓存默认使用的LruCache,应该都很熟悉,这里就不赘述了。

  • 初始化线程池
    PicassoExecutorService是一个线程池,构造方法如下。

 private static final int DEFAULT_THREAD_COUNT = 3;
  PicassoExecutorService() {
    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
        new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
  }

默认启动了3个核心线程,采用了PriorityBlockingQueue优先级阻塞队列,也就是说Picasso支持优先级调度,PriorityBlockingQueue里面存储的对象必须是实现Comparable接口的。熟悉线程池的应该知道submit方法会将无法控制的Runnable对象用FutureTask包装,使其可控制(可取消)。于是如下所见,重写了submit用PicassoFutureTask进行包装。

  @Override
  public Future<?> submit(Runnable task) {
    PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
    execute(ftask);
    return ftask;
  }

为了使其支持优先级排序,实现了Comparable接口。

  private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
      implements Comparable<PicassoFutureTask> {
    private final BitmapHunter hunter;

    public PicassoFutureTask(BitmapHunter hunter) {
      super(hunter, null);
      this.hunter = hunter;
    }

    @Override
    public int compareTo(PicassoFutureTask other) {
      Picasso.Priority p1 = hunter.getPriority();
      Picasso.Priority p2 = other.hunter.getPriority();
      return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
    }
  }

你可能会纳闷BitmapHunter是什么鬼。暂且只需当作Runnable即可。
此外,PicassoExecutorService还暴露了一个adjustThreadCount方法,用来根据网络情况调整线程数。

  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);
    }
  }
  • 初始化转换器
    默认RequestTransformer.IDENTITY表示不作处理
  public interface RequestTransformer {

    Request transformRequest(Request request);

    RequestTransformer IDENTITY = new RequestTransformer() {
      @Override public Request transformRequest(Request request) {
        return request;
      }
    };
  }

我们可以对一个Request处理后返回一个新的Request。此Request不是Okhttp中的那个Request,该类结构如下:
image_1ak0clai513b21jka1p0b171v1kkqg.png-72.5kB

  • 初始化统计状态Stats
    这个类主要用于统计缓存命中率,下载成功次数,失败次数等等。里面用了一个HandlerThread和Handler来传递消息。通过一系列的dispatchXXX,由Handler发送消息,Handler接收消息后,通过performXXX来进行统计,熟悉HandlerThread的都知道此时performXXX运行在此线程中,Handler实现如下

  private static class StatsHandler extends Handler {

    private final Stats stats;

    public StatsHandler(Looper looper, Stats stats) {
      super(looper);
      this.stats = stats;
    }

    @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);
            }
          });
      }
    }
  }
  • 初始化调度器Dispatcher
    从build方法中可以看出,大多数参数直接传进了这个类的构造方法中,可见这个类不容小觑。Dispatcher主要是来调度任务的,比如提交任务,取消任务,暂停加载,恢复加载,重试,加载完成,监听网络等等。同样,里面也用了一个HandlerThread和Handler来分发任务。通过一系列的dispatchXXX,由Handler发送消息,Handler接收消息后,通过performXXX来调度任务。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;
        }
      //..
      //省略了部分源码
    }
  }

看到这里,终于把with(context)相关的源码看完了。接下来我们来看看load相关源码。load相关的源码其实就是在生成一个Request对象。

构造请求参数(生成Request)

在看完一系列的初始化配置相关的源码后,现在来看看Picasso.with(context).load(R.drawable.landing_screen).into(imageView1)中的load,到底做了什么吧。

  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));
  }

可以看出,返回的是一个RequestCreator对象,load(Uri.parse(path))最终调用的就是RequestCreator的构造方法。构造方法如下。

  private final Picasso picasso;
  private final Request.Builder data;

  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);
  }

可以看出,构造方法主要做了两件事,赋值一个Picasso对象,然后生成一个Request.Builder。看到这个Request.Builder相信你肯定能猜到最后一定会通过build返回一个Request对象吧。
RequestCreator这个类主要用来设置一些Request对象的参数,比如resize,centercrop等等。
image_1ak0ef2kg1voo10uq1nf41u8113at.png-49.6kB
RequestCreator内部的自带的属性(不属于Request)如下。
image_1ak0evpp8cag10dc6a2b0nc0n1n.png-43.2kB

load相关源码总体来说比较简单,接下来,我们来看看最后一个方法,into。

提交请求

into方法大家应该都比较熟悉,直接传入一个ImageView就大功告成了。

 public void into(ImageView target) {
    into(target, null);
  }

内部调用了重载方法,如下。

  public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    checkMain();//检查是否在主线程

    if (target == null) {//View不可为空
      throw new IllegalArgumentException("Target must not be null.");
    }

    if (!data.hasImage()) {//如果没有图片(uri是否为空,资源id为0)
      picasso.cancelRequest(target);//取消请求
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());//设置占位图
      }
      return;
    }

    if (deferred) {//scale类型为fit
      if (data.hasSize()) {//fit时不能使用resize
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      int width = target.getWidth();
      int height = target.getHeight();
      if (width == 0 || height == 0) {
        if (setPlaceholder) {//设置占位图
          setPlaceholder(target, getPlaceholderDrawable());
        }
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));//如果宽高有一个为0,自动算出宽高比
        return;
      }
      data.resize(width, height);
    }

    //构建Request
    Request request = createRequest(started);
    //生成key
    String requestKey = createKey(request);
   //从内存中读取
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {
        picasso.cancelRequest(target);//取消请求
        setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);//设置bitmap
        if (picasso.loggingEnabled) {
          log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
        }
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }

    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }

   //内存没读到,就包装一个Action
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

   //提交Action
    picasso.enqueueAndSubmit(action);
  }

方法有点长,不要急慢慢来,首先会检测Uri是否为空,如果空的话就取消请求。紧接着构建Request对象。

  private Request createRequest(long started) {
    int id = nextId.getAndIncrement();

    Request request = data.build();
    request.id = id;
    request.started = started;
    //省略了部分源码
    Request transformed = picasso.transformRequest(request);
    if (transformed != request) {
      // If the request was changed, copy over the id and timestamp from the original.
      transformed.id = id;
      transformed.started = started;
       //省略了部分源码
    }

    return transformed;
  }

可以看出在createRequest里构建了Request对象,并给每一个request赋值了唯一的递增id。构建完Request之后根据Request生成一个key,如果可以从内存中读取就先从内存中读,读取到了就取消那个请求然后设置给ImageView。如果内存中没有读取到,最终会将原本的Request和RequestCreator中的属性包装为一个Action对象提交到队列中去。ImageViewAction继承于Action,Action的源码如下,此外Action还有三个主要的回调方法complete,errorcancel
image_1ak0gjtl0jer1q9h2tqq36s24.png-80.9kB

我们知道,最终会通过picasso.enqueueAndSubmit(action);来提交请求。那么picasso.enqueueAndSubmit(action);做了什么呢?源码在Picasso.java中,如下

  void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();//获取target,一般为view
    if (target != null && targetToAction.get(target) != action) {
     // targetToAction为一个map集合,一个target一个action
     //如果target已经存在一个任务,直接取消那个任务,执行新任务
      cancelExistingRequest(target);
      //然后把新任务放入map中
      targetToAction.put(target, action);
    }
    submit(action);//提交新任务
  }

一般第一次提交会直接走submit(action)提交任务,我们暂时不要管取消,submit源码如下。

  void submit(Action action) {
    dispatcher.dispatchSubmit(action);//内部是个Handler发送消息
  }

dispatchSubmit中由DispatcherHandler将action发送到子线程,继续跟踪源码,如下。

  void performSubmit(Action action, boolean dismissFailed) {
    if (pausedTags.contains(action.getTag())) {//根据tag判断是不是需要暂停
      pausedActions.put(action.getTarget(), action);//放入暂停的map
      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;
    }


     //一般第一次会走这里,通过forRequest生成一个BitmapHunter(Runable)
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    //提交runbale
    hunter.future = service.submit(hunter);
    //放入map中
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
    }
  }

生成BitmapHunter 的源码如下,BitmapHunter实现了Runable接口。

  static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
      Action action) {
    Request request = action.getRequest();
    //可以处理的请求类型
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    //对列表进行循环
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
      if (requestHandler.canHandleRequest(request)) {//判断提交过来的uri是否可以被处理
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }

BitmapHunter的构造方法如下。
image_1ak0hvd831689nr2klvha8dk32h.png-93.8kB
既然BitmapHunter实现了Runable接口,那么毋容置疑,接下来我们去看看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);
    }
  }

代码很长,但是核心的就result=hunt()这一句,其他的都是调度器(dispatcher)根据结果在分发任务。我们来看看这个核心方法。

  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);//然后根据请求处理去相应地方加载图片(网络,content,drawable等等)
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifRotation = 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);
        }
      }
    }


//如果bitmap不为空,进行处理
    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_DECODED, data.logId());
      }
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifRotation != 0) {
        synchronized (DECODE_LOCK) {//同步锁,保证只能有一个bitmap被处理
          if (data.needsMatrixTransform() || exifRotation != 0) {
            //处理旋转,缩放等其他变换
            bitmap = transformResult(data, bitmap, exifRotation);
            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);
        }
      }
    }
    //返回处理过后的bitmap
    return bitmap;
  }

处理请求结果

假如bitmap不为空,那么现在应该走 dispatcher.dispatchComplete(this);方法。

  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");
    }
  }

经过batch方法后一番周转,最终会通过主线程的Handler回调到Action的complete方法中。
主线程的的Handler如下。

static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
    @Override public void handleMessage(Message msg) {
      switch (msg.what) {
        case HUNTER_BATCH_COMPLETE: {
          @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
          //noinspection ForLoopReplaceableByForEach
          for (int i = 0, n = batch.size(); i < n; i++) {
            BitmapHunter hunter = batch.get(i);
            hunter.picasso.complete(hunter);
          }
          break;
        }
       //省略了部分源码
    }
  };

ImageViewAction中的complete源码如下。

@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    if (result == null) {
      throw new AssertionError(
          String.format("Attempted to complete action with no result!\n%s", this));
    }

    ImageView target = this.target.get();
    if (target == null) {
      return;
    }

    Context context = picasso.context;
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);//设置图片

    if (callback != null) {
      callback.onSuccess();
    }
  }

到此为止Picasso的源码算是解读完了。

最后

  • 怎么取消一个请求?
    调用picasso.cancelRequest();即可,所有取消请求内部都会调用cancelExistingRequest(target);来取消任务,源码如下。
   private void cancelExistingRequest(Object target) {
    checkMain();
    Action action = targetToAction.remove(target);//从map中移除
    if (action != null) {
      action.cancel();//取消任务。
      dispatcher.dispatchCancel(action);//调度器分发取消
    }
    if (target instanceof ImageView) {
      ImageView targetImageView = (ImageView) target;
      DeferredRequestCreator deferredRequestCreator =
          targetToDeferredRequestCreator.remove(targetImageView);//移除计算宽高
      if (deferredRequestCreator != null) {
        deferredRequestCreator.cancel();
      }
    }

Dispatcher中的执行取消的源码如下。

  void performCancel(Action action) {
    String key = action.getKey();
    BitmapHunter hunter = hunterMap.get(key);
    if (hunter != null) {
      hunter.detach(action);
      if (hunter.cancel()) {
        hunterMap.remove(key);
        if (action.getPicasso().loggingEnabled) {
          log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId());
        }
      }
    }

    if (pausedTags.contains(action.getTag())) {
      pausedActions.remove(action.getTarget());
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_CANCELED, action.getRequest().logId(),
            "because paused request got canceled");
      }
    }

    Action remove = failedActions.remove(action.getTarget());
    if (remove != null && remove.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_CANCELED, remove.getRequest().logId(), "from replaying");
    }
  }
  • 内部是怎么判读并加载Drawable图片?
    我们再来看一下Picasso的构造方法,看到没有,定义了一系列RequestHandler。
    image_1ak0tfv7h1ebutvk1dg2d12k9n9.png-46.4kB
    在forRequest中,会循环遍历属于那种类型,然后使用相应的加载方案。
    image_1ak0todtm9du1o0j1aneerotofm.png-17.9kB

  • 该框架会对任何图片做磁盘缓存吗?
    不会,如果允许,只会对网络图片进行磁盘缓存。除了NetWorkReqsutHandler,其他没有磁盘缓存功能。

  • Picasso是怎么做到adapter中自动取消任务的?
    其实靠的是ImageView的复用,一个View对应一个Action,当ImageView被复用就自动取消之前的任务执行新任务。
    image_1ak0usedd1gk71ge98a71jqo1pag13.png-22.8kB


如果对于HandlerThread不太熟悉可以参考这篇【IntentService意图服务 源码解读】
对线程池和FeatureTask不太熟悉的可以参考这篇【AsyncTask异步任务 源码解读】

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

《Picasso源码学习》

前言 俗话说男儿有泪不轻弹,都26了,因为工作上的原因,有委屈,有压力,有不足,有外因,前几天自己在回家的路上,还是没忍住,人生最难过的事,莫过于别人对你失去信任。 纸上得来终觉浅, 绝知此事要躬行。...

Picasso源码分析

对于android的图片加载库,之前用过UIL、volley、Picasso、Glide、Fresco,都是很好的图片加载库,之前对于Picasso这个库研究的比较多一点,网络上对于Picasso的源...

Picasso源码分析(二):默认的下载器、缓存、线程池和转换器

下载器当用户没有为Picasso指定下载器的时候Picasso会通过Utils.createDefaultDownloader(context)方法创建一个默认的下载器 static Downloa...

Picasso源码初探

Picasso是Square基于Android平台的开源框架,提供图片加载和缓存功能,本文基于Picasso 2.6.0-SNAPSHOT版本代码,粗略分析其运行原理。相关网站:Picasso主页,g...

Picasso从使用到源码解析

Picasso从使用到源码解析一、使用Picasso是Square公司开源的一个Android图形缓存库,地址http://square.github.io/picasso/,可以实现图片下载和缓存功...

Android 图片加载框架Picasso基本使用和源码完全解析

说到Picasso,相信Android开发人员绝不陌生,它是Square公司开发的一款图片加载神器。使用过它的coder绝对是爱不释手:对它本身而言,轻量安全,有效加载图片并防止OOM;对我们开发者来...
  • guiman
  • guiman
  • 2017-05-31 10:02
  • 1676

Picasso源码解析

Picasso作为一个非常棒的android第三方图片加载库,在Github上获得了高达7000多的star。Picasso.with(context).load(“image url”).into(...

Picasso源码解析

Picasso源码解析 本文基于Picasso2.4.0版本 Picasso加载图片最简单的调用方法是 Picasso.with(mContext).load(url).into(iv); ...
  • yulyu
  • yulyu
  • 2017-02-13 10:47
  • 4982

Picasso源码原理分析

作者:谭东 QQ:852041173 Picasso是美国SQUARE移动支付公司开源的图片缓存加载库。可以实现图片下载和缓存功能,效率和性能都很不错。 Sq...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)