Picasso使用解析

概述

picasso是Square公司开源的一个Android图形缓存库,地址http://square.github.io/picasso/,可以实现图片下载和缓存功能。仅仅只需要一行代码就能完全实现图片的异步加载:

Picasso.with(this).load(uri).error(R.mipmap.ic_launcher).into(iv);

还提供了一些辅助功能:

Picasso.with(this).load(uri)
                .placeholder(new ColorDrawable(Color.GRAY))// 占位符:网络请求中显示的图片
                .error(R.mipmap.ic_launcher)// 图片请求异常显示的图片
                .transform(new Transformation() {
                    @Override
                    public Bitmap transform(Bitmap source) {
                        // 在此处可以任意处理图片
                        return null;
                    }
                    @Override
                    public String key() {
                        return null;
                    }
                })// 自定义的图片处理
                .resize(300,300)
                .centerCrop()// 居中显示图片,多余部分裁剪。使用此方法必须先设置resize,并且不能与centerInside一起使用
//                .centerInside()// 显示完整的图片。使用此方法必须先设置resize,并且不能与centerCrop一起使用
//                .fit()// 填充,使用此方法时,不能使用 resize、centerCrop、centerInside 
                .into(iv);

源码分析

按照使用流程进入源码一探究竟

Picasso的初始化

Picasso picasso = Picasso.with(this);

源码

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

典型的单例模式+建造者模式,最后返回Picasso对象。紧接着再看看build()方法里具体的构建过程

public Builder(Context context) {
      if (context == null) {
        throw new IllegalArgumentException("Context must not be null.");
      }
      this.context = context.getApplicationContext();
    }

Builder的构造方法里面,保存了应用程序的全局上下文

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()方法里面初始化了一些默认的配置,下载器、缓存策略、线程池、事务分发器。在此不做具体分析,接着往下看。picasso的load方法:

picasso.load(uri);

可以看到load方法返回了一个RequestCreator 对象。

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

RequestCreator的构造方法

RequestCreator(Picasso picasso, Uri uri, int resourceId) {
    if (picasso.shutdown) {//如果picasso是关闭的,直接抛异常
      throw new IllegalStateException(
          "Picasso instance already shut down. Cannot submit new requests.");
    }
    this.picasso = picasso;//持有picasso对象
    //初始化一个Request.Builder对象
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
  }

Request.Builder():记录了uri,resourceId ,bitmapConfig

 Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
      this.uri = uri;
      this.resourceId = resourceId;
      this.config = bitmapConfig;
    }

到此发现:load方法返回了一个RequestCreator对象,在RequestCreator的构造方法里面又初始化了一个Request.Builder对象。

此处粗略看一下RequestCreator提供了哪些方法

placeholder(int placeholderResId)//设置加载图片中的占位图片,设置一个资源ID
placeholder(Drawable placeholderDrawable)//设置加载图片中的占位图片,设置一个Drawable

error(int errorResId)// 图片加载异常的时候显示的图片
error(Drawable errorDrawable)

tag(Object tag)//设置一个标记(标记是逻辑地关联可以一起管理的相关请求的一种简单方法,例如暂停、恢复或取消。)

resizeDimen(int targetWidthResId, int targetHeightResId)//指定图片大小
resize(int targetWidth, int targetHeight)

centerCrop()//设置图片显示的策略
centerInside()

rotate(float degrees)//设置图片旋转角度
rotate(float degrees, float pivotX, float pivotY)

transform(Transformation transformation)//设置图片转换的自定义策略
transform(List<? extends Transformation> transformations)

fit()//填充ImageView , 不能和resize一起使用
public RequestCreator fit() {
    deferred = true;
    return this;
  }

可以看到上面都是一些我们常用的对图片进行操作的一些API,到此我们知道了RequestCreator提供了一些图片的基本操作,可以设置图片显示的大小,可以对图片做一些自定义的操作。至于 RequestCreator具体是做什么的?Request.Builder是做什么的?我们先放在这里,继续往下看

load方法执行后紧接着是into方法

public void into(ImageView target) {
    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.");
    }

    if (!data.hasImage()) {// 如果图片不存在
      picasso.cancelRequest(target);//取消本次请求
      if (setPlaceholder) {//如果设置了占位图
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }

    if (deferred) {//如果调用了fit()方法
      if (data.hasSize()) {//如果有设置resize()抛出异常
        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);//创建一个Request ,传入一个Request 开始时间
    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());
    }

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

    picasso.enqueueAndSubmit(action);//加入队列并提交
  }

此方法用来创建一个Request

private Request createRequest(long started) {
    int id = nextId.getAndIncrement();

    Request request = data.build();//调用Request.Builder的build方法

    request.id = id;
    request.started = started;
    ...
    省略了调试相关代码,和请求转换器的代码
    ...
    return request ;
  }

到此into()方法的内容看完了,在into()方法中创建了Request和Action对象,调用picasso对象的enqueueAndSubmit()方法加入队列并提交。可以看到Request.Builder是用来创建Request对象的
,前面的问题我们也可有个大致的猜想:RequestCreator字面意思就是一个Request创造者,在RequestCreator的构造方法中创建了Request.Builder对象,并在into()方法中调用了Request.Builder的build()方法,生成了一个Request对象,然后作为参数创建了一个Action对象。到此我们并没有看到具体的网络请求,并且引入了新的问题,Request是做什么的?Action是做什么的?

带着问题我们先来看一下Request.Builder的build()方法

public Request build() {
      if (centerInside && centerCrop) {//这两个不能同时使用
        throw new IllegalStateException("Center crop and center inside can not be used together.");
      }
      //使用centerCrop ,必须先设置resize()
      if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
        throw new IllegalStateException(
            "Center crop requires calling resize with positive width and height.");
      }
      //使用centerInside ,必须先设置resize()
      if (centerInside && (targetWidth == 0 && targetHeight == 0)) {
        throw new IllegalStateException(
            "Center inside requires calling resize with positive width and height.");
      }
      if (priority == null) {
        priority = Priority.NORMAL;//设置优先级
      }
      return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,
          centerCrop, centerInside, onlyScaleDown, rotationDegrees, rotationPivotX, rotationPivotY,
          hasRotationPivot, config, priority);
    }

此方法先是做了一些验证,然后调用了Request的构造方法

private Request(Uri uri, int resourceId, String stableKey, List<Transformation> transformations,
      int targetWidth, int targetHeight, boolean centerCrop, boolean centerInside,
      boolean onlyScaleDown, float rotationDegrees, float rotationPivotX, float rotationPivotY,
      boolean hasRotationPivot, Bitmap.Config config, Priority priority) {
    this.uri = uri;//图片uri
    this.resourceId = resourceId;//图片资源id
    this.stableKey = stableKey;
    if (transformations == null) {
      this.transformations = null;
    } else {
      this.transformations = unmodifiableList(transformations);
    }
    this.targetWidth = targetWidth;//重定义的宽
    this.targetHeight = targetHeight;//重定义的高
    this.centerCrop = centerCrop;
    this.centerInside = centerInside;
    this.onlyScaleDown = onlyScaleDown;
    this.rotationDegrees = rotationDegrees;
    this.rotationPivotX = rotationPivotX;
    this.rotationPivotY = rotationPivotY;
    this.hasRotationPivot = hasRotationPivot;
    this.config = config;
    this.priority = priority;
  }

可以看到,Request就是记录了我们加载图片时设置的一些参数:uri、宽、高、图片显示方式、旋转角度

我们接下来看一下ImageViewAction的构造方法

ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
      int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
      Callback callback, boolean noFade) {
    super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
        tag, noFade);
    this.callback = callback;
  }

此处记录下了图片加载的监听,调用了父类的构造

Action(Picasso picasso, T target, Request request, int memoryPolicy, int networkPolicy,
      int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {
    this.picasso = picasso;
    this.request = request;//一个具体的请求
    this.target =
        target == null ? null : new RequestWeakReference<T>(this, target, picasso.referenceQueue);// into()的目标
    this.memoryPolicy = memoryPolicy;//内存缓存策略
    this.networkPolicy = networkPolicy;//网络缓存策略
    this.noFade = noFade;
    this.errorResId = errorResId;
    this.errorDrawable = errorDrawable;
    this.key = key;
    this.tag = (tag != null ? tag : this);
  }

可以看到Action就是记录了picasso、request、target、memoryPolicy 、networkPolicy 等的对象

我们接下来去看一下picasso的enqueueAndSubmit()方法

void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    //此处判断了一下,target(就是我们要设置图片的ImageView)是否存在,并且该target是否已经有action了,如果有且不等于当前的action,就取消之前的,添加新的action。此处就是对当我们在listview中使用时,会出现的图片错位,重复加载做了优化
    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
  }
void submit(Action action) {
    dispatcher.dispatchSubmit(action);
  }

可以看到,submit()方法中使用了dispatcher来分发了action。

看一下具体的分发逻辑

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

此处发送了一个消息,我们来看一下handler是哪一个

this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);

发现是DispatcherHandler来接受处理的,这是一个子线程中的handler,进入看一下具体的处理方法

@Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
        }
        ....
      }
  }

在此处调用了dispatcher.performSubmit(action);来处理

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

 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;
    }
    //创建一个BitmapHunter对象
    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,这是一个实现了Runnable接口的类,并最终通过线程池提交了该任务

我们查看一下该任务具体的执行方法,在run中调用了hunt() 方法

@Override public void run() {

     .....

      result = hunt();

      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
          // 请求成功后通过事务分发器,分发事务
        dispatcher.dispatchComplete(this);
      }
    .....

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

    .....

    // 在此调用了具体的网络请求方法
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);

    .....

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

具体的分发代码

 case HUNTER_COMPLETE: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;
        }

中间省略,最终调用了下面的代码,交给了picasso中handler处理

 mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));

最终调用了ImageViewAction的complete()方法,

@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {

       ....

    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

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

可以看到最后是调用了PicassoDrawable的setBitmap()方法来设置图片


最后的总结: Picasso通过建造者模式创建了单例,并且初始化了 下载器、内存缓存策略、线程池、事务分发器。并在load方法中创建了Request.Builder,在into()方法中创建了Request和Action,并通过Picasso的enqueueAndSubmit()方法把action加入到队列并提交。再由事务分发器处理,创建出BitmapHunter,并交给线程池处理。在BitmapHunter中,又交给了具体的RequestHandler来处理请求,最后把结果在分发到Picasso中,最后通过PicassoDrawable的setBitmap()方法设置图片。


分析到此就结束了,这只是大体流程的分析,里面有很多具体的技术实现,都没有详细的展开介绍,其中有不对的地方还请见谅,希望能够指出,大家一起进步

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值