詳談Picasso圖片快取庫特點及用法
本文采用研究生論文格式編寫,方便大家閱讀。作者:譚東
摘要
Picasso是美國SQUARE移動支付公司開源的圖片快取載入庫。可以實現圖片下載和快取功能,效率和效能都很不錯。
Square公司官方部落格:http://square.github.io/
Square公司Github地址:https://github.com/square
Square公司Picasso的Wiki地址:http://square.github.io/picasso/
Square公司Picasso的Github地址:https://github.com/square/picasso
1 緒論
Picasso的用法是鏈式呼叫,和Glide用法很像。當然也支援個性化設定,是目前比較流行的圖片快取庫之一。
1.1 Picasso特點
Picasso是全尺寸下載圖片,也就是把要載入的圖片原圖下載快取下來。Picasso預設的快取格式為ARGB_888,相對於RGB_565記憶體佔用高了一半左右,當然圖片質量顯示要高一些。下面看下Picasso的主要特點:
①鏈式呼叫,使用簡單;
②具有一般圖片框架的基礎功能;
③方便的圖片轉換;
④載入過程監聽和錯誤處理;
⑤自動新增磁碟和記憶體二級快取;
⑥支援多種資料來源載入。
Picasso預設不支援Gif圖片載入。Picasso庫很小,類也很少,庫僅118KB大小。最新版目前是2.5.2。
2 Picasso基本用法
2.1 引入Picasso
Jar包下載:https://search.maven.org/remote_content?g=com.squareup.picasso&a=picasso&v=LATEST
Gradle引用:
- compile 'com.squareup.picasso:picasso:2.5.2'
- <dependency>
- <groupId>com.squareup.picasso</groupId>
- <artifactId>picasso</artifactId>
- <version>2.5.2</version>
- </dependency>
2.2 基本載入圖片
圖2.1 Picasso載入圖片
鏈式呼叫,邏輯上比較清晰。
- Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
改變圖片尺寸和填充方式:
- Picasso.with(context)
- .load(url)
- .resize(50, 50)
- .centerCrop()
- .into(imageView)
- public class CropSquareTransformation implements Transformation {
- @Override public Bitmap transform(Bitmap source) {
- int size = Math.min(source.getWidth(), source.getHeight());
- int x = (source.getWidth() - size) / 2;
- int y = (source.getHeight() - size) / 2;
- Bitmap result = Bitmap.createBitmap(source, x, y, size, size);
- if (result != source) {
- source.recycle();
- }
- return result;
- }
- @Override public String key() { return "square()"; }
- }
- Picasso.with(context)
- .load(url)
- .placeholder(R.drawable.user_placeholder)
- .error(R.drawable.user_placeholder_error)
- .into(imageView);
- Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
- Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
- Picasso.with(context).load(new File(...)).into(imageView3);
- Picasso picasso = Picasso.with(this);
- picasso.setIndicatorsEnabled(true);//藍色:從本地快取讀取的圖片,紅色:從網路載入的圖片,綠色:從記憶體快取載入的圖片
- picasso.setLoggingEnabled(true);//日誌除錯模式
圖2.2 Picasso載入圖片來源標識
圖片快取顏色格式配置:
- Picasso.with(context).load("http://square.github.io/picasso/static/sample.png").config(Bitmap.Config.RGB_565)
- .into(imageView);
3 Picasso高階用法
下面的程式碼是實現:
1.自定義Picasso全域性執行緒池
2.自定義Picasso預設快取目錄
3.自定義監聽Picasso載入圖片進度,結合OKHTTP
- package com.tandong.picassodemo;
- import android.app.Application;
- import com.tandong.picassodemo.utils.OkHttp3Downloader;
- import com.tandong.picassodemo.utils.ProgressListener;
- import com.squareup.picasso.Picasso;
- import java.io.IOException;
- import java.util.concurrent.PriorityBlockingQueue;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- import okhttp3.Cache;
- import okhttp3.Interceptor;
- import okhttp3.OkHttpClient;
- import okhttp3.Response;
- /**
- * Created by Administrator on 2017/4/12.
- */
- /**
- * 1.自定義Picasso全域性執行緒池
- * 2.自定義Picasso預設快取目錄
- * 3.自定義監聽Picasso載入圖片進度,結合OKHTTP
- */
- public class BaseApplication extends Application {
- private ThreadPoolExecutor threadPoolExecutor;
- private int cpu = 0;
- private Picasso picasso;
- private OkHttpClient okHttpClient;
- public static ProgressListener progress;
- @Override
- public void onCreate() {
- super.onCreate();
- initPicasso();
- }
- private void initPicasso() {
- okHttpClient = 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 ProgressResponseBody(progress, response.body())).build();
- }
- }).cache(new Cache(getExternalCacheDir(), 10 * 1024 * 1024)).build();
- cpu = Runtime.getRuntime().availableProcessors();
- threadPoolExecutor = new ThreadPoolExecutor(cpu + 1, cpu * 2 + 1, 1, TimeUnit.MINUTES, new PriorityBlockingQueue<Runnable>());
- picasso = new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//預設3個執行緒
- Picasso.setSingletonInstance(picasso);
- }
- public static void setProgressListener(ProgressListener progressListener) {
- progress = progressListener;
- }
- }
build.gradle引用:
- compile 'com.squareup.picasso:picasso:2.5.2'
- compile 'com.squareup.okhttp3:okhttp:3.6.0'
- package com.tandong.picassodemo.utils;
- import android.content.Context;
- import android.net.Uri;
- import android.os.StatFs;
- import com.squareup.picasso.Downloader;
- import com.squareup.picasso.NetworkPolicy;
- import java.io.File;
- import java.io.IOException;
- import okhttp3.Cache;
- import okhttp3.CacheControl;
- import okhttp3.Call;
- import okhttp3.OkHttpClient;
- import okhttp3.Request;
- import okhttp3.ResponseBody;
- /**
- * Created by office on 2017/4/10.
- */
- public final class OkHttp3Downloader implements Downloader {
- private static final String PICASSO_CACHE = "picasso-cache";
- private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB
- private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB
- private static File defaultCacheDir(Context context) {
- File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);
- if (!cache.exists()) {
- //noinspection ResultOfMethodCallIgnored
- cache.mkdirs();
- }
- return cache;
- }
- private static long calculateDiskCacheSize(File dir) {
- long size = MIN_DISK_CACHE_SIZE;
- try {
- StatFs statFs = new StatFs(dir.getAbsolutePath());
- long available = ((long) statFs.getBlockCount()) * statFs.getBlockSize();
- // Target 2% of the total space.
- size = available / 50;
- } catch (IllegalArgumentException ignored) {
- }
- // Bound inside min/max size for disk cache.
- return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);
- }
- /**
- * Creates a {@link Cache} that would have otherwise been created by calling
- * {@link #OkHttp3Downloader(Context)}. This allows you to build your own {@link OkHttpClient}
- * while still getting the default disk cache.
- */
- public static Cache createDefaultCache(Context context) {
- File dir = defaultCacheDir(context);
- return new Cache(dir, calculateDiskCacheSize(dir));
- }
- private static OkHttpClient createOkHttpClient(File cacheDir, long maxSize) {
- return new OkHttpClient.Builder()
- .cache(new Cache(cacheDir, maxSize))
- .build();
- }
- private final Call.Factory client;
- private final Cache cache;
- /**
- * Create new downloader that uses OkHttp. This will install an image cache into your application
- * cache directory.
- */
- public OkHttp3Downloader(Context context) {
- this(defaultCacheDir(context));
- }
- /**
- * Create new downloader that uses OkHttp. This will install an image cache into the specified
- * directory.
- *
- * @param cacheDir The directory in which the cache should be stored
- */
- public OkHttp3Downloader(File cacheDir) {
- this(cacheDir, calculateDiskCacheSize(cacheDir));
- }
- /**
- * Create new downloader that uses OkHttp. This will install an image cache into your application
- * cache directory.
- *
- * @param maxSize The size limit for the cache.
- */
- public OkHttp3Downloader(final Context context, final long maxSize) {
- this(defaultCacheDir(context), maxSize);
- }
- /**
- * Create new downloader that uses OkHttp. This will install an image cache into the specified
- * directory.
- *
- * @param cacheDir The directory in which the cache should be stored
- * @param maxSize The size limit for the cache.
- */
- public OkHttp3Downloader(File cacheDir, long maxSize) {
- this(createOkHttpClient(cacheDir, maxSize));
- }
- public OkHttp3Downloader(OkHttpClient client) {
- this.client = client;
- this.cache = client.cache();
- }
- public OkHttp3Downloader(Call.Factory client) {
- this.client = client;
- this.cache = null;
- }
- @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());
- }
- @Override
- public void shutdown() {
- if (cache != null) {
- try {
- cache.close();
- } catch (IOException ignored) {
- }
- }
- }
- }
自定義ResponseBody,實現對資料下載進度的監聽處理:
- package com.tandong.picassodemo;
- import android.util.Log;
- import com.tandong.picassodemo.utils.ProgressListener;
- import java.io.IOException;
- import okhttp3.MediaType;
- import okhttp3.ResponseBody;
- import okio.Buffer;
- import okio.BufferedSource;
- import okio.ForwardingSource;
- import okio.Okio;
- import okio.Source;
- /**
- * Created by Administrator on 2017/4/14.
- */
- public class ProgressResponseBody extends ResponseBody {
- private ResponseBody responseBody;
- private BufferedSource bufferedSource;
- private ProgressListener progressListener;
- public ProgressResponseBody(ProgressListener progressListener,ResponseBody responseBody) {
- this.responseBody = responseBody;
- this.progressListener=progressListener;
- }
- @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) {
- ForwardingSource forwardingSource = 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;
- progressListener.update((int) ((100 * totalBytesRead) / responseBody.contentLength()));
- Log.i("info", "進度:" + (int) ((100 * totalBytesRead) / responseBody.contentLength()));
- return bytesRead;
- }
- @Override
- public void close() throws IOException {
- super.close();
- }
- };
- return forwardingSource;
- }
- }
- package com.tandong.picassodemo.utils;
- import android.support.annotation.IntRange;
- /**
- * Created by Administrator on 2017/4/14.
- */
- public interface ProgressListener {
- public void update(@IntRange(from = 0, to = 100) int progress);
- }
- package com.tandong.picassodemo.view;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.Paint;
- import android.graphics.RectF;
- import android.support.annotation.IntRange;
- import android.support.v4.view.ViewCompat;
- import android.util.AttributeSet;
- import android.widget.ImageView;
- /**
- * Created by office on 2017/4/10.
- */
- public class PicassoImageView extends ImageView {
- private final int MAX_PROGRESS = 100;
- private Paint mArcPaint;
- private RectF mBound;
- private Paint mCirclePaint;
- private int mProgress = 0;
- public PicassoImageView(Context context) {
- this(context, null, 0);
- }
- public PicassoImageView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public PicassoImageView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init();
- }
- private void init() {
- mArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- mArcPaint.setStrokeWidth(dpToPixel(0.1f, getContext()));
- mArcPaint.setColor(Color.argb(120, 0xff, 0xff, 0xff));
- mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mCirclePaint.setStyle(Paint.Style.STROKE);
- mCirclePaint.setStrokeWidth(dpToPixel(2, getContext()));
- mCirclePaint.setColor(Color.argb(120, 0xff, 0xff, 0xff));
- mBound = new RectF();
- }
- public void setProgress(@IntRange(from = 0, to = MAX_PROGRESS) int mProgress) {
- this.mProgress = mProgress;
- ViewCompat.postInvalidateOnAnimation(this);
- }
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- int min = Math.min(w, h);
- int max = w + h - min;
- int r = min / 5;
- //set up a square in the imageView
- //設定了在圖片中間的一個小圓
- mBound.set(max / 2 - r, min / 2 - r, max / 2 + r, min / 2 + r);
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- //畫進度條, Paint是畫筆,詳見原始碼
- if (mProgress != MAX_PROGRESS && mProgress != 0) {
- float mAngle = mProgress * 360f / MAX_PROGRESS;
- //畫扇型
- canvas.drawArc(mBound, 270, mAngle, true, mArcPaint);
- //畫圓
- canvas.drawCircle(mBound.centerX(), mBound.centerY(), mBound.height() / 2, mCirclePaint);
- }
- }
- private static float scale;
- public static int dpToPixel(float dp, Context context) {
- if (scale == 0) {
- scale = context.getResources().getDisplayMetrics().density;
- }
- return (int) (dp * scale);
- }
- }
Picasso原始碼原理解析:
先看流程圖:
Picasso的原始碼很小,類也很少,相對簡單些。
我們主要關注以下標紅的幾個類。
不是太想寫太多的文字,那就看原始碼分析吧。
先看構造單例模式的Piasso例項:
- /**
- * with方式
- */
- Picasso.with(this).load("http://square.github.io/picasso/static/sample.png").centerCrop().into(imageView);
- /**
- * new with方式
- */
- Picasso picasso = Picasso.with(this);
- /**
- * builder方式
- */
- picasso = new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//預設3個執行緒
- Picasso.setSingletonInstance(picasso);
這幾種方式通過檢視,都是通過一個方式實現的構造,那就是builder方式。
我們按照這句最基本的方式進行原始碼分析。
- Picasso.with(this).load("http://square.github.io/picasso/static/sample.png").centerCrop().into(imageView);
點選with方法進去。
- public static Picasso with(Context context) {
- if (singleton == null) {
- synchronized (Picasso.class) {
- if (singleton == null) {
- singleton = new Builder(context).build();
- }
- }
- }
- return singleton;
- }
- /** Create the {@link Picasso} instance. */
- public Picasso build() {
- Context context = this.context;
- //是否自定義下載器,無的話使用預設下載器
- if (downloader == null) {
- downloader = Utils.createDefaultDownloader(context);
- }
- //是否自定義快取方式,無的話使用預設LruCache
- if (cache == null) {
- cache = new LruCache(context);
- }
- //是否自定義執行緒池,無的話使用預設執行緒池
- if (service == null) {
- service = new PicassoExecutorService();
- }
- //是否自定義轉換器,無的話使用預設轉換器
- if (transformer == null) {
- transformer = RequestTransformer.IDENTITY;
- }
- //統計器,統計快取的命中率
- Stats stats = new Stats(cache);
- //建立Dispather,分發排程器
- Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
- //建立Picasso例項
- return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
- defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
- }
- }
可以看出都是通過builder方式例項化的Picasso類。這裡例項化了Dispatcher和Picasso。
接下來點選load方法進去:
- /**
- * Start an image request using the specified path. This is a convenience method for calling
- * {@link #load(Uri)}.
- * <p>
- * This path may be a remote URL, file resource (prefixed with {@code file:}), content resource
- * (prefixed with {@code content:}), or android resource (prefixed with {@code
- * android.resource:}.
- * <p>
- * Passing {@code null} as a {@code path} will not trigger any request but will set a
- * placeholder, if one is specified.
- *
- * @see #load(Uri)
- * @see #load(File)
- * @see #load(int)
- * @throws IllegalArgumentException if {@code path} is empty or blank string.
- */
- 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));
- }
- /**
- * Start an image request using the specified URI.
- * <p>
- * Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder,
- * if one is specified.
- *
- * @see #load(File)
- * @see #load(String)
- * @see #load(int)
- */
- public RequestCreator load(Uri uri) {
- return new RequestCreator(this, uri, 0);
- }
- 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);
- }
- /** Fluent API for building an image download request. */
- @SuppressWarnings("UnusedDeclaration") // Public API.
- public class RequestCreator {
- private static final AtomicInteger nextId = new AtomicInteger();
- private final Picasso picasso;
- private final Request.Builder data;
- private boolean noFade;
- private boolean deferred;
- private boolean setPlaceholder = true;
- private int placeholderResId;
- private int errorResId;
- private int memoryPolicy;
- private int networkPolicy;
- private Drawable placeholderDrawable;
- private Drawable errorDrawable;
- private Object tag;
我們接下來點選centerCrop方法進去:
- /**
- * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than
- * distorting the aspect ratio. This cropping technique scales the image so that it fills the
- * requested bounds and then crops the extra.
- */
- public RequestCreator centerCrop() {
- data.centerCrop();
- return this;
- }
這個配置也是操作我們的RequestCreator。
最後點選into方法。
- /**
- * Asynchronously fulfills the request into the specified {@link ImageView}.
- * <p>
- * <em>Note:</em> This method keeps a weak reference to the {@link ImageView} instance and will
- * automatically support object recycling.
- */
- public void into(ImageView target) {
- into(target, null);
- }
- **
- * Asynchronously fulfills the request into the specified {@link ImageView} and invokes the
- * target {@link Callback} if it's not {@code null}.
- * <p>
- * <em>Note:</em> The {@link Callback} param is a strong reference and will prevent your
- * {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected. If
- * you use this method, it is <b>strongly</b> recommended you invoke an adjacent
- * {@link Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking.
- */
- 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);//時間戳為Key,建立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());
- }
- //關鍵,建立Action,這裡用的ImageViewAction
- Action action =
- new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
- errorDrawable, requestKey, tag, callback, noFade);
- //把請求封裝成Action,提交分發
- picasso.enqueueAndSubmit(action);
- }
- 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);
- }
- void submit(Action action) {//提交給Dispatcher分發
- dispatcher.dispatchSubmit(action);
- }
- void dispatchSubmit(Action action) {
- handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
- }
- 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);
- }
- });
- }
- }
- }
- 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,核心,它是個執行緒,執行圖片下載編解碼
- 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);//這個service是ExecutorService執行緒池,提交執行下載執行緒
- hunterMap.put(action.getKey(), hunter);
- if (dismissFailed) {
- failedActions.remove(action.getTarget());
- }
- if (action.getPicasso().loggingEnabled) {
- log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
- }
- }
大概看下BitmapHunter原始碼:
- /*
- * Copyright (C) 2013 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.squareup.picasso;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.graphics.Matrix;
- import android.net.NetworkInfo;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.PrintWriter;
- import java.io.StringWriter;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.Future;
- import java.util.concurrent.atomic.AtomicInteger;
- import static com.squareup.picasso.MemoryPolicy.shouldReadFromMemoryCache;
- import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
- import static com.squareup.picasso.Picasso.Priority;
- import static com.squareup.picasso.Picasso.Priority.LOW;
- import static com.squareup.picasso.Utils.OWNER_HUNTER;
- import static com.squareup.picasso.Utils.VERB_DECODED;
- import static com.squareup.picasso.Utils.VERB_EXECUTING;
- import static com.squareup.picasso.Utils.VERB_JOINED;
- import static com.squareup.picasso.Utils.VERB_REMOVED;
- import static com.squareup.picasso.Utils.VERB_TRANSFORMED;
- import static com.squareup.picasso.Utils.getLogIdsForHunter;
- import static com.squareup.picasso.Utils.log;
- class BitmapHunter implements Runnable {
- /**
- * Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since
- * this will only ever happen in background threads we help avoid excessive memory thrashing as
- * well as potential OOMs. Shamelessly stolen from Volley.
- */
- private static final Object DECODE_LOCK = new Object();
- private static final ThreadLocal<StringBuilder> NAME_BUILDER = new ThreadLocal<StringBuilder>() {
- @Override protected StringBuilder initialValue() {
- return new StringBuilder(Utils.THREAD_PREFIX);
- }
- };
- private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger();
- private static final RequestHandler ERRORING_HANDLER = new RequestHandler() {
- @Override public boolean canHandleRequest(Request data) {
- return true;
- }
- @Override public Result load(Request request, int networkPolicy) throws IOException {
- throw new IllegalStateException("Unrecognized type of request: " + request);
- }
- };
- final int sequence;
- final Picasso picasso;
- final Dispatcher dispatcher;
- final Cache cache;
- final Stats stats;
- final String key;
- final Request data;
- final int memoryPolicy;
- int networkPolicy;
- final RequestHandler requestHandler;
- Action action;
- List<Action> actions;
- Bitmap result;
- Future<?> future;
- Picasso.LoadedFrom loadedFrom;
- Exception exception;
- int exifRotation; // Determined during decoding of original resource.
- int retryCount;
- Priority priority;
- BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action,
- RequestHandler requestHandler) {
- this.sequence = SEQUENCE_GENERATOR.incrementAndGet();
- this.picasso = picasso;
- this.dispatcher = dispatcher;
- this.cache = cache;
- this.stats = stats;
- this.action = action;
- this.key = action.getKey();
- this.data = action.getRequest();
- this.priority = action.getPriority();
- this.memoryPolicy = action.getMemoryPolicy();
- this.networkPolicy = action.getNetworkPolicy();
- this.requestHandler = requestHandler;
- this.retryCount = requestHandler.getRetryCount();
- }
- @Override public void run() {
- try {
- updateThreadName(data);
- if (picasso.loggingEnabled) {
- log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
- }
- //這個result是Bitmap,呼叫了hunt方法獲取
- 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);
- }
- }
- 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 {//inputstream解碼
- 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;
- }
- /**
- * Decode a byte stream into a Bitmap. This method will take into account additional information
- * about the supplied request in order to do the decoding efficiently (such as through leveraging
- * {@code inSampleSize}).
- */
- static Bitmap decodeStream(InputStream stream, Request request) throws IOException {
- MarkableInputStream markStream = new MarkableInputStream(stream);
- stream = markStream;
- long mark = markStream.savePosition(65536); // TODO fix this crap.
- final BitmapFactory.Options options = RequestHandler.createBitmapOptions(request);
- final boolean calculateSize = RequestHandler.requiresInSampleSize(options);
- boolean isWebPFile = Utils.isWebPFile(stream);
- markStream.reset(mark);
- // When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash.
- // Decode byte array instead
- if (isWebPFile) {
- byte[] bytes = Utils.toByteArray(stream);
- if (calculateSize) {
- BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
- RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
- request);
- }
- return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
- } else {
- if (calculateSize) {
- BitmapFactory.decodeStream(stream, null, options);
- RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
- request);
- markStream.reset(mark);
- }
- Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
- if (bitmap == null) {
- // Treat null as an IO exception, we will eventually retry.
- throw new IOException("Failed to decode stream.");
- }
- return bitmap;
- }
- }
接下來,看dispatcher派發載入成功訊息的邏輯:
- void dispatchComplete(BitmapHunter hunter) {
- handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
- }
- case HUNTER_COMPLETE: {//載入完成
- BitmapHunter hunter = (BitmapHunter) msg.obj;
- dispatcher.performComplete(hunter);
- break;
- }
- 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");
- }
- }
- 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);
- }
- }
- case HUNTER_DELAY_NEXT_BATCH: {//繼續派發next_batch訊息
- dispatcher.performBatchComplete();
- break;
- }
- void performBatchComplete() {
- List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
- batch.clear();
- mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));//注意這裡的mainThreadHandler是Picasso的Handler
- logBatch(copy);
- }
注意這裡的mainThreadHander是Picasso裡的Handler,我們可以看下Dispatcher的例項化。
- Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
- Downloader downloader, Cache cache, Stats stats) {
- this.dispatcherThread = new DispatcherThread();
- this.dispatcherThread.start();
- Utils.flushStackLocalLeaks(dispatcherThread.getLooper());
- this.context = context;
- this.service = service;
- this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
- this.failedActions = new WeakHashMap<Object, Action>();
- this.pausedActions = new WeakHashMap<Object, Action>();
- this.pausedTags = new HashSet<Object>();
- this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
- this.downloader = downloader;
- this.mainThreadHandler = mainThreadHandler;//這個就是Picasso傳進來的mainThreadHandler
- this.cache = cache;
- this.stats = stats;
- this.batch = new ArrayList<BitmapHunter>(4);
- this.airplaneMode = Utils.isAirplaneModeOn(this.context);
- this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);
- this.receiver = new NetworkBroadcastReceiver(this);
- receiver.register();
- }
那我們看下Picasso裡的HUNTER_BATCH_COMPLETE訊息處理:
- static final String TAG = "Picasso";
- static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
- @Override public void handleMessage(Message msg) {
- switch (msg.what) {
- 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);//回撥了complete方法
- }
- break;
- }
- case REQUEST_GCED: {
- Action action = (Action) msg.obj;
- if (action.getPicasso().loggingEnabled) {
- log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), "target got garbage collected");
- }
- action.picasso.cancelExistingRequest(action.getTarget());
- break;
- }
- case REQUEST_BATCH_RESUME:
- @SuppressWarnings("unchecked") List<Action> batch = (List<Action>) msg.obj;
- //noinspection ForLoopReplaceableByForEach
- for (int i = 0, n = batch.size(); i < n; i++) {
- Action action = batch.get(i);
- action.picasso.resumeAction(action);
- }
- break;
- default:
- throw new AssertionError("Unknown handler message received: " + msg.what);
- }
- }
- };
我們看下Picasso的complete方法:
- void complete(BitmapHunter hunter) {
- Action single = hunter.getAction();//拿到了派發過來的Action
- 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();//拿到了Bitmap
- LoadedFrom from = hunter.getLoadedFrom();
- if (single != null) {
- deliverAction(result, from, single);//執行deliverAction方法
- }
- 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);
- }
- }
- 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);//回撥了action的complete方法
- 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());
- }
- }
- }
- /*
- * Copyright (C) 2013 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.squareup.picasso;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.graphics.drawable.Drawable;
- import android.widget.ImageView;
- class ImageViewAction extends Action<ImageView> {
- Callback callback;
- 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;
- }
- //圖片載入完成回撥
- @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);//設定bitmap給PicassoDrawable
- if (callback != null) {
- callback.onSuccess();
- }
- }
- @Override public void error() {
- ImageView target = this.target.get();
- if (target == null) {
- return;
- }
- if (errorResId != 0) {
- target.setImageResource(errorResId);
- } else if (errorDrawable != null) {
- target.setImageDrawable(errorDrawable);
- }
- if (callback != null) {
- callback.onError();
- }
- }
- @Override void cancel() {
- super.cancel();
- if (callback != null) {
- callback = null;
- }
- }
- }
- /*
- * Copyright (C) 2013 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.squareup.picasso;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.graphics.Canvas;
- import android.graphics.ColorFilter;
- import android.graphics.Paint;
- import android.graphics.Path;
- import android.graphics.Point;
- import android.graphics.Rect;
- import android.graphics.drawable.AnimationDrawable;
- import android.graphics.drawable.BitmapDrawable;
- import android.graphics.drawable.Drawable;
- import android.os.Build;
- import android.os.SystemClock;
- import android.widget.ImageView;
- import static android.graphics.Color.WHITE;
- import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
- final class PicassoDrawable extends BitmapDrawable {//繼承自BitmapDrawable
- // Only accessed from main thread.
- private static final Paint DEBUG_PAINT = new Paint();
- private static final float FADE_DURATION = 200f; //ms
- /**
- * Create or update the drawable on the target {@link ImageView} to display the supplied bitmap
- * image.
- */
- 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);//呼叫ImageView的setImageDrawable方法將轉換後的drawable載入進去顯示
- }
- /**
- * Create or update the drawable on the target {@link ImageView} to display the supplied
- * placeholder image.
- */
- static void setPlaceholder(ImageView target, Drawable placeholderDrawable) {
- target.setImageDrawable(placeholderDrawable);
- if (target.getDrawable() instanceof AnimationDrawable) {
- ((AnimationDrawable) target.getDrawable()).start();
- }
- }
- private final boolean debugging;
- private final float density;
- private final Picasso.LoadedFrom loadedFrom;
- Drawable placeholder;
- long startTimeMillis;
- boolean animating;
- int alpha = 0xFF;
- PicassoDrawable(Context context, Bitmap bitmap, Drawable placeholder,
- Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
- super(context.getResources(), bitmap);
- this.debugging = debugging;
- this.density = context.getResources().getDisplayMetrics().density;
- this.loadedFrom = loadedFrom;
- boolean fade = loadedFrom != MEMORY && !noFade;
- if (fade) {
- this.placeholder = placeholder;
- animating = true;
- startTimeMillis = SystemClock.uptimeMillis();
- }
- }
- @Override public void draw(Canvas canvas) {
- if (!animating) {
- super.draw(canvas);
- } else {
- float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;
- if (normalized >= 1f) {
- animating = false;
- placeholder = null;
- super.draw(canvas);
- } else {
- if (placeholder != null) {
- placeholder.draw(canvas);
- }
- int partialAlpha = (int) (alpha * normalized);
- super.setAlpha(partialAlpha);
- super.draw(canvas);
- super.setAlpha(alpha);
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
- invalidateSelf();
- }
- }
- }
- if (debugging) {
- drawDebugIndicator(canvas);
- }
- }
- @Override public void setAlpha(int alpha) {
- this.alpha = alpha;
- if (placeholder != null) {
- placeholder.setAlpha(alpha);
- }
- super.setAlpha(alpha);
- }
- @Override public void setColorFilter(ColorFilter cf) {
- if (placeholder != null) {
- placeholder.setColorFilter(cf);
- }
- super.setColorFilter(cf);
- }
- @Override protected void onBoundsChange(Rect bounds) {
- if (placeholder != null) {
- placeholder.setBounds(bounds);
- }
- super.onBoundsChange(bounds);
- }
- private void drawDebugIndicator(Canvas canvas) {
- DEBUG_PAINT.setColor(WHITE);
- Path path = getTrianglePath(new Point(0, 0), (int) (16 * density));
- canvas.drawPath(path, DEBUG_PAINT);
- DEBUG_PAINT.setColor(loadedFrom.debugColor);
- path = getTrianglePath(new Point(0, 0), (int) (15 * density));
- canvas.drawPath(path, DEBUG_PAINT);
- }
- private static Path getTrianglePath(Point p1, int width) {
- Point p2 = new Point(p1.x + width, p1.y);
- Point p3 = new Point(p1.x, p1.y + width);
- Path path = new Path();
- path.moveTo(p1.x, p1.y);
- path.lineTo(p2.x, p2.y);
- path.lineTo(p3.x, p3.y);
- return path;
- }
- }
如果你覺得本文太長,可以單獨檢視Picasso原始碼原理分析一文:
http://blog.csdn.net/jay100500/article/details/71055229
相關文章
- 快取圖片2016-04-05快取
- 圖片快取2016-03-18快取
- 圖片三級快取及OOM--android2016-03-03快取OOMAndroid
- 談談服務端快取的幾種用法2015-04-22服務端快取
- Android中圖片的三層快取詳解2016-07-10Android快取
- 圖片載入框架-Picasso最詳細的使用指南2017-02-26框架
- SDWebImage實現圖片展示、快取、清除快取2016-05-20Web快取
- Picasso 載入圖片的流程分析2018-01-26
- 淺談HTTP快取與CDN快取的那點事2022-11-15HTTP快取
- 圖片載入框架Picasso - 原始碼分析2019-03-04框架原始碼
- 圖片載入框架Picasso原始碼分析2019-01-21框架原始碼
- React Native圖片快取元件2018-10-18React Native快取元件
- 圖片快取(源於SDK文件)2013-11-28快取
- Android 圖片快取處理2014-05-28Android快取
- 安卓快取之LruCache及設計(非同步+快取)圖片載入器LruCacheImageLoader2016-08-25安卓快取非同步
- Android圖片載入框架Picasso原始碼分析(基於Picasso 2.71828)2019-02-28Android框架原始碼
- python自帶快取lru_cache用法及擴充套件(詳細)2020-12-08Python快取套件
- Swift iOS : WebView快取圖片的方法2019-02-28SwiftiOSWebView快取
- 我的圖片四級快取框架2018-01-30快取框架
- Android圖片快取框架Glide2018-05-24Android快取框架IDE
- Android圖片快取之Bitmap詳解2016-05-31Android快取
- nginx快取使用詳解,nginx快取使用及配置步驟2022-05-10Nginx快取
- Android使用LruCache、DiskLruCache實現圖片快取+圖片瀑布流2017-12-15Android快取
- Android平滑圖片載入和快取庫 Glide 使用詳解2017-09-20Android快取IDE
- iOS Cell非同步圖片載入優化,快取機制詳解2015-09-11iOS非同步優化快取
- Android 平滑圖片載入和快取庫 Glide 使用詳解2015-08-26Android快取IDE
- 淺談Flutter web 圖片選擇器及圖片壓縮2020-07-06FlutterWeb
- Android圖片載入庫Picasso原始碼分析2016-01-13Android原始碼
- Android ImageLoader 框架之圖片快取2015-04-05Android框架快取
- 快取穿透詳解及解決方案2022-02-17快取穿透
- Android 之 遠端圖片獲取和本地快取2013-11-28Android快取
- 圖片載入利器之Picasso(五)查漏補缺2017-03-10
- Android技術積累:圖片快取管理2013-11-28Android快取
- 淺談HTTP快取2018-11-04HTTP快取
- 淺談Web快取2016-03-06Web快取
- Mybatis 一級快取和二級快取原理區別 (圖文詳解)2022-09-21MyBatis快取
- 微信小程式 實現網路圖片本地快取2019-01-26微信小程式快取
- BlueHost主機網站圖片快取刪除教程2020-09-03網站快取