您的位置:首页 > 产品设计 > UI/UE

Picasso源码分析(四):不变模式、建造者模式和Request的预处理

2016-06-14 17:06 567 查看
Picasso源码分析(一):单例模式、建造者模式、面向接口编程

Picasso源码分析(二):默认的下载器、缓存、线程池和转换器

Picasso源码分析(三):快照功能实现和HandlerThread的使用

Picasso源码分析(四):不变模式、建造者模式和Request的预处理

Picasso源码分析(五):into方法追本溯源和责任链模式创建BitmapHunter

Picasso源码分析(六):BitmapHunter与请求结果的处理

Request的不变模式(Immutable Pattern)

  不变模式可增强对象的强壮型,允许多个对象共享某一个对象,降低了对该对象进行并发访问时的同步化开销。如果需要修改一个不变对象的状态,那么就需要建立一个新的同类型对象,并在创建时将这个新的状态存储在新对象里。

  不变模式只涉及到一个类。一个类的内部状态创建后,在整个生命周期都不会发生变化时,这样的类称作不变类。

  不变模式的优点:

因为不能修改一个不变对象的状态,所以可以避免由此引起的不必要的程序错误;换言之,一个不变的对象要比可变的对象更加容易维护。

因为没有任何一个线程能够修改不变对象的内部状态,一个不变对象自动就是线程安全的,这样就可以省掉处理同步化的开销。一个不变对象可以自由地被不同的客户端共享。

Picasso中的Request类使用了不变模式,Request类被final修饰,绝大部分属性也被final修饰。final的作用有三个:

final修饰类表示类不可以被继承

final修饰方法表示方法不可被子类覆盖,但可以被继承

final修饰变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

/** Immutable data about an image and the transformations that will be applied to it. */
public final class Request {
...
public final String stableKey;
/** List of custom transformations to be applied after the built-in transformations. */
public final List<Transformation> transformations;
/** Target image width for resizing. */
public final int targetWidth;
/** Target image height for resizing. */
public final int targetHeight;
...


Request类有三个属性并没有被final修饰,因为这三个属性赋值时机并非在构造函数期间或代码块中,所以如果被final修饰就必须在声明时赋值或者在构造或者代码块中赋值,显然逻辑不通,而其他被final修饰的属性均在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;
this.resourceId = resourceId;
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;
}


可见final属性均在构造函数中进行了赋值初始化。

Request的构造函数非常有特色,第一是用private进行修饰,第二是参数非常多,有15个参数,从这两点可以看到Request必然要使用建造者模式创建对象。

Request的建造者模式(Builder Pattern)

由于Request的构造函数参数非常多,因此必然要使用建造者模式来创建Request对象,通过方法链来设置众多属性,属性设置后通过build方法创建Request对象。

/** Builder for creating {@link Request} instances. */
public static final class Builder {
private Uri uri;
private int resourceId;
private String stableKey;
private int targetWidth;
private int targetHeight;
private boolean centerCrop;
private boolean centerInside;
private boolean onlyScaleDown;
private float rotationDegrees;
private float rotationPivotX;
private float rotationPivotY;
private boolean hasRotationPivot;
private List<Transformation> transformations;
private Bitmap.Config config;
private Priority priority;
...


可见Request的内部类Builder的所有属性正是Request的所有final属性。

/**
* The image URI.
* This is mutually exclusive with {@link #resourceId}.
*/
public final Uri uri;
/**
* The image resource ID.
* This is mutually exclusive with {@link #uri}.
*/
public final int resourceId;


对于一个Request来说,其uri和resourceId是互斥的,只能二选一。

Builder内部类保证了二者只能选其一。

/**
* Set the target image Uri.
* This will clear an image resource ID if one is set.
*/
public Builder setUri(Uri uri) {
if (uri == null) {
throw new IllegalArgumentException("Image URI may not be null.");
}
this.uri = uri;
this.resourceId = 0;
return this;
}
/**
* Set the target image resource ID.
* This will clear an image Uri if one is set.
*/
public Builder setResourceId(int resourceId) {
if (resourceId == 0) {
throw new IllegalArgumentException("Image resource ID may not be 0.");
}
this.resourceId = resourceId;
this.uri = null;
return this;
}


以设置优先级为例分析如何通过方法链设置属性

/** Execute request using the specified priority. */
public Builder priority(Priority priority) {
if (priority == null) {
throw new IllegalArgumentException("Priority invalid.");
}
if (this.priority != null) {
throw new IllegalStateException("Priority already set.");
}
this.priority = priority;
return this;
}


其他属性的设置方法类似,都是先检查是不是传入的参数是空指针,是不是重复设置了参数,最后对参数进行赋值,返回this指向自身的引用方便链式调用。

Build方法里边对已经设置的属性进行逻辑检查和默认属性设置,最后通过Request的15个参数的私有构造器进行了Request的对象创建

/** Create the immutable {@link Request} object. */
public Request build() {
if (centerInside && centerCrop) {
throw new IllegalStateException("Center crop and center inside can not be used together.");
}
if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
throw new IllegalStateException(
"Center crop requires calling resize with positive width and height.");
}
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);
}


Picasso中的load方法分析

load方法可以用来加载图片资源,可以从链接Uri、文件路径、文件File和资源id进行资源加载,因此Picasso的图片加载源还是比较灵活多样化的。

而从文件和从文件路径加载图片会先把文件或者路径转换为Uri,然后从Uri加载图片

public RequestCreator load(File file) {
if (file == null) {
return new RequestCreator(this, null, 0);
}
return load(Uri.fromFile(file));
}
public RequestCreator load(String path) {
if (path == null) {
return new RequestCreator(this, null, 0);
}
if (path.trim().length() == 0) {
throw new IllegalArgumentException("Path must not be empty.");
}
return load(Uri.parse(path));
}


而从Uri和资源id加载图片是互斥的,只能二选一,所以从Uri和从资源id去load图片是分开的。

public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
public RequestCreator load(int resourceId) {
if (resourceId == 0) {
throw new IllegalArgumentException("Resource ID must not be zero.");
}
return new RequestCreator(this, null, resourceId);
}


加载图片load方法创建了RequestCreator对象并委托其进行图片的加载,通过构造函数参数的不同来进行两种形式的不同加载。按照Effective Java的建议,此处应该使用静态工厂方式区分两种不同的类型而不是通过构造函数的赋值不同来区分。

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


而RequestCreator又委托Request的Builder进行Request对象的创建。

而通过RequestCreator,就可以为一个请求设置占位图、网络策略和内存策略 等。

占位图设置

以图片加载失败显示的占位图为例

/** An error drawable to be used if the request image could not be loaded. */
public RequestCreator error(Drawable errorDrawable) {
if (errorDrawable == null) {
throw new IllegalArgumentException("Error image may not be null.");
}
if (errorResId != 0) {
throw new IllegalStateException("Error image already set.");
}
this.errorDrawable = errorDrawable;
return this;
}


还是先检查传入的Drawable资源是不是空指针,是不是重复设置,最后返回this引用方便链式调用。

fit方法自动压缩图片

其中fit方法值得一提,调用fit方法可以对图片进行压缩到控件ImageView的尺寸后再显示到控件,节约了内存。这是导致此Request请求延迟执行,直到控件完全绘制出来,绘制后才能知道控件占据的空间大小。

/**
* Attempt to resize the image to fit exactly into the target ImageView's bounds. This
* will result in delayed execution of the request until the  ImageView has been laid out.
* Note: This method works only when your target is an ImageView.
*/
public RequestCreator fit() {
deferred = true;
return this;
}


resize手动压缩图片

fit方法自动压缩图片,而resize方法可以手动压缩图片尺寸

...
private final Request.Builder data;
...
/** Resize the image to the specified size in pixels. */
public RequestCreator resize(int targetWidth, int targetHeight) {
data.resize(targetWidth, targetHeight);
return this;
}


data是一个Request.Builder类型的数据,因此RequestCreator的resize方法实际上是直接委托给了Request.Builder的方法。

还有其他的一些设置比如 centerCrop、centerInside、onlyScaleDown、rotate、config、stableKey、transform和tag方法均是委托给了Request.Builder的方法的方法。

这些方法主要是设置了图片的加载规则和变换规则,这样就对请求进行了配置,配置后就可以愉快地通过into方法进行异步加载图片了,下一节将从into方法开始分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  源码