Picasso的使用:使用Transformation,下载后预处理图片并显示

在stackoverflow上闲逛的时候,发现有个哥们问了这么一个问题:Draw a line on ImageView set by Picasso 意思是他想在使用Picasso的时候,对加载的图片做一个类似的滤镜的功能,需要将图片的中间画一条横线,该怎么处理呢? 如果熟悉Picasso的机制,当然可以很快搞定,但如果不是很熟悉呢?最好的办法当然是探索源码,从源码中分析Picasso的原理,进而看看到底是怎么样来实现这个功能。我们从以下的角度来分析:

  • 尽可能使用Picasso线程的API,不去修改Picasso的源代码
  • 既然是图片下载之后添加横线,那么必然对应着Picasso中图片获取完成之后,分发结果的相关代码。

我们现在还没有完全分析完Picasso的源代码,但顺着这个路子,我们依然可以发现关于结果分发的相关代码。 在我们没有特殊的需求之前,我们一般是这样简单的使用Picasso的:

Picasso.with(context).load(“http://www.baidu.com/img/bdlogo.png").into(imageview);

我们依次分析

  • Picasso.with(context)是利用默认的构造器创建一个Picasso的对象,因此我们首先找一找Picasso的类当中,有没有相关的函数。我们来看Picasso的构造函数:

    Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,

    RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers,
    Stats stats, boolean indicatorsEnabled, boolean loggingEnabled) {
    

    从构造函数的名称,可以大体了解每一个参数是做什么使用的,这里面有一个RequestTransformer对象可能有点靠近,那我们去看看这段函数是做什么使用的。

    /**
    * A transformer that is called immediately before every request is submitted. This can be used to
    * modify any information about a request.
    * 请求在提交之前,会立刻调用一个transformer。这个可以用来一个请求的任何信息
    *


    * For example, if you use a CDN you can change the hostname for the image based on the current
    * location of the user in order to get faster download speeds.
    *


    * NOTE: This is a beta feature. The API is subject to change in a backwards incompatible
    * way at any time.
    /
    public interface RequestTransformer {
    /\
    *
    * Transform a request before it is submitted to be processed.
    *
    * @return The original request or a new request to replace it. Must not be null.
    */
    Request transformRequest(Request request);

    /** A {@link RequestTransformer} which returns the original request. /
    /\
    * 返回原始版本的Request */
    RequestTransformer IDENTITY = new RequestTransformer() {

    @Override public Request transformRequest(Request request) {
      return request;
    }
    

    };
    }

    可以看出,这其实并不是对结果的一个预处理,而是对请求的预处理,也就是,如果我们有需要,那么可以在同一的在Volley中,去对输入的请求进行统一的控制,比如本来要访问的域名都是a.happyhls.me,那么我就可以通过这个都给修改为b.happyhls.me,在一个系统中可能多个版本比如v1,v2同时运行的时候,会特别有用。

  • 既然Picasso的类中没有相关的信息,那么我们继续向下观察:

    Picasso.with(context).load(“http://www.baidu.com/img/bdlogo.png")

    根据我们之前的学习,程序执行到这里,其实是构建了一个RequestCreator对象,因此,我们可以继续分析ReqeustCreator对象,有没有可以发现的地方。在其中我们看到

    /**
    * Add a custom transformation to be applied to the image.
    *


    * Custom transformations will always be run after the built-in transformations.
    */
    // TODO show example of calling resize after a transform in the javadoc
    public RequestCreator transform(Transformation transformation) {
    data.transform(transformation);
    return this;
    }

    看到这里,注释很明了了,将需要设置的图片进行一次自定义的变换。作者还标记了一个//TODO,要写一个重新设置大小的例子。当然TODO嘛,肯定是没有。但既然找到地方了,那么我们就自己来写一个就可以了。观察这个方法,其实可以发现其实就是将数据在Transformation中走了一遍。

  • 那么我们继续分析Transformation,看看其中的门道。

    package com.squareup.picasso;

    import android.graphics.Bitmap;

    /** Image transformation. /
    public interface Transformation {
    /\
    *
    * Transform the source bitmap into a new bitmap. If you create a new bitmap instance, you must
    * call {@link android.graphics.Bitmap#recycle()} on {@code source}. You may return the original
    * if no transformation is required.
    */
    Bitmap transform(Bitmap source);

    /**
    * Returns a unique key for the transformation, used for caching purposes. If the transformation
    * has parameters (e.g. size, scale factor, etc) then these should be part of the key.
    */
    String key();
    }

    好了,这里是Transformation接口的全部代码,可以看到,Bitmap transform(Bitmap source)就是我们需要处理的地方,通过实现这个方法,可以将已经解码的图片source,根据自己的需要进行处理,并返回一个新的图片。需要注意的,处理完成,在返回之前,我们必须要通过方法{@link android.graphics.Bitmap#recycle()}将之前的资源回收掉。如果不需要的话,之前返回原始的图片就可以。

综上,我们已经根据代码路径,分析完成我们所需要做的事情,下面就是我写的例子,实现的效果也简单,就是解决上面的问题中提到的,如何在图片的中间加一条横线。上代码,Github

public class PostProcessActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_postprocess);
ImageView imageview = (ImageView)findViewById(R.id.imageview);

Picasso picasso = Picasso.with(getApplicationContext());
DrawLineTransformation myTransformation = new DrawLineTransformation();
picasso.load("http://www.baidu.com/img/bdlogo.png").transform(myTransformation).into(imageview);

}

class DrawLineTransformation implements Transformation {

@Override
public String key() {
  // TODO Auto-generated method stub
  return "drawline";
}

@Override
public Bitmap transform(Bitmap bitmap) {
  // TODO Auto-generated method stub
  synchronized (DrawLineTransformation.class) {
    if(bitmap == null) {
      return null;
    }
    Bitmap resultBitmap = bitmap.copy(bitmap.getConfig(), true);
    Canvas canvas = new Canvas(resultBitmap);
    Paint paint = new Paint();
    paint.setColor(Color.BLUE);
    paint.setStrokeWidth(10);
    canvas.drawLine(0, resultBitmap.getHeight()/2, resultBitmap.getWidth(), resultBitmap.getHeight()/2, paint);
    bitmap.recycle();
    return resultBitmap;
  }
}

}
}

明白思路之后,代码就很简单了,我注意了几个小细节,如果不相近,请朋友们补充:

  • 同解码一样,在处理的时候,添加同步,同一时刻仅仅处理一张图片
  • 像作者注释中写的一样,在新的图片生成之后,要调用bitmap.recycle()方法释放之前的旧图片所占用的空间。

好了,看一下效果: device-2014-11-23-231406 后面我们会和Volley一样,继续分析Picasso的源代码。