Picasso源代码分析:3、BitmapHunter

前两篇文章当中,我们依次分析了根据数据的流程,即我们调用的代码

Picasso.with(Context).load(imageUrl).into(ImageView)

依次分析了Picasso.java和ReqeustCreator.java的源代码,其中对Reqeust,Action,Target等等也有了一定的了解,接下来干嘛呢?

其实到现在为止,我们分析的,都是任务的创建和提交,提交到队列之后干嘛呢?在ReqeustCreator当中有一个同步的请求方法Bitmap get()方法,在该方法中,最后的代码依次是创建了一个BitmapHunter,然后调用了其中的hunt()方法,因此我们可以大体猜出,该类应该是负责执行具体的请求的类。我们先不去管具体的任务是怎么样调度的,先来分析一下这个类。BitmapHunter.java

BitmapHunter

首先看到,该类实现了Runnable接口,也就是说,在正常的情况下,其中的run()方法应该是程序的主要入口,Executor或者Thread调用该线程,并执行其中的run方法。我们依次来分析。

静态变量:

  //一个全局锁,用来保证在同一个时刻只有一张图片在解码。为了减少内存消耗,哈哈,原来这个地方是直接从Volley那里学来的,我说怎么一个样子。
  private static final Object DECODE_LOCK = new Object();

  private static final ThreadLocal<StringBuilder> NAME_BUILDER = new ThreadLocal<StringBuilder>() {
    @Override protected StringBuilder initialValue() {
      return new StringBuilder(Utils.THREAD_PREFIX);
    }
  };

  private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger();

  private static final RequestHandler ERRORING_HANDLER = new RequestHandler() {
    @Override public boolean canHandleRequest(Request data) {
      return true;
    }

    @Override public Result load(Request data) throws IOException {
      throw new IllegalStateException("Unrecognized type of request: " + data);
    }
  };
  • 在BitmapHunter当中也有一个全局锁,DECODE_LOCK,用来保证任何时刻最多只有一个图片正在解码,作者说这是从Volley中学来的。
  • NAME_BUILDER,是一个ThreadLocal<StringBuilder>,ThreadLocal大约是Thread Local Variable的意思,也就是说,在每个线程中,都有唯一的一个NAME_BUILDER的副本,等以后用的时候再去分析。
  • SEQUENCE_GENERATOR:序列号生成器,原子变量,确保多线程中能够正确的生成需要的序列号。
  • ERRORING_HANDLER:一个默认的用来处理错误的HANDLER,默认的实现办法是抛出异常。

成员变量:

类中的成员变量我们先不看,等用的时候再哎看看是怎么定以的就可以。

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) {
    exception = e;
    dispatcher.dispatchFailed(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()方法中都做了什么事情?

  1. 更新线程名称
  2. 调用hunt()方法获取请求结果。
  3. 根据结果result派发dispatcher.dispatchFailed(this);或者dispatcher.dispatchComplete(this);

所以重点还是在Bitmap hunt()方法当中

Bitmap hunt() throws IOException {
  Bitmap bitmap = null;

  //如果不跳过MemoryCache,那么则尝试从MemoryCache获取
  if (!skipMemoryCache) {
    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.loadFromLocalCacheOnly = (retryCount == 0);
  RequestHandler.Result result = requestHandler.load(data);
  if (result != null) {
    bitmap = result.getBitmap();
    //从枚举类获取信息,是从Network,Disk,还是Memory中获取到的数据
    loadedFrom = result.getLoadedFrom();
    //是否需要旋转
    exifRotation = result.getExifOrientation();
  }

  if (bitmap != null) {
    if (picasso.loggingEnabled) {
      log(OWNER_HUNTER, VERB_DECODED, data.logId());
    }
    //(错误理解!)派发数据---->奇怪,是什么先派发Decoded完成,再派发Transformed,如果需要Transformed,那么Decoded是否是重复工作了呢?
    //实际上,并不是派发数据,而是通知统计线程,完成该图片的下载和解码。
    stats.dispatchBitmapDecoded(bitmap);
    //此处则是需要旋转图片,或者是其他的自定义的图片滤镜
    if (data.needsTransformation() || exifRotation != 0) {
      synchronized (DECODE_LOCK) {
        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);
      }
    }
  }

  return bitmap;
}

可以看出,其实hunt()方法主要进行了以下的工作:

  1. 判断是否跳过MemoryCache,如果不跳过,那么就尝试从MemoryCache中获取数据。
  2. 然后调用Reqeust对应的ReqeustHandler去下载数据并解码为Bitmap
  3. 通知统计线程更新统计信息
  4. 如果需要Transformation或者旋转,那么则依次调用Transformation还有旋转
    1. 需要注意的是,此处同样是对Bitmap进行操作,因此也会消耗大量的内存空间,所以Picasso也是使用了同样一个锁DECODE_LOCK来保证同一个时刻仅仅有一个图片正在处理。
    2. 之前文章《Picasso的使用:使用Transformation,下载后预处理图片并显示》中,我们在ReqeustCreator中添加了一个自定义的Transformation,就在这个阶段会被调用。

void attach(Action action)

将action合并到当前实例当中,实际上我理解的就是Volley的ImageLoader里面的BatchedImageReqeust一样,将多个同样地址的请求打包在一起进行处理。其代码如下:

void attach(Action action) {
  boolean loggingEnabled = picasso.loggingEnabled;
  Request request = action.request;

  if (this.action == null) {
    this.action = action;
    if (loggingEnabled) {
      if (actions == null || actions.isEmpty()) {
        log(OWNER_HUNTER, VERB_JOINED, request.logId(), "to empty hunter");
      } else {
        log(OWNER_HUNTER, VERB_JOINED, request.logId(), getLogIdsForHunter(this, "to "));
      }
    }
    return;
  }

  if (actions == null) {
    actions = new ArrayList<Action>(3);
  }

  actions.add(action);

  if (loggingEnabled) {
    log(OWNER_HUNTER, VERB_JOINED, request.logId(), getLogIdsForHunter(this, "to "));
  }

  //更新优先级
  Priority actionPriority = action.getPriority();
  if (actionPriority.ordinal() > priority.ordinal()) {
    priority = actionPriority;
  }
}

其代码比较简单,主要包括两部分的功能:

  • 将action添加到成员变量actions当中
  • 修正优先级。以actions当中优先级最高的作为整个BitmapHunter的优先级。

BitmapHunter大体的思路就是这样,下面会继续分析ReqeustHandler以及Delivery的相关代码。

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注