前面介紹了比較重要的Picasso
對象的使用,以及他的一些重要方法,其實還有一個對象也很重要 ---- RequestCreator
,在第一篇文章中其實我已經介紹了,RequestCreator
的最終是爲了生成一個Request
對象。java
太多了,可是這些方法其實實現都很是的簡單,舉一個例子。android
public RequestCreator centerCrop() {
data.centerCrop(Gravity.CENTER);
return this;
}
//--------Request.java--------
public Builder centerCrop(int alignGravity) {
if (centerInside) {
throw new IllegalStateException("Center crop can not be used after calling centerInside");
}
centerCrop = true;
centerCropGravity = alignGravity;
return this;
}
複製代碼
其實RequestCreator
的方法每一個都很簡單就是用來給Request.Builder
對象設置參數的。Request.Builder
是用來生成Request
的,因此仍是Request
這個對象,而後來看裏面的一些配置參數,以及參數的做用。canvas
一個個來介紹下,public方法和屬性服務器
public Builder buildUpon() {
return new Builder(this);
}
複製代碼
根據request從新構建出一個新的Request.Builder
對象,能夠在原來的基礎之上再調用鏈式方法建立新的一個Request.Builder
對象。app
public boolean hasSize() {
return targetWidth != 0 || targetHeight != 0;
}
複製代碼
很簡單,就是看你有沒有resize 寬高框架
上面是2個方法,下面說屬性。ide
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;
}
...
複製代碼
在添加centerCrop
的時候必須設置targetWidth
和targetHeight
post
Picasso.get().load("http://i.imgur.com/DvpvklR.png")
.centerCrop()
.into(ivTest)
複製代碼
這樣會報錯, 應該這樣優化
Picasso.get().load("http://i.imgur.com/DvpvklR.png")
.centerCrop()
.resize(100,100)
.into(ivTest)
複製代碼
真正實現centerCrop
是在圖片加載後進行處理。ui
if (data.centerCrop) {
//獲取圖片要縮放的寬和圖片真正的寬的比例
float widthRatio =
targetWidth != 0 ? targetWidth / (float) inWidth : targetHeight / (float) inHeight;
//獲取圖片要縮放的高和圖片真正的高的比例
float heightRatio =
targetHeight != 0 ? targetHeight / (float) inHeight : targetWidth / (float) inWidth;
float scaleX, scaleY;
//若是寬的比例大於高的比例,就說明圖片在y方向上須要被裁減
if (widthRatio > heightRatio) {
int newSize = (int) Math.ceil(inHeight * (heightRatio / widthRatio));
//裁剪後有個對齊的方向,是上對齊,仍是下對齊,默認是中對齊
if ((data.centerCropGravity & Gravity.TOP) == Gravity.TOP) {
drawY = 0;
} else if ((data.centerCropGravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
drawY = inHeight - newSize;
} else {
drawY = (inHeight - newSize) / 2;
}
drawHeight = newSize;
scaleX = widthRatio;
scaleY = targetHeight / (float) drawHeight;
} else if (widthRatio < heightRatio) {
//這裏跟前面是同樣的邏輯,只不過是x軸方向上須要被裁減,以及左對齊,仍是右對齊
int newSize = (int) Math.ceil(inWidth * (widthRatio / heightRatio));
if ((data.centerCropGravity & Gravity.LEFT) == Gravity.LEFT) {
drawX = 0;
} else if ((data.centerCropGravity & Gravity.RIGHT) == Gravity.RIGHT) {
drawX = inWidth - newSize;
} else {
drawX = (inWidth - newSize) / 2;
}
drawWidth = newSize;
scaleX = targetWidth / (float) drawWidth;
scaleY = heightRatio;
} else {
drawX = 0;
drawWidth = inWidth;
scaleX = scaleY = heightRatio;
}
if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {
matrix.preScale(scaleX, scaleY);
}
}
複製代碼
其實這裏的代碼不少,可是咱們能夠從效果上去看,就是對圖片進行了 裁剪和縮放,而後以及對齊。這樣就很清楚了,上面代碼只是爲了獲取到。
centerCropGravity 上面已經介紹了,就是centerCrop的對齊的屬性
centerInside 其實跟centerCrop差很少,就是對圖片的一個適配。
else if (data.centerInside) {
// Keep aspect ratio if one dimension is set to 0
float widthRatio =
targetWidth != 0 ? targetWidth / (float) inWidth : targetHeight / (float) inHeight;
float heightRatio =
targetHeight != 0 ? targetHeight / (float) inHeight : targetWidth / (float) inWidth;
float scale = widthRatio < heightRatio ? widthRatio : heightRatio;
if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {
matrix.preScale(scale, scale);
}
}
複製代碼
代碼其實就很是的簡單,分別獲取到寬高的縮放比例,而後取最小的,這樣保證圖片在imageview裏面都看獲得,而且不變形。
//---------RequestCreator.java---------
//調用鏈式方法,可配置,最終實際上是傳到`Request`對象中
public RequestCreator config(@NonNull Bitmap.Config config) {
data.config(config);
return this;
}
//-------RequestHandler.java--------
static BitmapFactory.Options createBitmapOptions(Request data) {
final boolean justBounds = data.hasSize();
final boolean hasConfig = data.config != null;
BitmapFactory.Options options = null;
if (justBounds || hasConfig || data.purgeable) {
options = new BitmapFactory.Options();
options.inJustDecodeBounds = justBounds;
options.inInputShareable = data.purgeable;
options.inPurgeable = data.purgeable;
if (hasConfig) {
//最終實際上是在建立 BitmapFactory.Options的時候看成參數傳入,這樣子生成的圖片就會按照配置,若是不對圖片質量有很高要求的話能夠選擇RGB_565,節省了一半的內存
options.inPreferredConfig = data.config;
}
}
return options;
}
//---------BitmapFactory.java----------
/** * If this is non-null, the decoder will try to decode into this * internal configuration. If it is null, or the request cannot be met, * the decoder will try to pick the best matching config based on the * system's screen depth, and characteristics of the original image such * as if it has per-pixel alpha (requiring a config that also does). * * Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by * default. */
public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
複製代碼
其實呢這裏已經說的很清楚了,若是配置config,那麼就按你配置的來,若是沒有配置,默認是Bitmap.Config.ARGB_8888
//----------Request.java------------
public Builder rotate(float degrees, float pivotX, float pivotY) {
rotationDegrees = degrees;
rotationPivotX = pivotX;
rotationPivotY = pivotY;
hasRotationPivot = true;
return this;
}
複製代碼
其實這裏就能夠看出4個參數的做用 hasRotationPivot:是否設置了旋轉參數 rotationDegrees:旋轉角度 rotationPivotX,rotationPivotY:旋轉中心點x,y
6.onlyScaleDown
if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {
matrix.preScale(scaleX, scaleY);
}
....
private static boolean shouldResize(boolean onlyScaleDown, int inWidth, int inHeight, int targetWidth, int targetHeight) {
return !onlyScaleDown || (targetWidth != 0 && inWidth > targetWidth)
|| (targetHeight != 0 && inHeight > targetHeight);
}
複製代碼
onlyScaleDown,顧名思義,只容許縮小,因此每次在縮放的時候,先判斷下。
//-------Picasso.java--------
public enum Priority {
LOW,
NORMAL,
HIGH
}
複製代碼
從定義上看是3種優先級,從高到低。在構建Request
對象的時候會給默認值
//--------Request.java--------
public Request build() {
...
if (priority == null) {
priority = Priority.NORMAL;
}
...
複製代碼
那麼設置這個優先級到底有什麼用呢,其實還要看PicassoExecutorService
這個關鍵類
PicassoExecutorService() {
super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
}
複製代碼
這裏很是有意思,在構建一個線程池的時候,建立了一個PriorityBlockingQueue
有優先級的一個阻塞隊列。
@Override
public Future<?> submit(Runnable task) {
//每次執行的是一個PicassoFutureTask任務
PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
execute(ftask);
return ftask;
}
private static final class PicassoFutureTask extends FutureTask<BitmapHunter> implements Comparable<PicassoFutureTask> {
private final BitmapHunter hunter;
PicassoFutureTask(BitmapHunter hunter) {
super(hunter, null);
this.hunter = hunter;
}
//任務提交以後,線程池會一個個進行處理,會對加入的對象作下對比,看哪一個優先級高,就先執行哪一個
@Override
public int compareTo(PicassoFutureTask other) {
Picasso.Priority p1 = hunter.getPriority();
Picasso.Priority p2 = other.hunter.getPriority();
return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
}
}
}
複製代碼
//----------Request.java---------
public Builder purgeable() {
purgeable = true;
return this;
}
複製代碼
其實在第5點的config
裏面是有涉及到purgeable
這個的,其實也是BitmapFactory.Options
的一個參數。 這裏我把另一個參數合起來一塊兒說,就是inInputShareable
,這裏我把源碼的註釋貼出,而後解釋下。
/** * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is * ignored. * * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, if this * is set to true, then the resulting bitmap will allocate its * pixels such that they can be purged if the system needs to reclaim * memory. In that instance, when the pixels need to be accessed again * (e.g. the bitmap is drawn, getPixels() is called), they will be * automatically re-decoded. * * <p>For the re-decode to happen, the bitmap must have access to the * encoded data, either by sharing a reference to the input * or by making a copy of it. This distinction is controlled by * inInputShareable. If this is true, then the bitmap may keep a shallow * reference to the input. If this is false, then the bitmap will * explicitly make a copy of the input data, and keep that. Even if * sharing is allowed, the implementation may still decide to make a * deep copy of the input data.</p> * * <p>While inPurgeable can help avoid big Dalvik heap allocations (from * API level 11 onward), it sacrifices performance predictability since any * image that the view system tries to draw may incur a decode delay which * can lead to dropped frames. Therefore, most apps should avoid using * inPurgeable to allow for a fast and fluid UI. To minimize Dalvik heap * allocations use the {@link #inBitmap} flag instead.</p> * * <p class="note"><strong>Note:</strong> This flag is ignored when used * with {@link #decodeResource(Resources, int, * android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String, * android.graphics.BitmapFactory.Options)}.</p> */
@Deprecated
public boolean inPurgeable;
複製代碼
首先這是棄用的一個屬性,註釋裏也說的很清楚,LOLLIPOP
及以上版本就無效了,在KITKAT
及如下版本,有效。 翻譯下: 若是設置爲true,那麼生成bitmap而去申請的一塊內存,會在系統須要內存的時候,被回收。若是說這個bitmap又被調用拿去使用了,那麼就跟inInputShareable
這個屬性有關,若是這個屬性設置了false,那麼,就會對inputdata作一個深拷貝,若是是true的話,一開始就會先使用input的一個引用,可是最後真正要使用到bitmap的時候,仍是會對inputdata作一次深拷貝。
固然,後面還有最後一句比較關鍵的話Therefore, most apps should avoid using inPurgeable to allow for a fast and fluid UI.
意思就是說,若是你想你的app比較流暢,比較快,那麼你就不要去使用,由於從新去解碼一張圖片是要時間的,這樣極可能就會形成你加載圖片的時候白了幾秒。
固然,在內存緊張的時候是可使用的。
static BitmapFactory.Options createBitmapOptions(Request data) {
...
options.inInputShareable = data.purgeable;
options.inPurgeable = data.purgeable;
...
return options;
}
複製代碼
很顯然Picasso
把inInputShareable
,inPurgeable
綁在了一塊兒,要麼都true,要麼都false
resourceId 若是要直接加載R.mipmap.**,R.drawable.**這種資源的話,這個參數就!=0
stableKey
//key-value存入LruCache中,這裏就是key的生成規則
static String createKey(Request data, StringBuilder builder) {
//先看有沒有設置stableKey,有設置的話,就直接生成跟stableKey有關的一段字符串,後面能夠直接使用stableKey,獲取到須要的value。
if (data.stableKey != null) {
builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
builder.append(data.stableKey);
} else if (data.uri != null) {
//若是有說沒有上面的stableKey,可是有uri,那麼就直接根據uri生成對應的key
String path = data.uri.toString();
builder.ensureCapacity(path.length() + KEY_PADDING);
builder.append(path);
} else {
builder.ensureCapacity(KEY_PADDING);
builder.append(data.resourceId);
}
builder.append(KEY_SEPARATOR);
複製代碼
正常狀況下,其實你的圖片的uri不變的話,直接就uri就夠用了,可是若是你圖片的uri可能會發生改變,而後自己實際上是一張圖片的話,實際上是可使用stableKey。
好比說,你圖片存放了三方服務器,而後可使用http://****/x100/y100
這種裁減的方式的話,那麼就可使用stableKey
,由於你自己原圖是一張,而後呢正常狀況下你可能會下載了不少不通尺寸的圖片,而後根據uri保存在LruCache中。
可是這裏有一個優化的方式了,設置一個stableKey
,只保存一份圖片,而後再對圖片進行縮放或者裁減,這樣就防止存放了不少份不一樣尺寸的圖片。
Picasso.get().load("http://i.imgur.com/DvpvklR.png")
.resize(100,100)
.into(ivTest)
複製代碼
Picasso.get().load("http://i.imgur.com/DvpvklR.png")
.transform(object : Transformation {
override fun transform(source: Bitmap?): Bitmap {
//從新建立一個新的bitmap
val bitmap = Bitmap.createBitmap(source?.width!!, source?.height!!, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val p = Paint(Paint.ANTI_ALIAS_FLAG)
canvas.drawCircle(100f, 100f, 100f, p)
p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(source, 0f, 0f, p)
//這裏必需要對原來的bitmap作recycle,否則會報錯,後面
source?.recycle()
return bitmap
}
override fun key() = "test111"
})
.resize(200, 200)
.into(ivTest)
//-------BitmapHunter.java---------
static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {
for (int i = 0, count = transformations.size(); i < count; i++) {
final Transformation transformation = transformations.get(i);
Bitmap newResult;
try {
newResult = transformation.transform(result);
} catch (final RuntimeException e) {
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new RuntimeException(
"Transformation " + transformation.key() + " crashed with exception.", e);
}
});
return null;
}
if (newResult == null) {
final StringBuilder builder = new StringBuilder() //
.append("Transformation ")
.append(transformation.key())
.append(" returned null after ")
.append(i)
.append(" previous transformation(s).\n\nTransformation list:\n");
for (Transformation t : transformations) {
builder.append(t.key()).append('\n');
}
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new NullPointerException(builder.toString());
}
});
return null;
}
//若是有轉換,必需要對原來的bitmap作recycle
if (newResult == result && result.isRecycled()) {
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new IllegalStateException("Transformation "
+ transformation.key()
+ " returned input Bitmap but recycled it.");
}
});
return null;
}
...
複製代碼
相對來講圖片框架中,Picasso
是比較簡單,比較容易看得懂的,因此,若是想看圖片框架源碼的話,建議能夠從Picasso
源碼入手,先看到一些圖片框架的基本的一些功能,而後能夠嘗試Glide
,Fresco
,那必定會受益不淺的。最終能夠本身上手寫一份,我就是朝着這個目標前進的。fighting!!!