Picasso图片加载框架 —— 源码解析(一)

图片加载是Android开发中的常见情景,在日常项目中,也一直在用Picasso来处理各种加载需求。用的多了,也就自然想了解下其实现过程。这里,将深入源码,学习Picasso框架的实现过程和设计思想。计划用三篇文章来对Picasso做一个全面的分析,而这第一篇的目标则是梳理一下Picasso加载图片的整体思路和核心流程。这样有了主干,学习起来才能全而不乱。

一、Picasso简介

Picasso是Square公司推出的开源图片加载库。体量轻、功能完善、使用简单,配合OkHttp使用效果极佳。
官方链接:http://square.github.io/picasso/

若想在项目中使用Picasso,只需在gradle中添加依赖即可:

//2.5.0为版本号
dependencies {
    compile 'com.squareup.picasso:picasso:2.5.0'
}

本文所涉及到的源码均为Picasso 2.5.0版本

二、源码分析

在分析源码之前,我们先看下Picasso是如何使用的:

Picasso.with(context).load(url).into(imageView);

给定一个url和ImageView,就可以将图片加载到ImageView中,十分简单。

接下来,我们就一步步的看下,Picasso是如何通过一行代码实现图片加载的。

1. 初始化Picasso实例

static Picasso singleton = null;

Picasso是一个单例类,可通过Picasso.with()静态方法对Picasso进行初始化。

/**
 * Lru memory cache: 默认缓存大小为程序运行内存的15%
 * Disk cache: 默认缓存大小为5MB ~ 50MB 
*/
public static Picasso with(Context context) {
  if (singleton == null) {
    synchronized (Picasso.class) {
      if (singleton == null) {
        singleton = new Builder(context).build();
      }
    }
  }
  return singleton;
}

从源码可以看到,Picasso.with()方法做的事情非常简单,就是使用双重校验锁的方式,调用Picasso.Builder.build()构造Picasso的单例对象。

既然如此,那么Builder就一定保存了初始化Picasso所需的参数:

public static class Builder {
  //上下文
  private final Context context;
  //从外部资源(网络、磁盘等)下载图片的下载器
  private Downloader downloader;
  //异步加载图片的线程池  
  private ExecutorService service;
  //内存缓存    
  private Cache cache;
  //监听图片加载失败的回调 
  private Listener listener;
  //request的修改器 
  private RequestTransformer transformer;
  //自定义的图片获取方式  
  private List<RequestHandler> requestHandlers;
  //图片配置    
  private Bitmap.Config defaultBitmapConfig;
  //是否显示debug indicators    
  private boolean indicatorsEnabled;
  //是否允许debug logging   
  private boolean loggingEnabled;
}

所有变量均提供了set方法供用户从外部进行设置。若不设置,则Builder.build()方法也为其提供了默认实现。

public Picasso build() {
  Context context = this.context;

  if (downloader == null) {
    //若能反射找到com.squareup.okhttp.OkHttpClient,则downloader为OkHttpDownloader
    //否则downloader为UrlConnectionDownloader
    downloader = Utils.createDefaultDownloader(context);
  }
  if (cache == null) {
    cache = new LruCache(context);
  }
  if (service == null) {
    //核心线程数和最大线程数均为3的线程池
    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);
}

可以看到,Builder.build()方法做的事情就是为各成员变量赋值,并调用new Picasso()来构建Picasso的实例。

Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
    //省略其他代码

    //分发器
    this.dispatcher = dispatcher;
    //请求处理器
    List<RequestHandler> allRequestHandlers =
        new ArrayList<RequestHandler>(builtInHandlers + extraCount);
    // ResourceRequestHandler needs to be the first in the list to avoid
    // forcing other RequestHandlers to perform null checks on request.uri
    // to cover the (request.resourceId != 0) case.
    allRequestHandlers.add(new ResourceRequestHandler(context));
    if (extraRequestHandlers != null) {
      allRequestHandlers.addAll(extraRequestHandlers);
    }
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
    allRequestHandlers.add(new ContentStreamRequestHandler(context));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);
    //省略其他代码
}

在这之中,有两个很重要角色:Dispatcher和RequestHandler。大家先对此有个印象,后面用到时会详细讲。

2. 创建Request

初始化好了Picasso的实例,接下来就要调用Picasso.load()方法了。

Picasso.load()默认提供了四种重载:
load()
// load(String)和load(File)最终都会调用load(Uri)。
public RequestCreator load(File file) {
    if (file == null) {
      return new RequestCreator(this, null, 0);
    }
    return load(Uri.fromFile(file));
  }

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

public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
  }

//直接创建RequestCreator
public RequestCreator load(int resourceId) {
    if (resourceId == 0) {
      throw new IllegalArgumentException("Resource ID must not be zero.");
    }
    return new RequestCreator(this, null, resourceId);
  }

可以看到,这四个方法内部实现大同小异,最终都会创建一个RequestCreator对象。

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;
    //data即Request.Builder对象,其封装了真实的请求内容
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
  }

3. 分发Action

Picasso.load()方法创建好了RequestCreator,也准备好了url,接下来就要创建Action了。

//采用异步方式,将request的结果填充到ImageView中
public void into(ImageView target) {
    into(target, null);
  }

// Callback为监听图片添加结果(成功/失败)的回调
public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    //检查是否为主线程调用
    checkMain();

    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }
    
    //检查请求中是否设置了uri或resourceId。若均没有设置,则视其为无效请求,取消这次request
    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }

    //是否调整图片尺寸
    if (deferred) {
      if (data.hasSize()) {
        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));
        return;
      }
      data.resize(width, height);
    }

    //创建真实请求
    Request request = createRequest(started);
    String requestKey = createKey(request);

    //是否允许从cache中读取图片
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      //若cache中存在要请求的图片,则取消请求,直接加载cache中的图片
      if (bitmap != null) {
        picasso.cancelRequest(target);
        //将cache中的图片加载到ImageView上
        setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
        if (picasso.loggingEnabled) {
          log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
        }
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }

    //是否需要占位图片
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }

    //执行到此,则表示需要从外部资源下载图片
    //创建ImageViewAction,ImageViewAction内部持有ImageView和Request
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);
    
    //将Action放入队列并提交
    picasso.enqueueAndSubmit(action);
  }

RequestCreator.into()方法主要做了一些检查和准备工作,包括:
1)确保在主线程进行调用;
2)检查request的有效性,即uri或resourceId是否为空;
3)是否从cache中获取图片;
4)若request有效且cache中没有目标图片,则创建ImageViewAction,提交并放入队列。

到此为止,还是没有看到处理url和添加图片的过程,只能跟着Picasso.enqueueAndSubmit(action)继续往下看。

void enqueueAndSubmit(Action action) {
    //对于ImageViewAction来说,target即ImageView
    Object target = action.getTarget();

    //检查ImageView是否有其他正在执行的action,若有,则取消之前的action
    if (target != null && targetToAction.get(target) != action) {
      cancelExistingRequest(target);
      targetToAction.put(target, action);
    }

    //提交action
    submit(action);
  }

Picasso.enqueueAndSubmit()方法将action存入WeakHashMap中,并确保ImageView当前只有一个正在执行的action。

void submit(Action action) {
    dispatcher.dispatchSubmit(action);
  }

Picasso.submit()方法也没有对action进行处理,而是直接调用Dispatcher.dispatchSubmit()继续下发。

Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
      Downloader downloader, Cache cache, Stats stats) {
    //省略其他代码

    // DispatcherHandler继承自HandlerThread,因此handler为子线程的handler
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
    // mainThreadHandler为主线程handler
    this.mainThreadHandler = mainThreadHandler;

    //省略其他代码
}

void dispatchSubmit(Action action) {
    //将action装入message,并由子线程handler进行发送,完成线程切换
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
  }

Dispatcher是分发器,该类持有两个Handler:一个是mainThreadHandler,另一个是子线程handler。dispatchSubmit()方法即调用子线程handler的sendMessage()方法,将action发送到子线程进行处理。

值得注意的是,之前的代码是在主线程中运行的,而在handler.sendMessage()之后,便完成了主线程到子线程的切换。这也意味着接下来要做耗时操作了。

4. 提交BitmapHunter

在子线程接收到message后,会调用Dispatcher.performSubmit()方法。

@Override 
public void handleMessage(final Message msg) {
      switch (msg.what) {
        case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
        }
      //省略其他代码
      }
}

void performSubmit(Action action) {
    performSubmit(action, true);
}

void performSubmit(Action action, boolean dismissFailed) {
    //如果当前action在pausedTags中,则暂停执行并将其放入pausedActions
    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;
    }

    //从active hunters中寻找有无action对应的BitmapHunter
    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对象
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    //执行hunter任务
    hunter.future = service.submit(hunter);
    //将hunter放入active hunters
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

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

Dispatcher.performSubmit()方法是Dispatcher中非常重要的方法,所有的请求都会经此方法进行提交。该方法主要干了几件事:
1)从action中取出对应的tag,并检查其是否在pausedTags中。如果存在,则暂停执行该action并将其放入pausedActions;
2)从active hunters中寻找有无action对应的BitmapHunter。如果有,则直接取出对应的BitmapHunter,并将action attach上去;
3)若以上条件均不满足,则为当前action创建新的BitmapHunter,并提交给线程池执行。

5. hunt目标图片

至此,我们可以大胆猜测,这个BitmapHunter应该就是一个Runnable,图片下载过程应该就在其run()方法中。

class BitmapHunter implements Runnable {

  static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    //遍历Picasso.getRequestHandlers(),寻找合适的请求处理器
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

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

  @Override
  public void run() {
    try {
      updateThreadName(data);

      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
      }

      //调用hunt()方法获取结果
      // result是一个Bitmap变量
      result = hunt();

      //如果bitmap为null,则图片获取失败,调用dispatchFailed()方法分发失败消息
      //否则,调用dispatchComplete()方法分发成功消息
      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);
    }
  }
}

BitmapHunter有一个名为forRequest()的静态方法,用于BitmapHunter的实例化。在该方法内部,会遍历Picasso.getRequestHandlers()列表,寻找合适的RequestHandler。而这个RequestHandlers列表便是在Picasso.new()方法中创建的,参考2.1节。

得到BitmapHunter后,便可将其交给线程池来执行了。run()方法主要做了两件事:
1)调用hunt()方法得到Bitmap结果;
2)若成功获取图片,则调用dispatchComplete()方法分发成功消息,否则,调用dispatchFailed()方法分发失败消息。

接着看BitmapHunter.hunt()方法:

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

    //从cache中获取目标图片
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      bitmap = cache.get(key);
      if (bitmap != null) {
        //若找到目标图标,则更新stats状态并返回bitmap
        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.load()方法获取目标图片
    //目标图片会以Bitmap或Stream的形式存在result中
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifRotation = result.getExifOrientation();

      bitmap = result.getBitmap();

      //如果目标图片是Stream形式,则需decodeStream()
      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() || 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;
}

可以看到,图片获取过程是由requestHandler.load()完成的。而这个RequestHandler就是我们在forRequest()方法中找的请求处理器。

6. 下载目标图片

假设我们要从网络上获取图片,那么forRequest()中找的的目标RequestHandler应该就是NetworkRequestHandler。load()方法自然就是发送网络请求,返回Result的过程。

@Override 
public Result load(Request request, int networkPolicy) throws IOException {
    //downloader才是request的真正执行者
    Response response = downloader.load(request.uri, request.networkPolicy);
    if (response == null) {
      return null;
    }

    Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;

    //目标图片以Bitmap的形式返回
    Bitmap bitmap = response.getBitmap();
    if (bitmap != null) {
      return new Result(bitmap, loadedFrom);
    }

    //目标图片以Stream的形式返回
    InputStream is = response.getInputStream();
    if (is == null) {
      return null;
    }
    // Sometimes response content length is zero when requests are being replayed. Haven't found
    // root cause to this but retrying the request seems safe to do so.
    if (loadedFrom == DISK && response.getContentLength() == 0) {
      Utils.closeQuietly(is);
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && response.getContentLength() > 0) {
      stats.dispatchDownloadFinished(response.getContentLength());
    }
    return new Result(is, loadedFrom);
}

通过源码发现,NetworkRequestHandler并非request的真正处理者,其内部的downloader才是request的负责人。

Downloader是一个接口,Picasso内置两种实现:OkHttpDownloader和UrlConnectionDownloader。如果用户不主动设置的话,Picasso.Builder.build()方法会根据规则两者选一:若能反射找到com.squareup.okhttp.OkHttpClient,则使用OkHttpDownloader,否则downloader为UrlConnectionDownloader。

这里,我们以UrlConnectionDownloader为例,看一下UrlConnectionDownloader.load()方法:

@Override 
public Response load(Uri uri, int networkPolicy) throws IOException {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      installCacheIfNeeded(context);
    }
    
    //建立HttpURLConnection 
    HttpURLConnection connection = openConnection(uri);
    //使用cache
    connection.setUseCaches(true);

    if (networkPolicy != 0) {
      String headerValue;

      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        headerValue = FORCE_CACHE;
      } else {
        StringBuilder builder = CACHE_HEADER_BUILDER.get();
        builder.setLength(0);

        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
          builder.append("no-cache");
        }
        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
          if (builder.length() > 0) {
            builder.append(',');
          }
          builder.append("no-store");
        }

        headerValue = builder.toString();
      }

      connection.setRequestProperty("Cache-Control", headerValue);
    }

    int responseCode = connection.getResponseCode();
    if (responseCode >= 300) {
      connection.disconnect();
      throw new ResponseException(responseCode + " " + connection.getResponseMessage(),
          networkPolicy, responseCode);
    }

    long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
    boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));
    //返回Response
    return new Response(connection.getInputStream(), fromCache, contentLength);
}

基本就是HttpURLConnection发起网络请求的过程。

7. 分发Response

在UrlConnectionDownloader.load()得到Response后,会一层层地将结果返回:UrlConnectionDownloader.load() ---> NetworkRequestHandler.load() ---> BitmapHunter.hunt() ---> BitmapHunter.run()。

在BitmapHunter.run()中,根据目标图片是否为null来决定是发送成功消息,还是失败消息。

@Override 
public void run() {
      //省略其他代码
      result = hunt();
      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }
      //省略其他代码
}

这里,我们假设分发成功消息,即调用dispatcher.dispatchComplete(this)。

void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
  }

@Override 
public void handleMessage(final Message msg) {
  switch (msg.what) {
    case HUNTER_COMPLETE: {
      BitmapHunter hunter = (BitmapHunter) msg.obj;
      dispatcher.performComplete(hunter);
      break;
    }
    //省略其他代码
  }
}

void performComplete(BitmapHunter hunter) {
    //是否将目标图片缓存起来
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    //将当前的BitmapHunter从active hunters中移除
    hunterMap.remove(hunter.getKey());
    //批处理
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
    }
}

在Dispatcher.performComplete()中,根据内存策略决定是否缓存目标图片并调用Dispatcher.batch()继续批处理。

private void batch(BitmapHunter hunter) {
   if (hunter.isCancelled()) {
     return;
   }

   //将hunter添加到List中
   batch.add(hunter);
   if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
     //延迟200ms发送消息
     handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
   }
}

@Override 
public void handleMessage(final Message msg) {
 switch (msg.what) {
   case HUNTER_DELAY_NEXT_BATCH: {
         dispatcher.performBatchComplete();
         break;
   }
   //省略其他代码
 }
}

Dispatcher.batch()方法会将分发过来的hunter放到batch列表中,并延迟发送。

void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    //调用mainThreadHandler发送消息
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
  }

@Override 
public void handleMessage(final Message msg) {
  switch (msg.what) {
    case HUNTER_BATCH_COMPLETE: {
      List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
      //遍历batch列表,依次处理各个hunter
      for (int i = 0, n = batch.size(); i < n; i++) {
        BitmapHunter hunter = batch.get(i);
        hunter.picasso.complete(hunter);
      }
      break;
    }
    //省略其他代码
  }
}

在接收到HUNTER_DELAY_NEXT_BATCH消息后,Dispatcher.performBatchComplete()调用主线程handler继续分发消息,实现线程切换。

8. 加载图片

在Picasso.handleMessage()收到批处理消息后,会依次取出各个hunter,并调用Picasso.complete()。

void complete(BitmapHunter hunter) {
    Action single = hunter.getAction();
    List<Action> joined = hunter.getActions();

    boolean hasMultiple = joined != null && !joined.isEmpty();
    boolean shouldDeliver = single != null || hasMultiple;

    if (!shouldDeliver) {
      return;
    }

    Uri uri = hunter.getData().uri;
    Exception exception = hunter.getException();
    Bitmap result = hunter.getResult();
    LoadedFrom from = hunter.getLoadedFrom();

    //处理action
    if (single != null) {
      deliverAction(result, from, single);
    }

    //处理action
    if (hasMultiple) {
      for (int i = 0, n = joined.size(); i < n; i++) {
        Action join = joined.get(i);
        deliverAction(result, from, join);
      }
    }

    if (listener != null && exception != null) {
      listener.onImageLoadFailed(this, uri, exception);
    }
}

private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
    if (action.isCancelled()) {
      return;
    }
    if (!action.willReplay()) {
      targetToAction.remove(action.getTarget());
    }
    if (result != null) {
      if (from == null) {
        throw new AssertionError("LoadedFrom cannot be null.");
      }

      //回调action.complete()
      action.complete(result, from);
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
      }
    } else {
      action.error();
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_ERRORED, action.request.logId());
      }
    }
}

经过Picasso.complete() ---> Picasso.deliverAction(),最终回调到ImageViewAction.complete()方法。在该方法中,完成了ImageView加载图片的过程。

class ImageViewAction extends Action<ImageView> {
  @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;
    //将图片加载到ImageView上
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

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

final class PicassoDrawable extends BitmapDrawable {
  static void setBitmap(ImageView target, Context context, Bitmap bitmap,
      Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof AnimationDrawable) {
      ((AnimationDrawable) placeholder).stop();
    }
    PicassoDrawable drawable =
        new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
    //为ImageView添加图片
    target.setImageDrawable(drawable);
  }
}

三、总结

经过上述源码分析,想必大家都已了解Picasso是如何从网络下载图片并加载到ImageView的。这里,直接用流程图作简要总结:
Picasso工作流程

其中,橙线为发送过程调用链,绿线为返回过程调用链。主要步骤如下:
1)在Picasso中创建RequestCreator;
2)创建并提交Action至Dispatcher;
3)Dispatcher创建BitmapHunter并交给线程池执行;
4)BitmapHunter调用ResourceHandler及Downloader下载图片;
5)图片下载成功后,返回BitmapHunter并由Dispatcher负责分发;
6)Picasso收到下载成功的消息后,回调Action的complete()方法,将图片加载到ImageView上。

最后

本文作为Picasso源码解析系列的第一篇,目标是从源码入手,梳理Picasso加载图片的工作流程,掌握其主要思路。如大家对Picasso框架的实现细节感兴趣,欢迎关注该系列的后续文章。如对本文有疑问,欢迎留言交流。如需要转载,则请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容