Picasso从入门到源码解析

Picasso是Squareup公司出的一款图片加载框架,能够解决我们在Android开发中加载图片时遇到的诸多问题,比如OOM,图片错位等,问题主要集中在加载图片列表时,因为单张图片加载谁都会写。如果我们想在ListView或者GridView或者RecyclerView中加载图片墙,那么这个时候对原图片的二次处理就显得非常重要了,否则就会出现我们上文说的OOM或者图片错位等。不过,如果你使用了Picasso来加载图片的话,那么所有问题都会变得很简单。OK,那我们今天就来看看Picasso的使用。

1.基本使用

Picasso加载一张网络图片,最简单的一行代码就搞定:

  1. Picasso.with(this).load(“http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”).into(iv);  
Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg").into(iv);

如果你想对这张图片进行剪裁,可以使用resize方法:

  1. Picasso.with(this).load(“http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  2.                 .resize(200,200)  
  3.                 .into(iv);  
Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .resize(200,200)
                .into(iv);

注意这里的200表示200px,如果你想在resize时指定dp,可以使用如下方法:

  1. Picasso.with(this).load(“http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  2.                 .resizeDimen(R.dimen.iv_width,R.dimen.iv_height)  
  3.                 .into(iv);  
Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .resizeDimen(R.dimen.iv_width,R.dimen.iv_height)
                .into(iv);

在dimen文件中定义宽高即可:

  1. <dimen name=“iv_width”>200dp</dimen>  
  2.     <dimen name=”iv_height”>200dp</dimen>  
<dimen name="iv_width">200dp</dimen>
    <dimen name="iv_height">200dp</dimen>

其实我们看看resizeDimen的源码就知道它是怎么设置dp了:

  1. /** Resize the image to the specified dimension size. */  
  2. public RequestCreator resizeDimen(int targetWidthResId, int targetHeightResId) {  
  3.   Resources resources = picasso.context.getResources();  
  4.   int targetWidth = resources.getDimensionPixelSize(targetWidthResId);  
  5.   int targetHeight = resources.getDimensionPixelSize(targetHeightResId);  
  6.   return resize(targetWidth, targetHeight);  
  7. }  
  /** Resize the image to the specified dimension size. */
  public RequestCreator resizeDimen(int targetWidthResId, int targetHeightResId) {
    Resources resources = picasso.context.getResources();
    int targetWidth = resources.getDimensionPixelSize(targetWidthResId);
    int targetHeight = resources.getDimensionPixelSize(targetHeightResId);
    return resize(targetWidth, targetHeight);
  }

一句话,它就是把dp读取成px然后调用resize方法实现的。

OK,很多时候我还可以给Picasso下载的图片设置缩放模式,也就是ImageView的ScaleType属性(不了解的请移步这里),但是注意,缩放模式centerCrop和centerInside要和resize一起使用,否则会抛异常,而缩放模式fit不可以和resize一起使用,如下:

使用fit:

  1. Picasso.with(this).load(“http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  2.                 .fit()  
  3.                 .into(iv);  
Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .fit()
                .into(iv);

使用centerCrop:

  1. Picasso.with(this).load(“http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  2.         .resizeDimen(R.dimen.iv_width,R.dimen.iv_height)  
  3.         .centerCrop()  
  4.         .into(iv);  
        Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .resizeDimen(R.dimen.iv_width,R.dimen.iv_height)
                .centerCrop()
                .into(iv);

很多时候我们在图片加载出来之前需要先显示一张默认图片,也即占位图,而在图片加载出错的时候我们可能想显示一张错误图,这个Picasso也是支持的:

  1. Picasso.with(this).load(“http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  2.                 //占位图,图片加载出来之前显示的默认图片  
  3.                 .placeholder(R.mipmap.ic_launcher)  
  4.                 //错误图,图片加载出错时显示的图片  
  5.                 .error(R.mipmap.ic_launcher)  
  6.                 .into(iv);  
Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                //占位图,图片加载出来之前显示的默认图片
                .placeholder(R.mipmap.ic_launcher)
                //错误图,图片加载出错时显示的图片
                .error(R.mipmap.ic_launcher)
                .into(iv);

很多时候,我们可能想显示一个用户图像,但是这个用户图像是个圆形图片,这个用Picasso该怎么实现呢?首先定义一个Transformation,在transform方法中对图片进行二次处理,包括剪裁重新处理等等,那我这里想把原图变为一个圆形图,就可以按下面的写法来:

  1. Transformation transformation = new Transformation() {  
  2.             @Override  
  3.             public Bitmap transform(Bitmap source) {  
  4.                 int width = source.getWidth();  
  5.                 int height = source.getHeight();  
  6.                 int size = Math.min(width, height);  
  7.                 Bitmap blankBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);  
  8.                 Canvas canvas = new Canvas(blankBitmap);  
  9.                 Paint paint = new Paint();  
  10.                 paint.setAntiAlias(true);  
  11.                 canvas.drawCircle(size / 2, size / 2, size / 2, paint);  
  12.                 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));  
  13.                 canvas.drawBitmap(source, 00, paint);  
  14.                 if (source != null && !source.isRecycled()) {  
  15.                     source.recycle();  
  16.                 }  
  17.                 return blankBitmap;  
  18.             }  
  19.   
  20.             @Override  
  21.             public String key() {  
  22.                 return “squareup”;  
  23.             }  
  24.         };  
Transformation transformation = new Transformation() {
            @Override
            public Bitmap transform(Bitmap source) {
                int width = source.getWidth();
                int height = source.getHeight();
                int size = Math.min(width, height);
                Bitmap blankBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                Canvas canvas = new Canvas(blankBitmap);
                Paint paint = new Paint();
                paint.setAntiAlias(true);
                canvas.drawCircle(size / 2, size / 2, size / 2, paint);
                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
                canvas.drawBitmap(source, 0, 0, paint);
                if (source != null && !source.isRecycled()) {
                    source.recycle();
                }
                return blankBitmap;
            }

            @Override
            public String key() {
                return "squareup";
            }
        };

paint的setXfermode表示最终显示的图形取所绘制图形的交集,我这里先绘制了圆形,又绘制了一个矩形的Bitmap,圆形没有Bitmap大,所以交集肯定是圆形,所以最终显示结果就为圆形,在加载图片的时候可以通过transform属性来使用自定义的这个transformation,如下:

  1. Picasso.with(this).load(“http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  2.                .transform(transformation)  
  3.                .into(iv);  
 Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .transform(transformation)
                .into(iv);

最终显示结果如下:

依照这个思路,你想把图像做成什么形状都可以了吧!

Picasso还可以通过开启指示器,让你看到这个图片是从内存加载来的还是从SD卡加载来的还是从网络加载来的,设置方式如下:

  1. Picasso picasso = Picasso.with(this);  
  2.        //开启指示器  
  3.        picasso.setIndicatorsEnabled(true);  
  4.        picasso.load(”http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  5.                .into(iv);  
 Picasso picasso = Picasso.with(this);
        //开启指示器
        picasso.setIndicatorsEnabled(true);
        picasso.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .into(iv);

开启之后,图片的加载效果如下:

左上角会有一个蓝色的三角符号,不同的颜色表示图片的来源不同,红、蓝、绿三种颜色分别代表网络、SD卡和内存。

现在大部分的图片缓存框架都是支持三级缓存的,在Picasso中,我们也可以手动设置缓存策略,比如说当我们查看一张大图的时候,可能由于图片太大,不想将其缓存在内存中,那么可以自定义缓存策略,如下:

  1. Picasso picasso = Picasso.with(this);  
  2. //开启指示器  
  3. picasso.setIndicatorsEnabled(true);  
  4. picasso  
  5.         .load(”http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  6.         //第一个参数是指图片加载时放弃在内存缓存中查找  
  7.         //第二个参数是指图片加载完不缓存在内存中  
  8.         .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)  
  9.         .into(iv);  
        Picasso picasso = Picasso.with(this);
        //开启指示器
        picasso.setIndicatorsEnabled(true);
        picasso
                .load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                //第一个参数是指图片加载时放弃在内存缓存中查找
                //第二个参数是指图片加载完不缓存在内存中
                .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
                .into(iv);

当然,如果你想给图片加载过程设置一个监听器也是可以的,如下:

  1. Picasso picasso = Picasso.with(this);  
  2. //开启指示器  
  3. picasso.setIndicatorsEnabled(true);  
  4. picasso  
  5.         .load(”http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  6.         //第一个参数是指图片加载时放弃在内存缓存中查找  
  7.         //第二个参数是指图片加载完不缓存在内存中  
  8.         .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)  
  9.         .into(iv, new Callback() {  
  10.             @Override  
  11.             public void onSuccess() {  
  12.                 Log.d(”google_lenve_fb”“onSuccess: 图片加载成功!”);  
  13.             }  
  14.   
  15.             @Override  
  16.             public void onError() {  
  17.                 Log.d(”google_lenve_fb”“onSuccess: 图片加载失败!”);  
  18.             }  
  19.         });  
        Picasso picasso = Picasso.with(this);
        //开启指示器
        picasso.setIndicatorsEnabled(true);
        picasso
                .load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                //第一个参数是指图片加载时放弃在内存缓存中查找
                //第二个参数是指图片加载完不缓存在内存中
                .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
                .into(iv, new Callback() {
                    @Override
                    public void onSuccess() {
                        Log.d("google_lenve_fb", "onSuccess: 图片加载成功!");
                    }

                    @Override
                    public void onError() {
                        Log.d("google_lenve_fb", "onSuccess: 图片加载失败!");
                    }
                });

在ListView或者RecyclerView中加载图片时,当列表处于滑动状态的时候,我们可以停止图片的加载,当列表停止滚动的时候,我们又可以继续加载图片,如下:

  1. Object tag = new Object();  
  2. Picasso with = Picasso.with(this);  
  3. with.load(”http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”)  
  4.         .into(iv);  
  5. //暂停加载  
  6. with.pauseTag(tag);  
  7. //恢复加载  
  8. with.resumeTag(tag);  
        Object tag = new Object();
        Picasso with = Picasso.with(this);
        with.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .into(iv);
        //暂停加载
        with.pauseTag(tag);
        //恢复加载
        with.resumeTag(tag);

这里要传递的对象可以是任意对象,这两个方法的使用需要我们自己去监听ListView或者GridView的滑动状态。OK,以上这些都属于Picasso的一个基本使用,接下来我们来看看一些高级使用技巧。

2.自定义缓存位置

既然我们知道Picasso自带三级缓存,那么问题就来了,存储在SD卡的图片到底存储在哪里呢?在手机的内部存储中,即  /data/data/应用包名/cache  目录下,这个目录如果你有root权限就可以查看,可是有的时候我们需要自定义缓存位置,即不想将图片缓存在这里,又该怎么办?说到这里,我们不得不来看看Picasso的源码,with方法源码如下:

  1. public static Picasso with(Context context) {  
  2.   if (singleton == null) {  
  3.     synchronized (Picasso.class) {  
  4.       if (singleton == null) {  
  5.         singleton = new Builder(context).build();  
  6.       }  
  7.     }  
  8.   }  
  9.   return singleton;  
  10. }  
  public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }
大家看到,with方法返回了一个Picasso的单例,在创建Picasso的过程中,调用了new Builder(context).build()方法,说明Picasso实例创建的代码在build方法中,那我们再来看看这个build方法:

  1. public Picasso build() {  
  2.   Context context = this.context;  
  3.   
  4.   if (downloader == null) {  
  5.     downloader = Utils.createDefaultDownloader(context);  
  6.   }  
  7.   if (cache == null) {  
  8.     cache = new LruCache(context);  
  9.   }  
  10.   if (service == null) {  
  11.     service = new PicassoExecutorService();  
  12.   }  
  13.   if (transformer == null) {  
  14.     transformer = RequestTransformer.IDENTITY;  
  15.   }  
  16.   
  17.   Stats stats = new Stats(cache);  
  18.   
  19.   Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);  
  20.   
  21.   return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,  
  22.       defaultBitmapConfig, indicatorsEnabled, loggingEnabled);  
  23. }  
    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();
      }
      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这个判断(如果我使用with方法downloader肯定为null),如果downloader为null,则系统会帮我们创建一个默认的downloader,那我们来看看这个默认的downloader是怎么创建的:

  1. static Downloader createDefaultDownloader(Context context) {  
  2.   try {  
  3.     Class.forName(”com.squareup.okhttp.OkHttpClient”);  
  4.     return OkHttpLoaderCreator.create(context);  
  5.   } catch (ClassNotFoundException ignored) {  
  6.   }  
  7.   return new UrlConnectionDownloader(context);  
  8. }  
  static Downloader createDefaultDownloader(Context context) {
    try {
      Class.forName("com.squareup.okhttp.OkHttpClient");
      return OkHttpLoaderCreator.create(context);
    } catch (ClassNotFoundException ignored) {
    }
    return new UrlConnectionDownloader(context);
  }

啊哈,这里就很明白了,系统通过反射来检查我们在项目中是否使用了OkHttp,如果使用了,就使用OkHttp来创建一个下载器,否则就使用HttpUrlConnection来创建一个下载器,可是大家注意Class.forName(“com.squareup.okhttp.OkHttpClient”);这个方法的参数,这是OkHttp3以前的写法,现在我们都是使用OkHttp3了,OkHttp3的包名就不是这个样子,而是okhttp3.OkHttpClient,所以即使你在项目中引用了OkHttp3,Picasso还是会把HttpUrlConnection当作下载器来下载图片的,这个问题估计Picasso会在以后的版本中修正吧!OK,那如果我们想要使用自己的下载器又该怎么做呢?其实很简单,首先不使用with这个方法来初始化Picasso,而是使用Builder来初始化,在初始化的过程中传入自己的下载器,自己的下载器我们可以模仿Picasso里边的这个下载器来写,也可以自定义,我们来看一个Demo:

  1. Picasso picasso = new Picasso.Builder(this)  
  2.                 .downloader(new OkHttp3Downloader(this.getExternalCacheDir()))  
  3.                 .build();  
  4.         Picasso.setSingletonInstance(picasso);  
  5.         picasso.load(”http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”).into(iv);  
Picasso picasso = new Picasso.Builder(this)
                .downloader(new OkHttp3Downloader(this.getExternalCacheDir()))
                .build();
        Picasso.setSingletonInstance(picasso);
        picasso.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg").into(iv);

使用Builder来构建一个Picasso,在构建的过程中传入自己的下载器,这个下载器我没有自己来写,使用GitHub上的开源项目 https://github.com/JakeWharton/picasso2-okhttp3-downloader,里边的代码也都很简单,只有一个类,拷贝到你的项目中就可使用,不赘述。这样修改之后,Picasso的图片缓存位置就发生了改变,存到了   /storage/sdcard/Android/data/应用包名/cache   文件夹中,不同手机这个地址前面一部分可能会有一点点差异。使用这个方法初始化的时候,还调用了setSingletonInstance方法,我们来看看这个方法:

  1. public static void setSingletonInstance(Picasso picasso) {  
  2.   synchronized (Picasso.class) {  
  3.     if (singleton != null) {  
  4.       throw new IllegalStateException(“Singleton instance already exists.”);  
  5.     }  
  6.     singleton = picasso;  
  7.   }  
  8. }  
  public static void setSingletonInstance(Picasso picasso) {
    synchronized (Picasso.class) {
      if (singleton != null) {
        throw new IllegalStateException("Singleton instance already exists.");
      }
      singleton = picasso;
    }
  }

这个主要是用来检查Picasso的单例模式,如果Picasso不是单例的,则LruCache会失效,原因很简单,如果Picasso不是单例的,每一个Picasso都有自己的LruCache,那么LruCache本身的功能当然会失效。这一点需要注意。

3.自定义下载线程池

关于android开发中线程池,如果你还不了解,可以参考Android开发之线程池使用总结,使用Picasso下载图片的时候,系统内部也是有一个线程池,想看这个,我们还是得回到build方法:

  1. public Picasso build() {  
  2.   Context context = this.context;  
  3.   
  4.   if (downloader == null) {  
  5.     downloader = Utils.createDefaultDownloader(context);  
  6.   }  
  7.   if (cache == null) {  
  8.     cache = new LruCache(context);  
  9.   }  
  10.   if (service == null) {  
  11.     service = new PicassoExecutorService();  
  12.   }  
  13.   if (transformer == null) {  
  14.     transformer = RequestTransformer.IDENTITY;  
  15.   }  
  16.   
  17.   Stats stats = new Stats(cache);  
  18.   
  19.   Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);  
  20.   
  21.   return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,  
  22.       defaultBitmapConfig, indicatorsEnabled, loggingEnabled);  
  23. }  
    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();
      }
      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方法中还有一个判断,如果service为null,则新创建一个PicassoExecutorService,我们来看看这个PicassoExecutorService:

  1. class PicassoExecutorService extends ThreadPoolExecutor {  
  2.   private static final int DEFAULT_THREAD_COUNT = 3;  
  3.   
  4.   PicassoExecutorService() {  
  5.     super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,  
  6.         new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());  
  7.   }  
  8.   
  9.   ….  
  10.   ….  
  11. }   
class PicassoExecutorService extends ThreadPoolExecutor {
  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());
  }

  ....
  ....
} 

大家看到,这个PicassoExecutorService继承自ThreadPoolExecutor这个线程池,线程池中的核心线程数为3,线程池的最大线程数也为3,说明线程池中没有非核心线程,线程队列使用了PriorityBlockingQueue,说明所有加载进来的任务都将实现Comparator接口。OK,这是系统默认帮我们创建的线程池,如果你想修改,可以在创建Picasso实例的时候传入自己的线程池:

  1. int CPU_COUNT = Runtime.getRuntime().availableProcessors();  
  2. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CPU_COUNT + 1, CPU_COUNT * 2 + 1,  
  3.         1, TimeUnit.MINUTES, new PriorityBlockingQueue<Runnable>());  
  4. Picasso picasso = new Picasso.Builder(this)  
  5.         .executor(threadPoolExecutor)  
  6.         .downloader(new OkHttp3Downloader(this.getExternalCacheDir()))  
  7.         .build();  
  8. Picasso.setSingletonInstance(picasso);  
  9. picasso.load(”http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg”).into(iv);  
        int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CPU_COUNT + 1, CPU_COUNT * 2 + 1,
                1, TimeUnit.MINUTES, new PriorityBlockingQueue<Runnable>());
        Picasso picasso = new Picasso.Builder(this)
                .executor(threadPoolExecutor)
                .downloader(new OkHttp3Downloader(this.getExternalCacheDir()))
                .build();
        Picasso.setSingletonInstance(picasso);
        picasso.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg").into(iv);

对线程池的创建如果还不了解的话,请参考 Android开发之线程池使用总结

4.自定义下载进度条

我在之前的一篇文章中专门介绍过自定义进度条,没看过的小伙伴请戳这里Android自定义View之ProgressBar出场记。那我们今天就给Picasso加载图片的过程也来一个进度条,先来看看显示效果吧:


整体思路其实很简单,最关键是你要会用OkHttp。

经过上文的讲解,小伙伴们已经知道,我可以在构造一个Picasso实例的时候给它设置一个下载器,这个下载器是由OkHttp实现的,在这个下载器中我可以修改Picasso所加载图片的存储位置,同理,下载器中我也可以传递一个OkHttpClient作为构造参数(上文使用了缓存文件夹作为构造参数),我们来看看:

  1. public OkHttp3Downloader(OkHttpClient client) {  
  2.     this.client = client;  
  3.     this.cache = client.cache();  
  4. }  
    public OkHttp3Downloader(OkHttpClient client) {
        this.client = client;
        this.cache = client.cache();
    }

大家看到,如果我使用OkHttpClient作为构造参数,那么缓存位置则为OkHttpClient的缓存地址。而在OkHttpClient中有一个拦截器,我们可以在拦截器中来计算当前下载百分比,整体思路就是这样,我们来看看实现过程:

首先我来定义一个接口,这个接口用来更新我的进度条:

  1. public interface ProgressListener {  
  2.     //定义接口,取值范围为0~100  
  3.     public void update(@IntRange(from = 0, to = 100int progress);  
  4. }  
public interface ProgressListener {
    //定义接口,取值范围为0~100
    public void update(@IntRange(from = 0, to = 100) int progress);
}
然后定义一个OkHttpClient对象,在定义的过程中给OkHttpClient添加拦截器:

  1. OkHttpClient client = new OkHttpClient.Builder()  
  2.                 .addNetworkInterceptor(new Interceptor() {  
  3.                     @Override  
  4.                     public Response intercept(Chain chain) throws IOException {  
  5.                         Response response = chain.proceed(chain.request());  
  6.                         return response.newBuilder()  
  7.                                 .body(new MyProgressbarResponseBody(new ProgressListener() {  
  8.                                     @Override  
  9.                                     public void update(@IntRange(from = 0, to = 100final int progress) {  
  10.                                         //更新进度条  
  11.                                         runOnUiThread(new Runnable() {  
  12.                                             @Override  
  13.                                             public void run() {  
  14.                                                 Log.d(”google_lenve_fb”“run: ” + progress);  
  15.                                                 myPb.setSweepAngle(progress * 360f / 100);  
  16.                                             }  
  17.                                         });  
  18.                                     }  
  19.                                 }, response.body()))  
  20.                                 .build();  
  21.                     }  
  22.                 })  
  23.                 //设置缓存位置,Picasso下载的图片将缓存在这里  
  24.                 .cache(new Cache(this.getExternalCacheDir(), 10 * 1024 * 1024))  
  25.                 .build();  
OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Response response = chain.proceed(chain.request());
                        return response.newBuilder()
                                .body(new MyProgressbarResponseBody(new ProgressListener() {
                                    @Override
                                    public void update(@IntRange(from = 0, to = 100) final int progress) {
                                        //更新进度条
                                        runOnUiThread(new Runnable() {
                                            @Override
                                            public void run() {
                                                Log.d("google_lenve_fb", "run: " + progress);
                                                myPb.setSweepAngle(progress * 360f / 100);
                                            }
                                        });
                                    }
                                }, response.body()))
                                .build();
                    }
                })
                //设置缓存位置,Picasso下载的图片将缓存在这里
                .cache(new Cache(this.getExternalCacheDir(), 10 * 1024 * 1024))
                .build();

大家看到,这里核心的代码要算addNetworkInterceptor中的代码了,OkHttp中的拦截器有点类似于JavaWeb中的过滤器 ,在所有的请求到达Servlet之前,先对其进行一个简单的处理。而OkHttp中的拦截器,我们可以观察,修改请求和响应,大多数情况下我们使用拦截器来添加、移除、转换请求或者响应的头信息。OK,那么在本案例中我重新修改了Response的body属性,给它传入两个参数,一个就是刚刚定义的监听器,还有一个就是response的body,我们来看看这个MyProgressbarResponseBody,如下:

  1. public class MyProgressbarResponseBody extends ResponseBody {  
  2.     private ResponseBody responseBody;  
  3.     private ProgressListener progressListener;  
  4.     private BufferedSource bufferedSource;  
  5.   
  6.     public MyProgressbarResponseBody(ProgressListener progressListener, ResponseBody responseBody) {  
  7.         this.progressListener = progressListener;  
  8.         this.responseBody = responseBody;  
  9.     }  
  10.   
  11.     @Override  
  12.     public MediaType contentType() {  
  13.         return responseBody.contentType();  
  14.     }  
  15.   
  16.     @Override  
  17.     public long contentLength() {  
  18.         return responseBody.contentLength();  
  19.     }  
  20.   
  21.     @Override  
  22.     public BufferedSource source() {  
  23.         if (bufferedSource == null) {  
  24.             bufferedSource = Okio.buffer(source(responseBody.source()));  
  25.         }  
  26.         return bufferedSource;  
  27.     }  
  28.     private Source source(Source source) {  
  29.   
  30.         return new ForwardingSource(source) {  
  31.             long totalBytesRead = 0L;  
  32.   
  33.             @Override  
  34.             public long read(Buffer sink, long byteCount) throws IOException {  
  35.                 long bytesRead = super.read(sink, byteCount);  
  36.                 totalBytesRead += bytesRead != -1 ? bytesRead : 0;  
  37.                 if (progressListener != null) {  
  38.                     progressListener.update(  
  39.                             ((int) ((100 * totalBytesRead) / responseBody.contentLength())));  
  40.                 }  
  41.                 return bytesRead;  
  42.             }  
  43.         };  
  44.     }  
  45. }  
public class MyProgressbarResponseBody extends ResponseBody {
    private ResponseBody responseBody;
    private ProgressListener progressListener;
    private BufferedSource bufferedSource;

    public MyProgressbarResponseBody(ProgressListener progressListener, ResponseBody responseBody) {
        this.progressListener = progressListener;
        this.responseBody = responseBody;
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }
    private Source source(Source source) {

        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                if (progressListener != null) {
                    progressListener.update(
                            ((int) ((100 * totalBytesRead) / responseBody.contentLength())));
                }
                return bytesRead;
            }
        };
    }
}

MyProgressbarResponseBody继承自ResponseBody,并重写它里边的三个方法,分别返回数据类型,数据大小等信息,在source方法中我们来统计当前下载百分比,并且回调监听器中的接口。最后再来看一眼自定义的ProgressBar,对这个如果还不了解,请参考 Android自定义View之ProgressBar出场记

  1. public class MyProgressBar extends View {  
  2.     /** 
  3.      * View默认的宽 
  4.      */  
  5.     private static final int DEFAULTWIDTH = 200;  
  6.     /** 
  7.      * View默认的高度 
  8.      */  
  9.     private static final int DEFAULTHEIGHT = 200;  
  10.     private Paint sweepPaint;  
  11.     private int padding = 20;  
  12.     /** 
  13.      * 内层实体圆的颜色 
  14.      */  
  15.     private int sweepColor = getResources().getColor(R.color.pbColor);  
  16.     /** 
  17.      * 开始绘制的角度 
  18.      */  
  19.     private int startAngle = -90;  
  20.     /** 
  21.      * 已经绘制的角度 
  22.      */  
  23.     private float sweepAngle = 0;  
  24.   
  25.     public MyProgressBar(Context context) {  
  26.         this(context, null);  
  27.     }  
  28.   
  29.     public MyProgressBar(Context context, AttributeSet attrs) {  
  30.         this(context, attrs, 0);  
  31.     }  
  32.   
  33.     public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {  
  34.         super(context, attrs, defStyleAttr);  
  35.         setAlpha(0.8f);  
  36.         sweepPaint = new Paint();  
  37.         sweepPaint.setColor(sweepColor);  
  38.         sweepPaint.setAntiAlias(true);  
  39.     }  
  40.   
  41.     @Override  
  42.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  43.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  44.         //获取宽的测量模式  
  45.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  46.         //获取宽的测量值  
  47.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  48.         //获取高的测量模式  
  49.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  50.         //获取高的测量值  
  51.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  52.         switch (widthMode) {  
  53.             case MeasureSpec.EXACTLY:  
  54.                 break;  
  55.             case MeasureSpec.AT_MOST:  
  56.             case MeasureSpec.UNSPECIFIED:  
  57.                 //如果宽为wrap_content,则给定一个默认值  
  58.                 widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTWIDTH, getResources().getDisplayMetrics());  
  59.                 break;  
  60.         }  
  61.         switch (heightMode) {  
  62.             case MeasureSpec.EXACTLY:  
  63.                 break;  
  64.             case MeasureSpec.AT_MOST:  
  65.             case MeasureSpec.UNSPECIFIED:  
  66.                 heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTHEIGHT, getResources().getDisplayMetrics());  
  67.                 break;  
  68.         }  
  69.         widthSize = heightSize = Math.min(widthSize, heightSize);  
  70.         //设置测量结果  
  71.         setMeasuredDimension(widthSize, heightSize);  
  72.     }  
  73.   
  74.     @Override  
  75.     protected void onDraw(Canvas canvas) {  
  76.         if (sweepAngle != 360 && sweepAngle != 0) {  
  77.             RectF oval = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);  
  78.             Log.d(”google_lenve_fb”“onDraw: ” + sweepAngle);  
  79.             canvas.drawArc(oval, startAngle, sweepAngle, true, sweepPaint);  
  80.         }  
  81.     }  
  82.   
  83.     public void setSweepAngle(float sweepAngle) {  
  84.         this.sweepAngle = sweepAngle;  
  85.         if (Build.VERSION.SDK_INT > 15) {  
  86.             postInvalidateOnAnimation();  
  87.         } else {  
  88.             ViewCompat.postInvalidateOnAnimation(this);  
  89.         }  
  90.     }  
  91. }  
public class MyProgressBar extends View {
    /**
     * View默认的宽
     */
    private static final int DEFAULTWIDTH = 200;
    /**
     * View默认的高度
     */
    private static final int DEFAULTHEIGHT = 200;
    private Paint sweepPaint;
    private int padding = 20;
    /**
     * 内层实体圆的颜色
     */
    private int sweepColor = getResources().getColor(R.color.pbColor);
    /**
     * 开始绘制的角度
     */
    private int startAngle = -90;
    /**
     * 已经绘制的角度
     */
    private float sweepAngle = 0;

    public MyProgressBar(Context context) {
        this(context, null);
    }

    public MyProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setAlpha(0.8f);
        sweepPaint = new Paint();
        sweepPaint.setColor(sweepColor);
        sweepPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取宽的测量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        //获取宽的测量值
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //获取高的测量模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //获取高的测量值
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                //如果宽为wrap_content,则给定一个默认值
                widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTWIDTH, getResources().getDisplayMetrics());
                break;
        }
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTHEIGHT, getResources().getDisplayMetrics());
                break;
        }
        widthSize = heightSize = Math.min(widthSize, heightSize);
        //设置测量结果
        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (sweepAngle != 360 && sweepAngle != 0) {
            RectF oval = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
            Log.d("google_lenve_fb", "onDraw: " + sweepAngle);
            canvas.drawArc(oval, startAngle, sweepAngle, true, sweepPaint);
        }
    }

    public void setSweepAngle(float sweepAngle) {
        this.sweepAngle = sweepAngle;
        if (Build.VERSION.SDK_INT > 15) {
            postInvalidateOnAnimation();
        } else {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}

最后,加载一张网络图片几个,注意下载器的创建方式:

  1. Picasso picasso = new Picasso  
  2.            .Builder(this)  
  3.            .downloader(new OkHttp3Downloader(client))  
  4.            .build();  
     Picasso picasso = new Picasso
                .Builder(this)
                .downloader(new OkHttp3Downloader(client))
                .build();

5.Picasso源码剖析

其实我们在上文已经涉及到一些源码方面的东西了,那么接下来我们就来理一理Picasso加载图片的整体思路,首先还是先从with方法开始,进入到build方法中:

  1. public Picasso build() {  
  2.   Context context = this.context;  
  3.   
  4.   if (downloader == null) {  
  5.     downloader = Utils.createDefaultDownloader(context);  
  6.   }  
  7.   if (cache == null) {  
  8.     cache = new LruCache(context);  
  9.   }  
  10.   if (service == null) {  
  11.     service = new PicassoExecutorService();  
  12.   }  
  13.   if (transformer == null) {  
  14.     transformer = RequestTransformer.IDENTITY;  
  15.   }  
  16.   
  17.   Stats stats = new Stats(cache);  
  18.   
  19.   Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);  
  20.   
  21.   return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,  
  22.       defaultBitmapConfig, indicatorsEnabled, loggingEnabled);  
  23. }  
    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();
      }
      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方法中前面几个if判断我们刚才已经说过了,这几个判断中的变量我们在创建Picasso实例的时候都可以自定义,也可以使用系统默认创建的,我们再来总结一下:

downloader   创建一个下载器

cache 创建图片的缓存器,默认使用LruCache,这个我们一般不做修改,最多重新配置一下LruCache

service 创建图片下载的线程池

transformer 对Request进行转换,默认不做任何出处理,事实上我们一般也不需要做任何处理。

接下来就是创建一个Stats实例,这个stats主要是用来统计缓存,下载数量等数据,一言以蔽之,就是保存图片的一些状态信息。再之后,则是创建一个Dispatcher,创建Dispatcher的时候还传入了一个HANDLER,这个Handler我们在后文再说,dispatcher顾名思义就是分发,事实上dispatcher主要用来任务调度,这个一会再说,最后new一个Picasso实例返回:

  1. Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,  
  2.     RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,  
  3.     Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {  
  4.   this.context = context;  
  5.   this.dispatcher = dispatcher;  
  6.   this.cache = cache;  
  7.   this.listener = listener;  
  8.   this.requestTransformer = requestTransformer;  
  9.   this.defaultBitmapConfig = defaultBitmapConfig;  
  10.   
  11.   int builtInHandlers = 7// Adjust this as internal handlers are added or removed.  
  12.   int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);  
  13.   List<RequestHandler> allRequestHandlers =  
  14.       new ArrayList<RequestHandler>(builtInHandlers + extraCount);  
  15.   
  16.   // ResourceRequestHandler needs to be the first in the list to avoid  
  17.   // forcing other RequestHandlers to perform null checks on request.uri  
  18.   // to cover the (request.resourceId != 0) case.  
  19.   allRequestHandlers.add(new ResourceRequestHandler(context));  
  20.   if (extraRequestHandlers != null) {  
  21.     allRequestHandlers.addAll(extraRequestHandlers);  
  22.   }  
  23.   allRequestHandlers.add(new ContactsPhotoRequestHandler(context));  
  24.   allRequestHandlers.add(new MediaStoreRequestHandler(context));  
  25.   allRequestHandlers.add(new ContentStreamRequestHandler(context));  
  26.   allRequestHandlers.add(new AssetRequestHandler(context));  
  27.   allRequestHandlers.add(new FileRequestHandler(context));  
  28.   allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));  
  29.   requestHandlers = Collections.unmodifiableList(allRequestHandlers);  
  30.   
  31.   this.stats = stats;  
  32.   this.targetToAction = new WeakHashMap<Object, Action>();  
  33.   this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();  
  34.   this.indicatorsEnabled = indicatorsEnabled;  
  35.   this.loggingEnabled = loggingEnabled;  
  36.   this.referenceQueue = new ReferenceQueue<Object>();  
  37.   this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);  
  38.   this.cleanupThread.start();  
  39. }  
  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<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);

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

OK,大家看到在Picasso的构造方法里主要进行了一些变量的初始化,也初始化了RequestHandler,初始化RequestHandler时首先将我们提交进来的requestHandler加入到集合中,然后还往allRequestHandlers中提交了其它的RequestHandler,这些不同的RequestHandler,分别用来处理不同的资源,比如加载相册的图片、加载资产文件夹中的图片、加载网络图片等。

OK,那么到目前为止,我们所看到的都是build方法中引出的源码,执行完build之后,我们接下来该做的就是调用load方法了,不管你在load中传入了什么,最终都会到达下面这个方法:

  1. RequestCreator(Picasso picasso, Uri uri, int resourceId) {  
  2.   if (picasso.shutdown) {  
  3.     throw new IllegalStateException(  
  4.         ”Picasso instance already shut down. Cannot submit new requests.”);  
  5.   }  
  6.   this.picasso = picasso;  
  7.   this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);  
  8. }  
  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);
  }

shutdown属性是判断Picasso实例是否已经停止运行,如果已经shutdown则抛异常,否则将我们即将要加载的图片信息保存在data中,data是一个Request.Builder对象,里边保存了我们所有的图片加载的配置信息,比如你调用了centerCrop方法:

  1. public RequestCreator centerCrop() {  
  2.   data.centerCrop();  
  3.   return this;  
  4. }  
  public RequestCreator centerCrop() {
    data.centerCrop();
    return this;
  }

大家看到这些方法不过都是修改data里边的变量,当所有的配置信息都完成之后,接下载就到into方法了,那么小伙伴们大概也猜到了,真正的图片加载过程是在into方法中完成的,如下:

  1. public void into(ImageView target, Callback callback) {  
  2.   long started = System.nanoTime();  
  3.   checkMain();  
  4.   
  5.   if (target == null) {  
  6.     throw new IllegalArgumentException(“Target must not be null.”);  
  7.   }  
  8.   
  9.   if (!data.hasImage()) {  
  10.     picasso.cancelRequest(target);  
  11.     if (setPlaceholder) {  
  12.       setPlaceholder(target, getPlaceholderDrawable());  
  13.     }  
  14.     return;  
  15.   }  
  16.   
  17.   if (deferred) {  
  18.     if (data.hasSize()) {  
  19.       throw new IllegalStateException(“Fit cannot be used with resize.”);  
  20.     }  
  21.     int width = target.getWidth();  
  22.     int height = target.getHeight();  
  23.     if (width == 0 || height == 0) {  
  24.       if (setPlaceholder) {  
  25.         setPlaceholder(target, getPlaceholderDrawable());  
  26.       }  
  27.       picasso.defer(target, new DeferredRequestCreator(this, target, callback));  
  28.       return;  
  29.     }  
  30.     data.resize(width, height);  
  31.   }  
  32.   
  33.   Request request = createRequest(started);  
  34.   String requestKey = createKey(request);  
  35.   
  36.   if (shouldReadFromMemoryCache(memoryPolicy)) {  
  37.     Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);  
  38.     if (bitmap != null) {  
  39.       picasso.cancelRequest(target);  
  40.       setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);  
  41.       if (picasso.loggingEnabled) {  
  42.         log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), ”from ” + MEMORY);  
  43.       }  
  44.       if (callback != null) {  
  45.         callback.onSuccess();  
  46.       }  
  47.       return;  
  48.     }  
  49.   }  
  50.   
  51.   if (setPlaceholder) {  
  52.     setPlaceholder(target, getPlaceholderDrawable());  
  53.   }  
  54.   
  55.   Action action =  
  56.       new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,  
  57.           errorDrawable, requestKey, tag, callback, noFade);  
  58.   
  59.   picasso.enqueueAndSubmit(action);  
  60. }  
  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);
  }

into方法有点长,但是逻辑还是很清晰,我们来看一下,

首先checkMain方法检查程序是否运行在主线程,接下来target不能为空,这个不用多说,简单。data.hasImage表示是否设置了要加载的图片资源,如果设置了,则返回true,否则返回false。返回false时进入到if判断中,这个时候首先取消加载,然后如果设置了占位图片,就将其显示出来。接下来进入到if(deferred)的判断中,deferred这个变量是在哪里进行初始化的呢?我们来看看这里:

  1. public RequestCreator fit() {  
  2.   deferred = true;  
  3.   return this;  
  4. }  
  5.   
  6. /** Internal use only. Used by {@link DeferredRequestCreator}. */  
  7. RequestCreator unfit() {  
  8.   deferred = false;  
  9.   return this;  
  10. }  
  public RequestCreator fit() {
    deferred = true;
    return this;
  }

  /** Internal use only. Used by {@link DeferredRequestCreator}. */
  RequestCreator unfit() {
    deferred = false;
    return this;
  }

是在我们调用了fit方法的时候,也就是说,如果我们希望我们的图片在加载的过程中能够自由缩放以填满整个ImageView的话,那么就会进入到这个分支中,进来之后首先是判断data.hasSize,我们知道这个是判断图片是否有宽高,我们来看看hasSize方法:

  1. boolean hasSize() {  
  2.   return targetWidth != 0 || targetHeight != 0;  
  3. }  
    boolean hasSize() {
      return targetWidth != 0 || targetHeight != 0;
    }

那么targetWidth和targetHeight又是在什么地方调用的呢?我们不由得想到了resize方法:

  1. public Builder resize(int targetWidth, int targetHeight) {  
  2.   if (targetWidth < 0) {  
  3.     throw new IllegalArgumentException(“Width must be positive number or 0.”);  
  4.   }  
  5.   if (targetHeight < 0) {  
  6.     throw new IllegalArgumentException(“Height must be positive number or 0.”);  
  7.   }  
  8.   if (targetHeight == 0 && targetWidth == 0) {  
  9.     throw new IllegalArgumentException(“At least one dimension has to be positive number.”);  
  10.   }  
  11.   this.targetWidth = targetWidth;  
  12.   this.targetHeight = targetHeight;  
  13.   return this;  
  14. }  
    public Builder resize(int targetWidth, int targetHeight) {
      if (targetWidth < 0) {
        throw new IllegalArgumentException("Width must be positive number or 0.");
      }
      if (targetHeight < 0) {
        throw new IllegalArgumentException("Height must be positive number or 0.");
      }
      if (targetHeight == 0 && targetWidth == 0) {
        throw new IllegalArgumentException("At least one dimension has to be positive number.");
      }
      this.targetWidth = targetWidth;
      this.targetHeight = targetHeight;
      return this;
    }

没错,是这里,那我们在这里可以得出结论了,如果在加载一张图片的是否使用了fit这种缩放模式的话,那么不可以给图片设置resize属性,否则会抛一个Fit cannot be used with resize异常,其实这个也很好理解,你设置了fit就是希望图片自由缩放以便将ImageView填充满,结果又给图片设置了固定大小,那么你到底想怎样?。接下来系统来获取ImageView的宽和高,如果ImageView的宽和高为0的话,则首先把占位图片设置上,然后去监听ImageView的target.getViewTreeObserver().addOnPreDrawListener(this);接口,当ImageView的宽高被赋值之后,继续加载。否则直接设置ImageView的宽高为图片的宽高。OK,以上还都是在做准备工作,一个网络请求还是没有发起。接下来我们就要开始构造请求了,在into方法的第33行,我们构建一个请求,接下来是一个shouldReadFromMemoryCache,看名字就知道是否该从内存中读取图片,如果是,则根据key从Cache中读取一张图片出来,不知道大家是否还记得我们的Cache实际上就是LruCache。
如果从内从中读取到了图片,就取消请求,并把图片设置给ImageView。同时,如果我们设置了回调,则调用回调的onSuccess方法。

接下来55行创建Action,并且将Action添加到一个Picasso的enqueueAndSubmit方法中。接下来我们就来看看这个请求入队的方法:

  1. void enqueueAndSubmit(Action action) {  
  2.   Object target = action.getTarget();  
  3.   if (target != null && targetToAction.get(target) != action) {  
  4.     // This will also check we are on the main thread.  
  5.     cancelExistingRequest(target);  
  6.     targetToAction.put(target, action);  
  7.   }  
  8.   submit(action);  
  9. }  
  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);
  }

首先获取action里边的target,其实就是我们的ImageView,如果这个ImageView不为空,并且该ImageView已经有了一个Action,则取消已经存在的请求,然后重新给该target设置Action,完了之后就是submit了,我们来看看这个submit:

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

咦,dispatcher,大家还记不记得我们是在哪里初始化的dispatcher呢?没错,build方法中,这里调用了dispatcher的dispatchSubmit方法,点击去再看:

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

哦,原来是使用了Handler,发送了一条消息,那我们来找找handler初始化的地方,在Dispatcher类中,Handler通过如下方式初始化:

  1. this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);  
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
竟然不是new一个Handler,DispatcherHandler是什么鬼?来看看:

  1. private static class DispatcherHandler extends Handler {  
  2.   private final Dispatcher dispatcher;  
  3.   
  4.   public DispatcherHandler(Looper looper, Dispatcher dispatcher) {  
  5.     super(looper);  
  6.     this.dispatcher = dispatcher;  
  7.   }  
  8.   
  9.   @Override public void handleMessage(final Message msg) {  
  10.     switch (msg.what) {  
  11.       case REQUEST_SUBMIT: {  
  12.         Action action = (Action) msg.obj;  
  13.         dispatcher.performSubmit(action);  
  14.         break;  
  15.       }  
  16.       case REQUEST_CANCEL: {  
  17.         Action action = (Action) msg.obj;  
  18.         dispatcher.performCancel(action);  
  19.         break;  
  20.       }  
  21.       case TAG_PAUSE: {  
  22.         Object tag = msg.obj;  
  23.         dispatcher.performPauseTag(tag);  
  24.         break;  
  25.       }  
  26.       case TAG_RESUME: {  
  27.         Object tag = msg.obj;  
  28.         dispatcher.performResumeTag(tag);  
  29.         break;  
  30.       }  
  31.       case HUNTER_COMPLETE: {  
  32.         BitmapHunter hunter = (BitmapHunter) msg.obj;  
  33.         dispatcher.performComplete(hunter);  
  34.         break;  
  35.       }  
  36.       case HUNTER_RETRY: {  
  37.         BitmapHunter hunter = (BitmapHunter) msg.obj;  
  38.         dispatcher.performRetry(hunter);  
  39.         break;  
  40.       }  
  41.       case HUNTER_DECODE_FAILED: {  
  42.         BitmapHunter hunter = (BitmapHunter) msg.obj;  
  43.         dispatcher.performError(hunter, false);  
  44.         break;  
  45.       }  
  46.       case HUNTER_DELAY_NEXT_BATCH: {  
  47.         dispatcher.performBatchComplete();  
  48.         break;  
  49.       }  
  50.       case NETWORK_STATE_CHANGE: {  
  51.         NetworkInfo info = (NetworkInfo) msg.obj;  
  52.         dispatcher.performNetworkStateChange(info);  
  53.         break;  
  54.       }  
  55.       case AIRPLANE_MODE_CHANGE: {  
  56.         dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);  
  57.         break;  
  58.       }  
  59.       default:  
  60.         Picasso.HANDLER.post(new Runnable() {  
  61.           @Override public void run() {  
  62.             throw new AssertionError(“Unknown handler message received: ” + msg.what);  
  63.           }  
  64.         });  
  65.     }  
  66.   }  
  67. }  
  private static class DispatcherHandler extends Handler {
    private final Dispatcher dispatcher;

    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;
        }
        case HUNTER_DELAY_NEXT_BATCH: {
          dispatcher.performBatchComplete();
          break;
        }
        case NETWORK_STATE_CHANGE: {
          NetworkInfo info = (NetworkInfo) msg.obj;
          dispatcher.performNetworkStateChange(info);
          break;
        }
        case AIRPLANE_MODE_CHANGE: {
          dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
          break;
        }
        default:
          Picasso.HANDLER.post(new Runnable() {
            @Override public void run() {
              throw new AssertionError("Unknown handler message received: " + msg.what);
            }
          });
      }
    }
  }

DispatcherHandler继承自Handler重写了它里边的方法,顺藤摸瓜,找到属于我们的case,点进去,最终来到了这个方法:

  1. void performSubmit(Action action, boolean dismissFailed) {  
  2.   if (pausedTags.contains(action.getTag())) {  
  3.     pausedActions.put(action.getTarget(), action);  
  4.     if (action.getPicasso().loggingEnabled) {  
  5.       log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),  
  6.           ”because tag ’” + action.getTag() + “’ is paused”);  
  7.     }  
  8.     return;  
  9.   }  
  10.   
  11.   BitmapHunter hunter = hunterMap.get(action.getKey());  
  12.   if (hunter != null) {  
  13.     hunter.attach(action);  
  14.     return;  
  15.   }  
  16.   
  17.   if (service.isShutdown()) {  
  18.     if (action.getPicasso().loggingEnabled) {  
  19.       log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), ”because shut down”);  
  20.     }  
  21.     return;  
  22.   }  
  23.   
  24.   hunter = forRequest(action.getPicasso(), this, cache, stats, action);  
  25.   hunter.future = service.submit(hunter);  
  26.   hunterMap.put(action.getKey(), hunter);  
  27.   if (dismissFailed) {  
  28.     failedActions.remove(action.getTarget());  
  29.   }  
  30.   
  31.   if (action.getPicasso().loggingEnabled) {  
  32.     log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());  
  33.   }  
  34. }  
  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());
    }
  }
一进来,首先判断该请求是否该暂停,接下来关键的是24行,调用forRequest方法给hunter赋值,我们来看看这个forRequest方法:

  1. static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,  
  2.     Action action) {  
  3.   Request request = action.getRequest();  
  4.   List<RequestHandler> requestHandlers = picasso.getRequestHandlers();  
  5.   
  6.   // Index-based loop to avoid allocating an iterator.  
  7.   //noinspection ForLoopReplaceableByForEach  
  8.   for (int i = 0, count = requestHandlers.size(); i < count; i++) {  
  9.     RequestHandler requestHandler = requestHandlers.get(i);  
  10.     if (requestHandler.canHandleRequest(request)) {  
  11.       return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);  
  12.     }  
  13.   }  
  14.   
  15.   return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);  
  16. }  
  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);
  }

这里有一个for循环,for循环中的东西就是我们所有的RequestHandler,然后通过一个if来匹配,看使用那个RequestHandler来处理我们的图片加载。

第25行创建一个BitmapHunter,并在线程池中执行请求,线程池中传入的对象是hunter,那毫无疑问,hunter肯定是实现了Runnable接口的,那接下来就去看看这个BitmapHunter的run方法:

  1. @Override public void run() {  
  2.   try {  
  3.     updateThreadName(data);  
  4.   
  5.     if (picasso.loggingEnabled) {  
  6.       log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));  
  7.     }  
  8.   
  9.     result = hunt();  
  10.   
  11.     if (result == null) {  
  12.       dispatcher.dispatchFailed(this);  
  13.     } else {  
  14.       dispatcher.dispatchComplete(this);  
  15.     }  
  16.   } catch (Downloader.ResponseException e) {  
  17.     if (!e.localCacheOnly || e.responseCode != 504) {  
  18.       exception = e;  
  19.     }  
  20.     dispatcher.dispatchFailed(this);  
  21.   } catch (NetworkRequestHandler.ContentLengthException e) {  
  22.     exception = e;  
  23.     dispatcher.dispatchRetry(this);  
  24.   } catch (IOException e) {  
  25.     exception = e;  
  26.     dispatcher.dispatchRetry(this);  
  27.   } catch (OutOfMemoryError e) {  
  28.     StringWriter writer = new StringWriter();  
  29.     stats.createSnapshot().dump(new PrintWriter(writer));  
  30.     exception = new RuntimeException(writer.toString(), e);  
  31.     dispatcher.dispatchFailed(this);  
  32.   } catch (Exception e) {  
  33.     exception = e;  
  34.     dispatcher.dispatchFailed(this);  
  35.   } finally {  
  36.     Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);  
  37.   }  
  38. }  
  @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);
    }
  }
一进来,先更新线程名称,然后是第9行调用了hunt方法,获取到一个result,这个result是一个Bitmap,如果获取到了Bitmap则调用dispatcher.dispatchComplete方法,否则调用dispatcher.dispatchFailed方法,这两个实际上都是调用了Handler的sendMessage方法,来发送不同的消息做不同处理,我们这里就来看看hunt()方法,看看这个Bitmap到底是怎么获取的:

  1. Bitmap hunt() throws IOException {  
  2.   Bitmap bitmap = null;  
  3.   
  4.   if (shouldReadFromMemoryCache(memoryPolicy)) {  
  5.     bitmap = cache.get(key);  
  6.     if (bitmap != null) {  
  7.       stats.dispatchCacheHit();  
  8.       loadedFrom = MEMORY;  
  9.       if (picasso.loggingEnabled) {  
  10.         log(OWNER_HUNTER, VERB_DECODED, data.logId(), ”from cache”);  
  11.       }  
  12.       return bitmap;  
  13.     }  
  14.   }  
  15.   
  16.   data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;  
  17.   RequestHandler.Result result = requestHandler.load(data, networkPolicy);  
  18.   if (result != null) {  
  19.     loadedFrom = result.getLoadedFrom();  
  20.     exifRotation = result.getExifOrientation();  
  21.   
  22.     bitmap = result.getBitmap();  
  23.   
  24.     // If there was no Bitmap then we need to decode it from the stream.  
  25.     if (bitmap == null) {  
  26.       InputStream is = result.getStream();  
  27.       try {  
  28.         bitmap = decodeStream(is, data);  
  29.       } finally {  
  30.         Utils.closeQuietly(is);  
  31.       }  
  32.     }  
  33.   }  
  34.   
  35.   if (bitmap != null) {  
  36.     if (picasso.loggingEnabled) {  
  37.       log(OWNER_HUNTER, VERB_DECODED, data.logId());  
  38.     }  
  39.     stats.dispatchBitmapDecoded(bitmap);  
  40.     if (data.needsTransformation() || exifRotation != 0) {  
  41.       synchronized (DECODE_LOCK) {  
  42.         if (data.needsMatrixTransform() || exifRotation != 0) {  
  43.           bitmap = transformResult(data, bitmap, exifRotation);  
  44.           if (picasso.loggingEnabled) {  
  45.             log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());  
  46.           }  
  47.         }  
  48.         if (data.hasCustomTransformations()) {  
  49.           bitmap = applyCustomTransformations(data.transformations, bitmap);  
  50.           if (picasso.loggingEnabled) {  
  51.             log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), ”from custom transformations”);  
  52.           }  
  53.         }  
  54.       }  
  55.       if (bitmap != null) {  
  56.         stats.dispatchBitmapTransformed(bitmap);  
  57.       }  
  58.     }  
  59.   }  
  60.   
  61.   return bitmap;  
  62. }  
  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);
    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);
        }
      }
    }

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

首先是判断是否可以从内存中获取这张图片,如果可以,将图片加载出来并返回,并更新stats中相关变量,否则就会来到第17行,从一个RequestHandler中读取,那么RequestHandler是我们在new一个Picasso的时候传入了多个RequestHandler,这里到底是使用哪一个RequestHandler呢?这就和我们上文说的匹配RequestHandler有关了,毫无疑问,我们下载网络图片,当然是匹配NetworkRequestHandler,那我们看看NetworkRequestHandler里边的load方法:

  1. @Override public Result load(Request request, int networkPolicy) throws IOException {  
  2.   Response response = downloader.load(request.uri, request.networkPolicy);  
  3.   if (response == null) {  
  4.     return null;  
  5.   }  
  6.   
  7.   Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;  
  8.   
  9.   Bitmap bitmap = response.getBitmap();  
  10.   if (bitmap != null) {  
  11.     return new Result(bitmap, loadedFrom);  
  12.   }  
  13.   
  14.   InputStream is = response.getInputStream();  
  15.   if (is == null) {  
  16.     return null;  
  17.   }  
  18.   // Sometimes response content length is zero when requests are being replayed. Haven’t found  
  19.   // root cause to this but retrying the request seems safe to do so.  
  20.   if (loadedFrom == DISK && response.getContentLength() == 0) {  
  21.     Utils.closeQuietly(is);  
  22.     throw new ContentLengthException(“Received response with 0 content-length header.”);  
  23.   }  
  24.   if (loadedFrom == NETWORK && response.getContentLength() > 0) {  
  25.     stats.dispatchDownloadFinished(response.getContentLength());  
  26.   }  
  27.   return new Result(is, loadedFrom);  
  28. }  
  @Override public Result load(Request request, int networkPolicy) throws IOException {
    Response response = downloader.load(request.uri, request.networkPolicy);
    if (response == null) {
      return null;
    }

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

    Bitmap bitmap = response.getBitmap();
    if (bitmap != null) {
      return new Result(bitmap, loadedFrom);
    }

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

这个方法里首先调用了downloader里边的load方法,获取一个Response对象,然后再拿到这个response对象里边的Bitmap返回,downloader就是我们在上文说的那个downloader,我们就看那个源码吧,反正和Piasso自带的差不多,看看它里边的load方法:

  1. @Override  
  2. public Response load(Uri uri, int networkPolicy) throws IOException {  
  3.     CacheControl cacheControl = null;  
  4.     if (networkPolicy != 0) {  
  5.         if (NetworkPolicy.isOfflineOnly(networkPolicy)) {  
  6.             cacheControl = CacheControl.FORCE_CACHE;  
  7.         } else {  
  8.             CacheControl.Builder builder = new CacheControl.Builder();  
  9.             if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {  
  10.                 builder.noCache();  
  11.             }  
  12.             if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {  
  13.                 builder.noStore();  
  14.             }  
  15.             cacheControl = builder.build();  
  16.         }  
  17.     }  
  18.   
  19.     Request.Builder builder = new Request.Builder().url(uri.toString());  
  20.     if (cacheControl != null) {  
  21.         builder.cacheControl(cacheControl);  
  22.     }  
  23.   
  24.     okhttp3.Response response = client.newCall(builder.build()).execute();  
  25.     int responseCode = response.code();  
  26.     if (responseCode >= 300) {  
  27.         response.body().close();  
  28.         throw new ResponseException(responseCode + “ ” + response.message(), networkPolicy,  
  29.                 responseCode);  
  30.     }  
  31.   
  32.     boolean fromCache = response.cacheResponse() != null;  
  33.   
  34.     ResponseBody responseBody = response.body();  
  35.     return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());  
  36. }  
    @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();
            }
        }

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

        okhttp3.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());
    }
哈哈,在这里我们总算看到了网络访问的代码了,就是大家熟悉的OkHttp网络请求了,下载到数据之后,再重新new一个Response对象返回。just so so。。。。

现在我们再回到BitmapHunter的run方法中,当成功获取到bitmap之后,接下来调用dispatcher.dispatchComplete(this);发送一条消息:

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

又是Handler,再找:

  1. case HUNTER_COMPLETE: {  
  2.           BitmapHunter hunter = (BitmapHunter) msg.obj;  
  3.           dispatcher.performComplete(hunter);  
  4.           break;  
  5.         }  
case HUNTER_COMPLETE: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;
        }

这里又调用了dispatcher.performComplete方法,点击去看看:

  1. void performComplete(BitmapHunter hunter) {  
  2.   if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {  
  3.     cache.set(hunter.getKey(), hunter.getResult());  
  4.   }  
  5.   hunterMap.remove(hunter.getKey());  
  6.   batch(hunter);  
  7.   if (hunter.getPicasso().loggingEnabled) {  
  8.     log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), ”for completion”);  
  9.   }  
  10. }  
  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");
    }
  }

首先判断了是否该将Bitmap写入到内存缓存中,需要的话就写入,然后是batch方法:

  1. private void batch(BitmapHunter hunter) {  
  2.     if (hunter.isCancelled()) {  
  3.       return;  
  4.     }  
  5.     batch.add(hunter);  
  6.     if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {  
  7.       handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);  
  8.     }  
  9.   }  
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);
    }
  }

首先判断如果hunter已经被取消,则直接返回,否则将hunter加入到batch中,然后判断Handler中是否有一条HUNTER_DELAY_NEXT_BATCH消息,没有的话就发一条,OK,发一条之后,我们来找到相关的case:

  1. case HUNTER_DELAY_NEXT_BATCH: {  
  2.           dispatcher.performBatchComplete();  
  3.           break;  
  4.         }  
case HUNTER_DELAY_NEXT_BATCH: {
          dispatcher.performBatchComplete();
          break;
        }

继续点:

  1. void performBatchComplete() {  
  2.   List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);  
  3.   batch.clear();  
  4.   mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));  
  5.   logBatch(copy);  
  6. }  
  void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
  }

在这里将batch存入到一个新的List集合中,然后mainThreadHandler又发送一条消息,这个mainThreadHandler是什么鬼?不知道大家是否还记得在build方法中我们创建Dispatch实例的时候传入了一个Handler,就是那个在主线程中创建的Handler,在Picasso那个类里边,我们找到了HUNTER_BATCH_COMPLETE这个case:

  1. case HUNTER_BATCH_COMPLETE: {  
  2.           @SuppressWarnings(“unchecked”) List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;  
  3.           //noinspection ForLoopReplaceableByForEach  
  4.           for (int i = 0, n = batch.size(); i < n; i++) {  
  5.             BitmapHunter hunter = batch.get(i);  
  6.             hunter.picasso.complete(hunter);  
  7.           }  
  8.           break;  
  9.         }  
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;
        }

这个case中我们来一条一条的处理batch中的消息,交给picasso的complete方法去处理:

  1. void complete(BitmapHunter hunter) {  
  2.    Action single = hunter.getAction();  
  3.    List<Action> joined = hunter.getActions();  
  4.   
  5.    boolean hasMultiple = joined != null && !joined.isEmpty();  
  6.    boolean shouldDeliver = single != null || hasMultiple;  
  7.   
  8.    if (!shouldDeliver) {  
  9.      return;  
  10.    }  
  11.   
  12.    Uri uri = hunter.getData().uri;  
  13.    Exception exception = hunter.getException();  
  14.    Bitmap result = hunter.getResult();  
  15.    LoadedFrom from = hunter.getLoadedFrom();  
  16.   
  17.    if (single != null) {  
  18.      deliverAction(result, from, single);  
  19.    }  
  20.   
  21.    if (hasMultiple) {  
  22.      //noinspection ForLoopReplaceableByForEach  
  23.      for (int i = 0, n = joined.size(); i < n; i++) {  
  24.        Action join = joined.get(i);  
  25.        deliverAction(result, from, join);  
  26.      }  
  27.    }  
  28.   
  29.    if (listener != null && exception != null) {  
  30.      listener.onImageLoadFailed(this, uri, exception);  
  31.    }  
  32.  }  
 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();

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

    if (hasMultiple) {
      //noinspection ForLoopReplaceableByForEach
      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);
    }
  }

在这里,14行我们拿到Bitmap,17行去派发Action,如果有合并的Action则在25行进行派发,我们来看看这个派发操作:

  1. private void deliverAction(Bitmap result, LoadedFrom from, Action action) {  
  2.   if (action.isCancelled()) {  
  3.     return;  
  4.   }  
  5.   if (!action.willReplay()) {  
  6.     targetToAction.remove(action.getTarget());  
  7.   }  
  8.   if (result != null) {  
  9.     if (from == null) {  
  10.       throw new AssertionError(“LoadedFrom cannot be null.”);  
  11.     }  
  12.     action.complete(result, from);  
  13.     if (loggingEnabled) {  
  14.       log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), ”from ” + from);  
  15.     }  
  16.   } else {  
  17.     action.error();  
  18.     if (loggingEnabled) {  
  19.       log(OWNER_MAIN, VERB_ERRORED, action.request.logId());  
  20.     }  
  21.   }  
  22. }  
  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(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());
      }
    }
  }
第8行,如果Bitmap不为空,则会执行第12行,调用action的complete方法,Action是我们在into方法中创建的,当时new了一个ImageViewAction,所以我们去找ImageViewAction的complete方法:

  1. @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {  
  2.   if (result == null) {  
  3.     throw new AssertionError(  
  4.         String.format(”Attempted to complete action with no result!\n%s”this));  
  5.   }  
  6.   
  7.   ImageView target = this.target.get();  
  8.   if (target == null) {  
  9.     return;  
  10.   }  
  11.   
  12.   Context context = picasso.context;  
  13.   boolean indicatorsEnabled = picasso.indicatorsEnabled;  
  14.   PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);  
  15.   
  16.   if (callback != null) {  
  17.     callback.onSuccess();  
  18.   }  
  19. }  
  @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();
    }
  }

获取到所有信息之后,然后调用PicassoDrawable的setBitmap方法:

  1. static void setBitmap(ImageView target, Context context, Bitmap bitmap,  
  2.     Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {  
  3.   Drawable placeholder = target.getDrawable();  
  4.   if (placeholder instanceof AnimationDrawable) {  
  5.     ((AnimationDrawable) placeholder).stop();  
  6.   }  
  7.   PicassoDrawable drawable =  
  8.       new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);  
  9.   target.setImageDrawable(drawable);  
  10. }  
  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);
    target.setImageDrawable(drawable);
  }

终于看到了给target设置图片的代码了,这里的代码都很简单,不多说。

OK,这就是对Picasso做了一个简单介绍,有问题的小伙伴欢迎留言讨论。


以上。


参考资料

1.http://blog.csdn.net/zxm317122667/article/details/51814095

2.http://www.jianshu.com/p/c2b029f69f52




转载自: http://blog.csdn.net/u012702547/article/details/52273918

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值