Picasso源码学习

1.开始

Picasso是square公司推出的一个轻量级的开源图片加载框架,用法十分简单,通过下面一行代码即可完成网络图片的加载:

Picasso.get().load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png").into(iv1);
2.加载图片过程分析

Picasso项目整个代码比较少,本文以2.8的java版本进行分析。上面的例子中加载流程如下:
加载流程

下面进行具体的代码走读

2.1 get()

这个方法通过单例模式创建全局Picasso实例,其中context的获取用了一个小技巧—通过Provider提供,同时可自定义这个对象,结合Builder模式实现自己的处理策略。

// -----------------------------------------------Picasso.java-----------------------------------------------
// 创建单例 
public static Picasso get() {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          if (PicassoProvider.context == null) {
            throw new IllegalStateException("context == null");
          }
          singleton = new Builder(PicassoProvider.context).build();
        }
      }
    }
    return singleton;
  }

// 自定义配置
 public static void setSingletonInstance(@NonNull Picasso picasso) {
    if (picasso == null) {
      throw new IllegalArgumentException("Picasso must not be null.");
    }
    synchronized (Picasso.class) {
      if (singleton != null) {
        throw new IllegalStateException("Singleton instance already exists.");
      }
      singleton = picasso;
    }
  }

Builder包含一些默认配置,具体如下:

// -----------------------------------------------Picasso.java-----------------------------------------------
public static class Builder {
  public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        downloader = new OkHttp3Downloader(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);// 调度器
      // defaultBitmapConfig为null	
      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }
}
2.2 load()

load()方法提供对文件、路径、url和图片资源id等处理方式,方法具体如下图:

load方法

这几个load方法都转化为RequestCreator,RequestCreator之后创建了Request。从命名可以猜测,每次图片加载都转换为一个请求Request,具体代码如下:

// -----------------------------------------------Picasso.java-----------------------------------------------
public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
}
// -----------------------------------------------RequestCreator.java-----------------------------------------------
public class RequestCreator {
  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);
  }
}
// -----------------------------------------------Request.java-----------------------------------------------
public final class Request {
  public static final class Builder {
    Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
      this.uri = uri;
      this.resourceId = resourceId;
      this.config = bitmapConfig;
    }
  }
}

此阶段的总体流程如下:
在这里插入图片描述

2.3 into()

下面开始重要的图片解析、转换流程。into方法先做了一些检查工作,之后才开始加载,加载策略分为内存加载、网络加载。内存缓存策略分为NO_CACHENO_STORE

// -----------------------------------------------RequestCreator.java-----------------------------------------------
public void into(ImageView target, Callback callback) {
    // 一些检查与处理分支:
    // 检查是否主线程,不是的话抛出异常
    // 检查url与resourceId,没有图片的话,流程终止
    // 检查是否是deferRequest,是的话进行defer处理
  
    // 加载策略:
    // 1.优先从内存中读取
    // 2.内存中没有的话,再从网络读取

    // 如果有具体的转换操作,将构造方法时创建的this.data添加转换处理,默认不进行转换,即是上面的RequestTransformer.IDENTITY
    Request request = createRequest(started);
    // 内存缓存中key
    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 action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
  }
2.3.1 内存加载

内存中保存的key的组成是:路径+转换标识,代码如下:

// -----------------------------------------------RequestCreator.java-----------------------------------------------
static String createKey(Request data, StringBuilder builder) {
  if (data.stableKey != null) {
    // 默认为null,不走这里
    builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
    builder.append(data.stableKey);
  } else if (data.uri != null) {
    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);

  // 接下来添加操作的标识
  if (data.rotationDegrees != 0) {
    builder.append("rotation:").append(data.rotationDegrees);
    if (data.hasRotationPivot) {
      builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
    }
    builder.append(KEY_SEPARATOR);
  }
  if (data.hasSize()) {
    builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
    builder.append(KEY_SEPARATOR);
  }
  if (data.centerCrop) {
    builder.append("centerCrop:").append(data.centerCropGravity).append(KEY_SEPARATOR);
  } else if (data.centerInside) {
    builder.append("centerInside").append(KEY_SEPARATOR);
  }

  if (data.transformations != null) {
    for (int i = 0, count = data.transformations.size(); i < count; i++) {
      builder.append(data.transformations.get(i).key());
      builder.append(KEY_SEPARATOR);
    }
  }

  return builder.toString();
}

内存中存在就优先从内存中加载,quickMemoryCacheCheck()就是通过key从内存中查找

// -----------------------------------------------Picasso.java-----------------------------------------------
Bitmap quickMemoryCacheCheck(String key) {
  Bitmap cached = cache.get(key);
  if (cached != null) {
    stats.dispatchCacheHit();
  } else {
    stats.dispatchCacheMiss();
  }
  return cached;
}

内存缓存使用了LRU算法,整个类流程如下图:
内存加载流程

2.3.2 网络加载

从上面可以看出,创建了一个ImageViewAction,之后提交到Dispatcher进行调度。在提到dispatcher之前做了一些检查工作,如非主线程检查、取消已经存在的请求

// -----------------------------------------------RequestCreator.java-----------------------------------------------
public void into(ImageView target, Callback callback) {
  ...
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
  }

// -----------------------------------------------Picasso.java-----------------------------------------------
void enqueueAndSubmit(Action action) {
  Object target = action.getTarget();
  if (target != null && targetToAction.get(target) != action) {
   // 取消已经存在的Request
    cancelExistingRequest(target);
    targetToAction.put(target, action);
  }
  submit(action);
}
void submit(Action action) {
  dispatcher.dispatchSubmit(action);
}
// -----------------------------------------------Dispatcher.java-----------------------------------------------
void dispatchSubmit(Action action) {
  handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}

Dispatcher调度器中定义很多执行消息,使用了Handler进行分发。REQUEST_SUBMIT代表请求的开始,执行了performSubmit

// -----------------------------------------------Dispatcher.java-----------------------------------------------
void performSubmit(Action action, boolean dismissFailed) {
  ....
  // 创建hunter,执行请求
  hunter = forRequest(action.getPicasso(), this, cache, stats, action);
  hunter.future = service.submit(hunter);
  hunterMap.put(action.getKey(), hunter);
  if (dismissFailed) {
    failedActions.remove(action.getTarget());
  }
}

BitmapHunter 实际上是一个Runnable,放在线程池执行后台任务。从上面知道,load方法支持url/file等几种形式,每种形式对应了一个RequestHandler,但是Picasso这个处理的比较简陋,直接通过uri.getScheme()进行区分,比较容易推测到不支持直接用filePath

// -----------------------------------------------BitmapHunter .java-----------------------------------------------
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
Action action) {
    // 找到对应的RequestHandler
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
    RequestHandler requestHandler = requestHandlers.get(i);
    if (requestHandler.canHandleRequest(request)) {
    return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
    }
    }
    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

RequestHandler
我们跟踪看run(),看看网络加载的处理。通过上面获取到的RequestHandler进行load(),添加对原图转化处理,并将结果通知Dispatcher

// -----------------------------------------------BitmapHunter .java-----------------------------------------------
public void run() {
    try {
      ...
      result = hunt();
      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }
    }
    ...
}

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
    // 先判断内存中有没有,有点话直接从内存中获取
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      bitmap = cache.get(key);
      if (bitmap != null) {
        stats.dispatchCacheHit();
        loadedFrom = MEMORY;
        return bitmap;
      }
    }
    // 具体的加载
    networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifOrientation = result.getExifOrientation();
      bitmap = result.getBitmap();
      if (bitmap == null) {
        Source source = result.getSource();
        try {
          bitmap = decodeStream(source, data);
        } finally {
          try {
            source.close();
          } catch (IOException ignored) {
          }
        }
      }
    }
    // 对原图添加转化处理
    if (bitmap != null) {
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifOrientation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifOrientation != 0) {
            bitmap = transformResult(data, bitmap, exifOrientation);
    
          }
          if (data.hasCustomTransformations()) {
            bitmap = applyCustomTransformations(data.transformations, bitmap);
          }
        }
        if (bitmap != null) {
          stats.dispatchBitmapTransformed(bitmap);
        }
      }
    }
    return bitmap;
  }

接着我们来看NetworkRequestHandler具体做了什么,可以知道网络请求使用了okhttp,磁盘缓存也使用了okhttp

// -----------------------------------------------BitmapHunter.java-----------------------------------------------
public Result load(Request request, int networkPolicy) throws IOException {
    // 用okhttp进行网络请求
    okhttp3.Request downloaderRequest = createRequest(request, networkPolicy);
    Response response = downloader.load(downloaderRequest);
    ResponseBody body = response.body();

    if (!response.isSuccessful()) {
      body.close();
      throw new ResponseException(response.code(), request.networkPolicy);
    }
    // 用okhttp缓存
    Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK;
    if (loadedFrom == DISK && body.contentLength() == 0) {
      body.close();
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && body.contentLength() > 0) {
      stats.dispatchDownloadFinished(body.contentLength());
    }
    return new Result(body.source(), loadedFrom);
}

private static okhttp3.Request createRequest(Request request, int networkPolicy) {
    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();
      }
    }

    okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(request.uri.toString());
    if (cacheControl != null) {
      builder.cacheControl(cacheControl);
    }
    return builder.build();
}

// -----------------------------------------------OkHttp3Downloader.java-----------------------------------------------
public final class OkHttp3Downloader implements Downloader {
  final Call.Factory client;

  public Response load(@NonNull Request request) throws IOException {
    return client.newCall(request).execute();
  }
}

至此,整个网络加载大致梳理完成,对图片的一些解码或者转化,大家可以自己看源码。
网络加载流程
into整个流程如下:(刚用plantuml画图_)
into流程

3.总结
  • 整个代码不是很多,逻辑看起来比较清晰
  • 非主线程抛出异常、支持加载的形式比较简陋
  • 缺少activity/fragment中生命周期处理
  • 运行自身的sample,对比glide,加载很慢
4.问题

1.Picasso三级缓存流程?

内存缓存(LRU算法)–> 磁盘缓存 -> 网络请求 ,其中磁盘缓存和网络请求使用的是okhttp

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值