Picasso

图片加载库

Posted by Cc1over on July 24, 2018

Picasso基本使用

0、导入依赖

  implementation 'com.squareup.picasso:picasso:2.5.2'

1、加载显示图片

 Picasso.with(this)
        .load("http://ww3.sinaimg.cn/large/610dc034jw1fasakfvqe1j20u00mhgn2.jpg")
        .into(mImageView);

with(Context) 获取一个Picasso单例,参数是一个Context上下文

load(String) 调用load 方法加载图片

into (ImageView) 将图片显示在对应的View上,可以是ImageView,也可以是实现了Target j接口的自定义View。

重载方法

load(Uri uri) 加载一个以Uri路径给的图片

Uri uri = Uri.parse(ANDROID_RESOURCE + context.getPackageName() + FOREWARD_SLASH + resourceId)
Picasso.with(this).load(uri).into(mImageView);

load(File file)加载File中的图片

  Picasso.with(this).load(file).into(mImageView);

load(int resourceId)加载本地资源图片

  Picasso.with(this).load(R.mipmap.ic_launcher).into(mImageView);

占位图

Picasso.with(MainActivity.this)
       .load(url)
       .placeholder(R.mipmap.ic_launcher)
       .into(headerImage);

异常图

Picasso.with(MainActivity.this)
       .load(url)
       .error(R.drawable.error)
       .into(headerImage);
noPlaceholder

这个方法的意思就是:在调用into的时候明确告诉你没有占位图设置。根据这个方法签名的解释是阻止View被回收的时候Picasso清空target或者设置一个应用的占位图。需要注意的是placeholder和noPlaceholder 不能同时应用在同一个请求上,会抛异常。

noFade

无论你是否设置了占位图,Picasso 从磁盘或者网络加载图片时,into 显示到ImageView 都会有一个简单的渐入过度效果,让你的UI视觉效果更柔顺丝滑一点,如果你不要这个渐入的效果(没有这么坑爹的需求吧!!!),就调用noFade方法。

设置图片的尺寸(Resize)、缩放(Scale)和裁剪(Crop)

Resize(int w,int h用于重新设置尺寸

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .resize(400,200)
                .into(mImageView);

将尺寸写在imens.xml文件中,设置dp单位

// <dimen name="image_width">300dp</dimen><br>
// <dimen name="image_height">200dp</dimen>
Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .resizeDimen(R.dimen.image_width,R.dimen.image_height)
                .into(mImageView);
onlyScaleDown

当调用了resize 方法重新设置图片尺寸的时候,调用onlyScaleDown 方法,只有当原始图片的尺寸大于我们指定的尺寸时,resize才起作用,如:

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .resize(4000,2000)
                .onlyScaleDown()
                .into(mImageView);

图片裁剪 centerCrop() 使用resize 来重新设置图片的尺寸的时候,会发现有些图片拉伸或者扭曲了(使用ImageView的时候碰到过吧),为了避免这种情况,Picasso 同样给我们提供了一个方法,centerCrop,充满ImageView 的边界,居中裁剪。

centerInside

上面的centerCrop是可能看不到全部图片的,如果你想让View将图片展示完全,可以用centerInside,但是如果图片尺寸小于View尺寸的话,是不能充满View边界的。

fit

fit 它会自动测量我们的View的大小,然后内部调用reszie方法把图片裁剪到View的大小,这就帮我们做了计算size和调用resize 这2步。 使用fit 还是会出现拉伸扭曲的情况,因此最好配合前面的centerCrop使用,代码如下:

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .fit()
                .centerCrop()
                .into(mImageView);

特别注意:
1,fit 只对ImageView 有效
2,使用fit时,ImageView 宽和高不能为wrap_content,很好理解,因为它要测量宽高。

Rotation()

图片旋转

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .rotate(180)
                .into(mImageView);

这个方法它是以(0,0)点旋转,但是有些时候我们并不想以(0,0)点旋转,还提供了另外一个方法可以指定原点: rotate(float degrees, float pivotX, float pivotY) 以(pivotX, pivotY)为原点旋转。

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .rotate(180,200,100)
                .into(mImageView);

转换器Transformation!!!

Transformation 这就是Picasso的一个非常强大的功能了,它允许你在load图片 -> into ImageView 中间这个过成对图片做一系列的变换。比如你要做图片高斯模糊、添加圆角、做度灰处理、圆形图片等等都可以通过Transformation来完成。

首先自定义一个Transform

private class customTransformer implements Transformation{

        @Override
        public Bitmap transform(Bitmap source) {
            //在这里可以对Bitmap进行操作,比如改变大小,形状等

            return source;
        }
        @Override
        public String key() {
            return null;
        }
    }

然后在transform方法中进行处理:

 Picasso.with(MainActivity.this)
        .load(url)
        .placeholder(R.mipmap.ic_launcher)
        .transform(new customTransformer())
        .error(R.drawable.error)
        .into(headerImage);
多个Transformation操作的2种应用方式

直接调用多次transform 方法,不会覆盖的。它只是保存到了一个List 里面 接受一个List,将Transformation 放大list 里

请求的优先级

Picasso 为请求设置有优先级,有三种优先级,LOW、NORMAL、HIGH。默认情况下都是NORMAL

public enum Priority {
    LOW,
    NORMAL,
    HIGH
  }

可以通过priority方法设置请求的优先级,这会影响请求的执行顺序,但是这是不能保证的,它只会往高的优先级靠拢。代码如下:

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .priority(Picasso.Priority.HIGH)
               // .priority(Picasso.Priority.LOW)
                .into(mImageView);

Tag管理请求

Picasso允许我们为一个请求设置tag来管理请求,相应的一些处理如下:

1) cancelTag(Object tag) 取消设置了给定tag的所有请求

2) pauseTag(Object tag) 暂停设置了给定tag 的所有请求

3) resumeTag(Object tag) resume 被暂停的给定tag的所有请求

4) tag(Object tag) 为请求设置tag

Picasso.with(this).load(mData.get(position))
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .tag("PhotoTag")
                .into(holder.mImageView);
Tag管理常见的应用场景

图片列表滑动的时候不加载图片,滑动停止后开始加载图片

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                final Picasso picasso = Picasso.with(MainActivity.this);

                if (newState == SCROLL_STATE_IDLE) {
                    picasso.resumeTag("PhotoTag");
                } else {
                    picasso.pauseTag("PhotoTag");
                }
            }
        });

在Activity finish之后用于停止未完成的任务,防止内存泄漏

 @Override
    protected void onDestroy() {
        super.onDestroy();
        Picasso.with(this).cancelTag("PhotoTag");
    }

同步/异步加载图片

同步加载使用get方法,返回一个Bitmap

try {
           Bitmap bitmap =  Picasso.with(this).load(URL).get();
        } catch (IOException e) {
            e.printStackTrace();
        }

异步加载 一般直接加载图片通过into显示到imageView中也是异步的,除此之外还提供了2个异步的方法
1、fetch() 异步方式加载图片
2、fetch(Callback callback) 异步方式加载图片并给一个回调接口。

Picasso.with(this).load(URL).fetch(new Callback() {
            @Override
            public void onSuccess() {
                //加载成功
            }

            @Override
            public void onError() {
              //加载失败
            }
        });

fetch方法在请求成功之后并不会返回一个bitmap,而是将结果存到了缓存,包括磁盘和内存缓存,可以用于预加载。

缓存(Disk和Memory)

默认情况下,Picasso 内存缓存和磁盘缓存都开启了的,也就是加载图片的时候,内存和磁盘都缓存了。
memoryPolicy设置内存缓存策略

NO_CACHE:表示处理请求的时候跳过检查内存缓存

NO_STORE: 请求成功之后,不将最存到内存。

with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE) //静止内存缓存
                .into(mBlurImage);

networkPolicy 设置磁盘缓存策略

NO_CACHE: 表示处理请求的时候跳过处理磁盘缓存

NO_STORE: 表示请求成功后,不将结果缓存到Disk,但是这个只对OkHttp有效。

OFFLINE: 这个就跟 上面两个不一样了,如果networkPolicy方法用的是这个参数,那么Picasso会强制这次请求从缓存中获取结果,不会发起网络请求,不管缓存中能否获取到结果。

with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE)//跳过内存缓存
                .networkPolicy(NetworkPolicy.NO_CACHE)//跳过磁盘缓存
                .into(mBlurImage);

强制从缓存中获取

with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .networkPolicy(NetworkPolicy.OFFLINE)//强制从缓存获取结果
                .into(mBlurImage);

Debug和日志

Picasso也是有三级缓存策略的,Picasso可以通过setIndicatorsEnabled(boolean)展示图片的来源,调用这个方法之后在图片的左上角出现一个带色块的三角形标示,有3种颜色,绿色表示从内存加载、蓝色表示从磁盘加载、红色表示从网络加载。

 Picasso.with(this)
        .setIndicatorsEnabled(true);//显示指示器

Picasso源码解析

Picasso的工作流程

分析一些上面提及到的常见的方法

Picasso.with()

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

这个方法维护一个Picasso的单例,如果还未实例化就通过new Builder(context).build()创建一个singleton并返回,下面来看一下Builder类的实现:

public static class Builder {
	
  public Builder(Context context) {
    if (context == null) {
      throw new IllegalArgumentException("Context must not be null.");
    }
    this.context = context.getApplicationContext();
  }
	
  /** Create the {@link Picasso} instance. */
  public Picasso build() {
    Context context = this.context;
	
    if (downloader == null) {
//创建默认下载器
      downloader = Utils.createDefaultDownloader(context);
    }
    if (cache == null) {
//创建Lru内存缓存
      cache = new LruCache(context);
    }
    if (service == null) {
//创建线程池,默认有3个执行线程,会根据网络状况自动切换线程数
      service = new PicassoExecutorService();
    }
    if (transformer == null) {
//创建默认的transformer,并无实际作用
      transformer = RequestTransformer.IDENTITY;
    }
 //创建stats用于统计缓存,以及缓存命中率,下载数量等等
    Stats stats = new Stats(cache);
 //创建dispatcher对象用于任务的调度
    Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
	
    return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
        defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
  }
}

我们使用Picasso默认配置的时候(当然也可以自定义),最后会调用build()方法并配置好我们需要的各种对象,最后实例化一个Picasso对象并返回。下面就看一下load方法的实现

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

通过load函数,我们最终得到了一个RequestCreator对象,通过这个对象我们就可以定制一些对图片的特殊处理了,再看看RequestCreator的构造方法

equestCreator(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对象,将我们需要加载的图片的信息都保存在data里,在我们通过.centerCrop()或者.transform()等等方法的时候实际上也就是改变data内的对应的变量标识,再到处理的阶段根据这些参数来进行对应的操作,所以在我们调用into()方法之前,所有的操作都是在设定我们需要处理的参数,真正的操作都是有into()方法引起的。
关键来了:into方法

public void into(ImageView target) {
  //传入空的callback
  into(target, null);
}

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
  if (!data.hasImage()) {
    picasso.cancelRequest(target);
    //如果设置占位图片,直接加载并返回
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    return;
  }
  //如果是延时加载,也就是选择了fit()模式
  if (deferred) {
    //fit()模式是适应target的宽高加载,所以并不能手动设置resize,如果设置就抛出异常
    if (data.hasSize()) {
      throw new IllegalStateException("Fit cannot be used with resize.");
    }
    int width = target.getWidth();
    int height = target.getHeight();
    //如果目标ImageView的宽或高现在为0
    if (width == 0 || height == 0) {
      //先设置占位符
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      //监听ImageView的ViewTreeObserver.OnPreDrawListener接口,一旦ImageView
      //的宽高被赋值,就按照ImageView的宽高继续加载.
      picasso.defer(target, new DeferredRequestCreator(this, target, callback));
      return;
    }
    //如果ImageView有宽高就设置设置
    data.resize(width, height);
  }

  //构建Request
  Request request = createRequest(started);
  //构建requestKey
  String requestKey = createKey(request);

  //根据memoryPolicy来决定是否可以从内存里读取
  if (shouldReadFromMemoryCache(memoryPolicy)) {
    //通过LruCache来读取内存里的缓存图片
    Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
    //如果读取到
    if (bitmap != null) {
      //取消target的request
      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对象,由于我们是往ImageView里加载图片,所以这里创建的是一个ImageViewAction对象.
  Action action =
      new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
          errorDrawable, requestKey, tag, callback, noFade);

  //将Action对象入列提交
  picasso.enqueueAndSubmit(action);
}

into方法中的处理流程

  • into会检查当前是否是在主线程上执行。
  • 如果我们没有提供一个图片资源并且有设置placeholder,那么就会把我们设置的placeholder显示出来,并中断执行。
  • defered属性我们一般情况下不需要关注,只有当我们调用了RequestCreator的fit方法时defered才为true,而fit()模式是适应target的宽高加载,所以并不能手动设置resize,如果设置就抛出异常。
  • 接下来就是创建了一个Request对象,我们在前面做得一些设置都会被封装到这个Request对象里面。
  • 检查我们要显示的图片是否可以直接在缓存中获取,如果有就直接显示出来好了。
  • 缓存没命中,那就只能费点事把源图片down下来了。这个过程是异步的,并且通过一个Action来完成请求前后的衔接工作。
小结

至此,Picasso在主线程中的工作就结束了。通过上面的分析,我们看到Picasso的思想还是很清晰的:首先通过Picasso创建了一个RequestCreator对象,通过这个对象我们可以针对不同的场景来设置一些属性,之后创建出Request对象,最后通过Action来确定异步请求并对请求结果做处理。

Picasso的内部构造以及设计思想

Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
    ...
}

Picasso是不允许外部创建的,而是通过单例和构建者模式去进行对象的构建,在Builder中实例化了Picasso所需要的这些组件,这样就可以个性化地去管理需要用到的各个组件。
上面的工作流程中也简介了Builder的各部分重要的组件,其中有一个就是我们的调度器Dispatcher,图片要不要开始下载以及下载后Bitmap的返回都是通过这个调度器来执行的,而通过上面的工作流程也得知到,其实在into的最后调用Picasso的enqueueAndSubmit方法,代码如下:

void enqueueAndSubmit(Action action) {
  Object target = action.getTarget();
  //取消这个target已经有的action.
  if (target != null && targetToAction.get(target) != action) {
    // This will also check we are on the main thread.
    cancelExistingRequest(target);
    targetToAction.put(target, action);
  }
  //提交action
  submit(action);
}
//调用dispatcher来派发action
void submit(Action action) {
  dispatcher.dispatchSubmit(action);
}

可见在enqueueAndSubmit方法的最后是调用了Dispatcher的dispatchSubmit方法,代码如下:

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

附带:Dispatcher的内部构造

Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
      Downloader downloader, Cache cache, Stats stats) {
    this.dispatcherThread = new DispatcherThread();
    this.dispatcherThread.start();
    ...
    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
    ...
}

通过上面的代码可见,在Dispatcher内部,Dispatcher定义了DispatcherThread和DispatcherHandler两个内部类,并在Dispatcher的构造函数中对他们经行了实例化,所有的调度也都是通过handler异步的执行的。
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());
    }
}

简单分析一下方法的逻辑:这里主要是获取到一个BitmapHunter实例,当然在这里做了一些操作:(通过action的key来在hunterMap查找是否有相同的hunter,这个key里保存的是我们的uri或者resourceId和一些参数,如果都是一样就将这些action合并到一个BitmapHunter里去。)然后就交给Picasso的线程池去执行,而接下来加载图片成功或失败的结果也是由调度器去更新UI。

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

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

        result = hunt();
        // 通过Dispatcher来处理结果
        if (result == null) {
            dispatcher.dispatchFailed(this);
        } else {
            dispatcher.dispatchComplete(this);
        }
    } catch() { 
        //各种异常处理
    }

下载成功的时候,也是像上面调度图片加载一样通过handler去发送消息,再到息处理函数进行实际逻辑。

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());
    }
    hunterMap.remove(hunter.getKey());
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
    }
}

批处理:在这里Picasso并不是立即将图片显示出来,而是用到而是用到了一个批处理,其实就是把操作先暂存在一个list中,等空闲的时候再拿出来处理,这样做得好处也是尽量减少主线程的执行时间,一方面防止ANR,另一方面快速返回,响应页面的其他渲染操作,防止卡顿用户界面。

private void batch(BitmapHunter hunter) {
    if (hunter.isCancelled()) {
      return;
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
      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;
        }
        ...
      }
}
void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
}

由上面的三个方法可见,其实最后还是调用了Dispatcher的方法来处理,而又上面Dispatcher的构建过程也知道,其实在Dispatcher的构建过程中会创建一个handlerThread和handler,而handler肯定就是用于不断地派送消息,而这个过程在handlerThread里面去执行的用意也是很明显,因为handlerThread适用于会长时间在后台运行,并且间隔时间内(或适当情况下)会调用的情况(有点扯远了)反正就是目前不在主线程,所以这也是为什么在构建的时候要在外部传进来一个主线程的handler,就是用来更新UI的。然后通过这个发送消息的过程,处理又回到了Picasso,它的其中有一个静态的handler,就可以接收到Dispatcher发来的消息然后进行图片显示的操作

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

来到这里图片显示就完成了,很显然这里通过了PicassoDrawable.setBitmap()将我们的Bitmap设置到了我们的ImageView上就可以带一点渐变加载动画的效果。