• Picasso学习


    1.开始

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

    Picasso.get().load("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png").into(iv1);
    
    • 1

    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;
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    // -----------------------------------------------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);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    // -----------------------------------------------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;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    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);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

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

    // -----------------------------------------------Picasso.java-----------------------------------------------
    Bitmap quickMemoryCacheCheck(String key) {
      Bitmap cached = cache.get(key);
      if (cached != null) {
        stats.dispatchCacheHit();
      } else {
        stats.dispatchCacheMiss();
      }
      return cached;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    内存缓存使用了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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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());
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    接着我们来看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();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

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

    3.总结

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

    4.问题

    1.Picasso三级缓存流程?

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

  • 相关阅读:
    arm交叉编译ntpdate与服务器进行时间同步
    Hibernate 配置文件详解
    某电商商品搜索系统架构设计
    基于STM32的步进电机驱动设计( 含源码 )
    GPT-4o横空出世,如何评价GPT-4o?
    基于ssm水果销售管理系统
    初识设计模式 - 装饰器模式
    华为机试真题 Java 实现【学生方阵】
    查找算法.
    WPF/C#:如何将数据分组显示
  • 原文地址:https://blog.csdn.net/wangadping/article/details/126514216