Picassio源码分析

步骤

get():

双锁创建Picasso单例,通过Builder模式创建

Picasso singleton;

  public static Picasso get() {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          if (PicassoProvider.context == null) {
            throw new IllegalStateException("context == null");
          }
          singleton = new Builder(PicassoProvider.context).build();
        }
      }
    }
    return singleton;
  }
private  Context;
 public Builder(@NonNull Context context) {
      if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
      }
    // 这里得到的是ApplicationContext的上下文
      this.context = context.getApplicationContext();
    }

可以看到传入的只是Builder到这里只有一个上下文context

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

      if (downloader == null) {
        downloader = new OkHttp3Downloader(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 : OKHTTP的下载器
  • LruCache : 缓存器
  • PicassoExecutorService : 线程池服务
  • transformer : 变压器
  Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
    this.context = context;
    this.dispatcher = dispatcher;
    this.cache = cache;
    this.listener = listener;
    this.requestTransformer = requestTransformer;
    this.defaultBitmapConfig = defaultBitmapConfig;

    int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
    int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
    List<RequestHandler> allRequestHandlers = new ArrayList<>(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);

    this.stats = stats;
    this.targetToAction = new WeakHashMap<>();
    this.targetToDeferredRequestCreator = new WeakHashMap<>();
    this.indicatorsEnabled = indicatorsEnabled;
    this.loggingEnabled = loggingEnabled;
    this.referenceQueue = new ReferenceQueue<>();
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    this.cleanupThread.start();
  }

这里我们才真正真正初始化了单例Picasso ,最后传入对应的配置。接下开看看比较重要的:
allRequestHandlers:是一个容器内部包裹了多个RequestHandler,用来解决不同的请求,举个例子,家里装了新家
我们需要做的事情就是请工人安装,但是工人分为好几种,电工.管道.工装空调的等等他们各司其职 ,用来解决不同的场景,而这里我们就可以把他看成一个一个的“工人”。

--

load()

 public RequestCreator load(@Nullable 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(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
  }

这里可以看见调用load的时候我们进行判断过滤,输入的满足条件就可以开始创建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;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
  }

做了两点,传入picasso引用,创建Request.Builder ,这里load就结束了其实,我们发现返回的其实是包装好了的RequestCreator,而且RequestCreator内部包含引用data=Request.Builder,但是到目前为止我们什么都还没做,只是初始化了相关配置,创建了RequestCreator而已。

into

好戏现在才开始


  public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    checkMain();

    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }

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

    if (shouldReadFromMemoryCache(memoryPolicy)) {
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {
        picasso.cancelRequest(target);
        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());
    }

    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
  }
  
  
   private Request createRequest(long started) {
    int id = nextId.getAndIncrement();

    Request request = data.build();
    request.id = id;
    request.started = started;

    boolean loggingEnabled = picasso.loggingEnabled;
    if (loggingEnabled) {
      log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString());
    }

    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;

      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed);
      }
    }

    return transformed;
  }
  
    boolean hasImage() {
      return uri != null || resourceId != 0;
    }
  1. 我们检查是不是在主线程
  2. 判断uri或者resourceId ,即来源是不是为空,为空就直接取消请求,设置占位图
  3. 是不是调用fit(),但是不能和resize一起用
  4. 创建Request实体,即Request.Builder.build(),这里有过滤然后设置该Request为普通
  5. 设置resourceId还有开始的时间
  6. createRequest(started)设置了transformed转换器的一个接口,实现该接口应该可以手动改变request,如果转换后的request变更我们就改变他
  7. 是不是需要在缓存中找,如果找到取消请求,设置图片,回调成功接口。
  8. 没有找到就设置占位图,创建一个Action,调用picasso.enqueueAndSubmit(action);

Action 和 Requst的对比。

如果查看区别的话,其实属性都可以查看出来:
我们看看Request属性:

 int id;
  long started;
  int networkPolicy;
  public final Uri uri;
  public final int resourceId;
  public final String stableKey;
  public final List<Transformation> transformations;
  public final int targetWidth;
  public final int targetHeight;
  public final boolean centerCrop;
  public final boolean centerInside;
  public final boolean onlyScaleDown;
  public final float rotationDegrees;
  public final float rotationPivotX;
  public final float rotationPivotY;
  public final boolean hasRotationPivot;
  public final Bitmap.Config config;
  public final Priority priority;

再来看看Action属性

final Picasso picasso;
  final Request request;
  final WeakReference<T> target;
  final boolean noFade;
  final int memoryPolicy;
  final int networkPolicy;
  final int errorResId;
  final Drawable errorDrawable;
  final String key;
  final Object tag;
  boolean willReplay;
  boolean cancelled;

我们可以看见Request跟注重的是他请求的本身 如:id,请求的来源等等 ,而Action更倾向于一个加载的任务而且它持有Request引用,还有任务的状态,各种策略等,所以是包含关系。

回到代码中:

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

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

因为Target 是所谓的虚引用,然后拿出Target判断是不是和数组里的Target一样不一样就更新。最后交给了
dispatcher.dispatchSubmit(action)

Dispatcher

中文译为分发者即 : 任务分发者

  • 他是在Picassio初始化的时候被创建的是
  • 每一个Dispatcher都关联了 线程池 下载器 缓存 监控器 以及主线程的Handler
  • 他是发动机,也是我们的控制或者说中心
  • Dispatcher内部有一个东西即 DispatcherHandler这是Dispatcher自己的Handler,他绑定的是Dispatcher子线程的Looper

Dispatcher. dispatchSubmit():

void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
  }

而这个handler就是我们之前聊到的DispatcherHandler,相当于把发送的任务交给了子线程,我看看接下来到底是怎样的吧:
这里插一句 : 子线程handleMessage调用performSubmit方法不多讲中间过程不多讲

oid performSubmit(Action action, boolean dismissFailed) {
//注意哦,这里已经不在主线程了,而是在dispatcher线程(HandlerThread)
    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
    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());
    }
  }
  1. 判断任务是不是被暂停了,如果暂停了就放入暂停的容器里
  2. 在通过 keyhuntermap中寻找有没有对应的hunter
  3. 判断是不是线程被关闭,不然就return
  4. 创建对应的Hunter ,等一会儿详细讨论它
  5. 调用直接把Hunter扔进去线程池中,为什么能直接扔,看下面
  6. 然后把Hunterkey为键值对放到hunterMap容器中

BitmapHunter

中文翻译 图像猎人 脑洞真大
先看实现接口吧:

class BitmapHunter implements Runnable

实现了Runable的接口,这说明了上文的为什么可以直接扔进线程池的原因,所以等等我们思路就跟更加明了直接在run方法看线程池执行的过程
不过在这之前我们得看看Hunter是如何创建的:

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

    // Index-based loop to avoid allocating an iterator.
    //noinspection ForLoopReplaceableByForEach
    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);
  }

你还记得之前创建的千万个RequestHandler吗?现在我们正调用canHandleRequest方法,一个一个对比他们的能力是不是和这个任务匹配,如果找到就创建BitmapHunter对象并且传入对应requestHandler引用。

接下来,emm.....看Hunterrun方法

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

我们只用关心两点:

  • 调用hunt “打猎” ,然后传回打猎的结果
  • 如果有结果 调用 dispatcher.dispatchComplete(this)否则调用dispatcher.dispatchFailed(this);

然后看看怎么“打的猎吧”

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

   //缓存没有命中的话,再调用requestHandler.load
   data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
   RequestHandler.Result result = requestHandler.load(data, networkPolicy);
   //拿到结果
   if (result != null) {
     loadedFrom = result.getLoadedFrom();
     exifRotation = result.getExifOrientation();
      //从结果中拿bitmap
     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() || 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;
 }
  • 缓存里面找
  • 没找到调用Handlerload方法了
  • load拿到结果取出 bitmap
  • 查看我们是不是要变化即data.needsTransformation,如果要变化就变化
  • 最后返回bitmapHunter ,调用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");
    }
  }
  • 首先根据缓存策略看是不是缓存
  • 执行完后应该删除hunter在容器中
  • 批量处理hunter

然后看看怎么批量处理的吧:

batch = new ArrayList();
  private void batch(BitmapHunter hunter) {
    if (hunter.isCancelled()) {
      return;
    }
    if (hunter.result != null) {
      hunter.result.prepareToDraw();
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
      handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
  }

看到这里BATCH_DELAY是200ms 意思说在这期间全部返回的结果都交给了batch这个容器,最后延迟200ms发送信息

事情又回到了dispatcher的子线程中,而子线程会调用dispatcher.performComplete(hunter);

  void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
  }
  • 子线程提出批量的BitmapHunter然后清空batch
  • 最后交给主线程批量更新UI

下面就看看主线程吧:

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

不用说逐个调用hunter.picasso.complete(hunter);

 @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    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();

    if (single != null) {
      deliverAction(result, from, single, exception);
    }

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

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

那我们再开看看分发deliverAction方法吧

 private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
    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(result, from);
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
      }
    } else {
      action.error(e);
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_ERRORED, action.request.logId(), e.getMessage());
      }
    }
  }

这里没什么要讲得只是解决前面的判断问题吗,继续轮到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));
  }

  //得到target也就是ImageView
  ImageView target = this.target.get();
  if (target == null) {
    return;
  }

  Context context = picasso.context;
  boolean indicatorsEnabled = picasso.indicatorsEnabled;
  //通过PicassoDrawable来将bitmap设置到ImageView上
  PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

  //回调callback接口
  if (callback != null) {
    callback.onSuccess();
  }
}
  • 判断target很简单,因为他是弱引用,有个能target到达这里已经被回收了
  • PicassoDrawable.setBitmap方法吧bitmap设置给ImageView
  • 调用成功的回调
    特别说一下PicassoDrawable负责入场动画,但是这里不深入
    到这里所以的流程都结束了

我们需要简化总接一下流程即:

创建->入队->执行->解码->变换->批处理->完成->分发->显示(可选)

  • 创建:get()初始化配置,创建picassio,调用load()创建RequestCreater
  • 入队:在into中,RequestCreater创建request,通过request创建一个 Action即 一个完整的任务,交给picasso最后传给dispatcher的子线程,子线程找到对应的ReqestHandler,并打包Action 传给Hunter,最后把Hunter放入线程池入队
  • 解码: 在线程池中,调用ReqestHandlerload方法尝试解码Action
  • 变换: 拿到解码后的结果后判断是不是需要变换,如果需要就进行变化
  • 批处理 :将变换后的结果放入容器batch中,每隔0.2秒线程池就发送给dispatcher的子线程信息,子线程接收到信息后取出batch容器内容,并把它发送给主线程,主线程接受到对象逐个处理容器中每个hunter返回的结果
  • 完成: 上面就会调用picasso.complete(hunter)方法:代表任务执行完成
  • 分发: complete判断是不是需要分发如果需要就调用deliverAction(result, from, join);方法
  • 显示 : 上面任务就会调用action.complete(result, from); 根据action的不同调用对饮的显示方法

设计模式

建造者模式

建造者模式是指:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式应该是我们都比较熟悉的一种模式,在创建AlertDialog的时候通过配置不同的参数就可以展示不同的AlertDialog,这也正是建造者模式的用途,通过不同的参数配置或者不同的执行顺序来构建不同的对象,在Picasso里当构建RequestCreator的时候正是使用了这种设计模式,我们通过可选的配置比如centerInside(),placeholder()等等来分别达到不同的使用方式,在这种使用场景下使用建造者模式是非常合适的.

责任链模式

责任链模式是指:一个请求沿着一条“链”传递,直到该“链”上的某个处理者处理它为止。当我们有一个请求可以被多个处理者处理或处理者未明确指定时。可以选择使用责任链模式,在Picasso里当我们通过BitmapHunter的forRequest()方法构建一个BitmapHunter对象时,需要为Request指定一个对应的RequestHandler来进行处理Request.这里就使用了责任链模式,依次调用requestHandler.canHandleRequest(request)方法来判断是否该RequestHandler能处理该Request如果可以就构建一个RequestHandler对象返回.这里就是典型的责任链思想的体现。

引用:Picasso源代码分析

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

推荐阅读更多精彩内容

  • Picasso,看的版本是v.2.5.2 使用方法,大概这么几种加载资源的形式 还可以对图片进行一些操作:设置大小...
    Jinjins1129阅读 342评论 0 3
  • Picasso 是 Android 开发中最受欢迎的图片请求加载框架之一 ,它诞生于 2013 年,距今已有五年的...
    zhuhean阅读 1,700评论 1 7
  • 概述 在Android开发界,大家都知到square出品必属精品,图片加载库Picasso自然也是。本文就从源码角...
    朔野阅读 650评论 0 7
  • 虽然漏洞百出,好在,贾老师并未深究郑毅一周不见人影这一事件。总算唐塞过去了,郑毅心里充满了自责和悔恨,为了弥补自己...
    樵砥阅读 193评论 0 2
  • 吃一口夏天,再饮南极 红烧驾照,清蒸雪佛兰 干煸支付宝,爆炒苹果 开一瓶XO 马爹利,点支比特币... 哟,老朋友...
    何老师干货2阅读 142评论 0 0