美文网首页
加载库源码浅析之 Picasso

加载库源码浅析之 Picasso

作者: wavever | 来源:发表于2020-08-16 20:04 被阅读0次

    Picasso 作为 Android 上一个老牌的图片加载库,似乎近些年在 Glide 的 “打压” 下已经变的黯然失色,但作为 square 出品的优秀框架,其实现的架构和思想仍然有许多值得借鉴和学习的地方,本文所使用的 Picasso 版本号为 2.71828,在 gradle 中依赖如下:

    implementation 'com.squareup.picasso:picasso:2.71828'
    

    Picasso 加载图片的方式和 Glide 类似,都是链式调用:

    Picasso.get().load("").into(target); // 异步加载并设置到 target 上
    Picasso.get().load("").get(); //同步加载,不能在主线程调用
    Picasso.get().load("").fetch(); // 异步加载
    

    这里 Picasso 提供了三种方式:

    • into() :参数可以是 View,如 ImageView 和 RemoteViews,也可以是一个 Target 类型的对象,等于是通过回调的方式来实现加载结果的处理
    • get():同步获取 btimap 对象,会检查当前线程是否是主线程,如果是则直接会抛出异常
    • fetch():异步获取 bitmap 对象,可以通过设置回调来监听是否加载成功

    使用 into 加载的过程大致上可以分为三个阶段:一是获取 Picasso 对象,对应 Picasso.get(),这一步是初始化如线程池、缓存等;二是请求封装,对应 load(url),这一步则是对资源请求的封装,例如占位图、加载失败图、tag、资源裁剪等;最后则是请求处理阶段,对应 into(ImageView) ,这一步处理的事情最为繁重,包括资源请求、资源处理等,下图为 into(ImageView) 流程的的序列图,接下来就通过围绕这个序列图来进行分析:

    序列图.png

    获取 Picasso 对象

    这一节对应于序列图中的步骤 1、2、3。

    静态方法 get() 是通过 DCL 实现的单例模式,返回一个全局的 Picass 对象:

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

    这里 context 的获取是通过 PicassoProvider 实现的, PicassoProvider 继承自 ContentProvider,主要作用就是为 Picasso 提供 context 对象:

    public final class PicassoProvider extends ContentProvider {
    
      @SuppressLint("StaticFieldLeak") static Context context;
    
      @Override public boolean onCreate() {
        context = getContext();
        return true;
      }
      ...
    }
    

    并在自己的 AndroidManifest 文件中注册:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.squareup.picasso" >
    
        <uses-sdk android:minSdkVersion="14" />
    
        <application>
            <provider
                android:name="com.squareup.picasso.PicassoProvider"
                android:authorities="${applicationId}.com.squareup.picasso"
                android:exported="false" />
        </application>
    
    </manifest>
    

    由于在进程启动时 ContentProvider 的加载是优先于 Application 的加载,因此很多三方 SDK 也通常使用这种方式来自动进行初始化,通过在 aar 包中添加一个自定义的 ContentProvider,在其 onCreate() 方法中实现初始化逻辑。
    除了通过 get() 方法,也可以通过 Picasso 的静态内部类 Builder 来自行构建,主要提供了如下方法:

    // 设置 Bitmap.Config
    public Builder defaultBitmapConfig(@NonNull Bitmap.Config bitmapConfig)
    // 下载图片的方式,默认使用 OkHttp3Downloader
    Builder downloader(@NonNull Downloader downloader)
    // 线程池,默认为 PicassoExecutorService
    public Builder executor(@NonNull ExecutorService executorService)
    // 内存缓存,默认为 LruCache
    public Builder memoryCache(@NonNull Cache memoryCache)
    // 设置请求转换器,只能有一个
    public Builder requestTransformer(@NonNull RequestTransformer transformer)
    // 添加请求处理器,可以有多个
    public Builder addRequestHandler(@NonNull RequestHandler requestHandler)
    

    下载器

    对下载行为的抽象,提供 load() 方法来加载图片并返回一个 okhttp3.Response 对象,shutdown() 方法用于对资源进行情理:

    public interface Downloader {
    
      @NonNull Response load(@NonNull okhttp3.Request request) throws IOException; // 加载图片
    
      void shutdown();
    }
    

    默认只有 OkHttp3Downloader 一个实现,其内部通过 OkHttp 进行网络请求下载图片:

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

    线程池

    默认使用的线程池为 PicassoExecutorService,核心线程数和最大线程数数量相同,默认都为 3 个,但可根据网络状态进行动态调整:2G 网络为 1 个、3G 网络为 2 个、4G网络为 3 个、WIFI 网络为 4 个。工作队列为 PriorityBlockingQueue,是一个无界阻塞队列,可以通过自定义 compareTo() 来指定排序规则:

    class PicassoExecutorService extends ThreadPoolExecutor {
      private static final int DEFAULT_THREAD_COUNT = 3;
    
      PicassoExecutorService() {
        super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
            new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
      }
        
       private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
          implements Comparable<PicassoFutureTask> {
          ...
    
        @Override
        public int compareTo(PicassoFutureTask other) {
          Picasso.Priority p1 = hunter.getPriority();
          Picasso.Priority p2 = other.hunter.getPriority();
    
          // High-priority requests are "lesser" so they are sorted to the front.
          // Equal priorities are sorted by sequence number to provide FIFO ordering.
          return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
        }
      }
    }
    

    compareTo() 中会首先判断两个请求的优先级,如果优先级相同,则会接着判断添加的序号,sequence 是一个自增的 int 整型,也就是说先入队的请求先处理,是一种先进先出的方式。

    内存缓存器

    Cache 接口提供了基本的对于缓存内容的处理方法:

    public interface Cache {
      Bitmap get(String key);
      void set(String key, Bitmap bitmap);
      int size();
      int maxSize(); //最大可缓存的字节数
      void clear();
      void clearKeyUri(String keyPrefix); //根据前缀移除对应的缓存
    }
    

    其有 2 个实现类,NONE 和 LruCache,其中 NONE 是空实现,LruCache 内部是对 android.util.LruCache 的封装,最大缓存大小为可用内存大小的 1/7。

    请求转换器

    RequestTransformer 可以理解为是 Picasso 向外提供的一个对请求进行转换的接口,用户可以通过自定义一个转换器来对生成的 Request 对象进行处理,只能设置一个,默认提供的实现为 IDENTITY 直接返回原 Request 对象,如下所示:

    public interface RequestTransformer {
    
      Request transformRequest(Request request);
      
      RequestTransformer IDENTITY = new RequestTransformer() {
        @Override public Request transformRequest(Request request) {
          return request;
        }
      };
    }
    

    请求处理器

    RequestHandler 是一个抽象类:

    public abstract class RequestHandler {
        public abstract boolean canHandleRequest(Request data);
        public abstract Result load(Request request, int networkPolicy) throws IOException;
        ...
    }
    

    其作用就是在不同场景下加载图片,例如 Picasso 通过 AssetRequestHandler 来加载 asset 文件夹下的图片资源,用 NetworkRequestHandler 来加载网络图片资源等,通过遍历所有的 Handler,并调用它们的 canHandleRequest(Request data) 方法,如果返回 true,则表示对应 Handler 可以处理该请求,则该请求就会交由这个 Handler 处理。在 Picasso 的构造函数中会添加默认 Handler 和用户定义的 Handler:

    Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
        RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
        Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
      ...
      int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
      int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
      List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);
      // 优先添加用户自定义的 handler
      allRequestHandlers.add(new ResourceRequestHandler(context));
      if (extraRequestHandlers != null) {
        allRequestHandlers.addAll(extraRequestHandlers);
      }
      allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
      allRequestHandlers.add(new MediaStoreRequestHandler(context));
      allRequestHandlers.add(new ContentStreamRequestHandler(context));
      allRequestHandlers.add(new AssetRequestHandler(context));
      allRequestHandlers.add(new FileRequestHandler(context));
      allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
      ...
    }
    

    请求封装

    这一节对应于序列图中的步骤 4、5、6,主要作用就是封装 Request 对象。
    Picasso 提供了 4 种图片来源的方法:

    public RequestCreator load(@NonNull File file)
    public RequestCreator load(@DrawableRes int resourceId)
    public RequestCreator load(@Nullable Uri uri)
    public RequestCreator load(@Nullable String path)
    

    这里需要注意的是,传入的参数不能为空,不然会直接抛一个异常,因此使用前的判空非常有必要。load() 方法会返回一个 RequestCreator 对象,通过这个对象就可以来对请求做一些处理操作。RequestCreator 内部持有一个 Request 对象,所有关于请求的配置都在 Request 类中。

    占位图和失败图

    占位图支持 resId 和 drawable 两种,并且互斥,如果同时设置两个则会抛出异常:

    public RequestCreator placeholder(@DrawableRes int placeholderResId)
    public RequestCreator noPlaceholder() //不设置占位图,与设置占位图互斥
    

    标签

    public RequestCreator tag(@NonNull Object tag)
    

    对一次请求进行标记,可用于对请求进行暂停、恢复、取消等操作。

    图片处理

    public RequestCreator fit() //调整图片大小为 ImageView 大小
    public RequestCreator resize(int targetWidth, int targetHeight)
    public RequestCreator centerCrop()
    public RequestCreator centerInside()
    public RequestCreator onlyScaleDown() //仅在图片大于 target 大小时对图片进行缩放处理
    public RequestCreator rotate(float degrees) // bitmap 旋转角度
    public RequestCreator config(@NonNull Bitmap.Config config) // bitmap config
    public RequestCreator transform(@NonNull Transformation transformation)  // 对 bitmap 对象做自定义处理
    public RequestCreator purgeable() // bitmap复用
    public Builder transform(@NonNull Transformation transformation)
    

    Transformation 用于对加载后的 bitmap 做处理:

    public interface Transformation {
      Bitmap transform(Bitmap source);
      String key();
    }
    

    缓存策略

    public RequestCreator stableKey(@NonNull String stableKey) // 设置缓存 key 值,拥有相同 key 值的资源将被视为是相同资源
    public RequestCreator memoryPolicy(@NonNull MemoryPolicy policy, @NonNull MemoryPolicy... additional) // 内存缓存策略
    public RequestCreator networkPolicy(@NonNull NetworkPolicy policy, @NonNull NetworkPolicy... additional) //磁盘缓存策略
    

    MemoryPolicy 用于指定内存缓存的策略:

    public enum MemoryPolicy {
      NO_CACHE(1 << 0), //请求时跳过内存缓存中查询
      NO_STORE(1 << 1); //请求后跳过缓存到内存中
      ...
    }
    

    NetworkPolicy 用于指定磁盘缓存的策略,而 Picasso 中磁盘缓存是基于 OkHttp 的缓存来实现:

    public enum NetworkPolicy {
      NO_CACHE(1 << 0), //跳过从磁盘缓存查询,直接通过网络获取资源
      NO_STORE(1 << 1), //跳过写入到磁盘缓存
      OFFLINE(1 << 2); //跳过从网络获取,直接从磁盘缓存中获取
    }
    

    优先级

    public RequestCreator priority(@NonNull Priority priority) // 请求优先级,用于对请求任务进行排序
    public enum Priority {
      LOW,
      NORMAL,
      HIGH
    }
    

    用于在前文说到的线程池阻塞队列进行请求任务排序。

    加载动画

    public RequestCreator noFade() // 加载图片到 ImageView 时不显示渐变动画
    
    // com.squareup.picasso.PicassoDrawable#draw
    @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);
          }
          // setAlpha will call invalidateSelf and drive the animation.
          int partialAlpha = (int) (alpha * normalized);
          super.setAlpha(partialAlpha);
          super.draw(canvas);
          super.setAlpha(alpha);
        }
      }
    }
    

    PicassoDrawable 继承自 BitmapDrawable,通过重写 onDraw() 方法实现了渐变过渡动画。

    请求处理

    对应于序列图中步骤 7 以后。

    请求入队

    into() 中,首先会通过 createRequest() 来创建一个 Request 请求对象,然后通过 createKey() 来创建请求的 key 值,这里 key 值的创建通过 stableKey、uri、rotationDegrees、resize、centerCrop 等属性组合而成,因此即使请求的是同个资源,如果其中有任何一个属性有变化,由于生成的 key 值不同,也会重新去请求。

    public void into(ImageView target, Callback callback) {
        ...
        Request request = createRequest(started);
        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 (callback != null) {
              callback.onSuccess();
            }
            return;
          }
        }
        ...
        Action action = new ImageViewAction(...);
        picasso.enqueueAndSubmit(action);
    }
    

    如果没有从内存中读取到,会先创建一个 Action 对象,Action 是一个抽象类:

    abstract class Action<T> {
        ...
        abstract void complete(Bitmap result, Picasso.LoadedFrom from);
        abstract void error(Exception e);
        ...
    }
    

    它有几个实现类:FetchAction、GetAction、ImageViewAction 等,分别对应了 fetch()get()into(ImageView) 方法。接着会将这个 action 对象通过 Picasso.enqueueAndSubmit() 重新又发送到了 Picasso 中:

    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.dispatchSubmit(action);
    }
    

    请求发送

    这里 action 对象最终会被传入到 Dispatcher#performSubmit() 中:

    //com.squareup.picasso.Dispatcher#performSubmit()
    void performSubmit(Action action, boolean dismissFailed) {
      //如果该请求已经暂停,则加入到 pausedActions 中
      if (pausedTags.contains(action.getTag())) {
        pausedActions.put(action.getTarget(), action);
        return;
      }
      BitmapHunter hunter = hunterMap.get(action.getKey());
      if (hunter != null) {
        hunter.attach(action);
        return;
      }
      //如果线程池已经关闭,则对该请求不做处理  
      if (service.isShutdown()) {
        return;
      }
      hunter = forRequest(action.getPicasso(), this, cache, stats, action);
      //将请求封装成为 Runnable 对象并发送到线程池中
      hunter.future = service.submit(hunter);
      hunterMap.put(action.getKey(), hunter);
      ...
    }
    

    forRequest() 会返回一个 BitmapHunter 对象,它继承自 Runnable,当 run() 执行时,会通过 hunt()来获取 Bitmap,这里就是真正来解析 Bitmap 的地方,首先还是会去内存里读取,接着会调用 RequestHandler 的 load() 来加载资源,然后对将加载得到的 bitmap 进行处理,具体如何处理则取决于之前在 RequestCreator 中所设置,这一步处理完成后,还要对用户自定义的 Transformation 来进行,简化后的代码流程如下:

    Bitmap hunt() throws IOException {
      Bitmap bitmap = null;
      //从内存缓存读取
      if (shouldReadFromMemoryCache(memoryPolicy)) {
        bitmap = cache.get(key);
      }
      //从 requestHandler 加载
      RequestHandler.Result result = requestHandler.load(data, networkPolicy);
      if (bitmap != null) {
        // 对 bitmap 做处理,如 fit
        if (data.needsTransformation() || exifOrientation != 0) {
          synchronized (DECODE_LOCK) {
            if (data.needsMatrixTransform() || exifOrientation != 0) {
              bitmap = transformResult(data, bitmap, exifOrientation);
            }
            //用户自定义 bitmap 处理逻辑
            if (data.hasCustomTransformations()) {
              bitmap = applyCustomTransformations(data.transformations, bitmap);
            }
          }
        }
      }
      return bitmap;
    }
    

    看下 NetworkRequestHandler 中的 load() 逻辑:

    @Override public Result load(Request request, int networkPolicy) throws IOException {
      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);
      }
      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();
    }
    

    首先会调用 createRequest() 来创建一个 Okhttp 的 Request 对象,会根据之前定义的磁盘缓存逻辑来设置 Request 的 CacheControl。接着会将这个创建好的 Request 对象传给 OkHttp3Downloader 来调用 okhttp3.Call.Factory#newCall() 发送网络请求。

    请求结果缓存

    通过 BitmapHunter.hunt() 获取到 Bitmap 后,会通过 Dispatcher#dispatchComplete() 来回调结果到 Dispatcher 中,在这里会对结果进行缓存处理:

    //com.squareup.picasso.Dispatcher#performComplete
    void performComplete(BitmapHunter hunter) {
      if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
        cache.set(hunter.getKey(), hunter.getResult());
      }
      hunterMap.remove(hunter.getKey());
      batch(hunter);
    }
    

    最终结果依次被回调到 ImageViewAction#complete() 或者是 ImageViewAction#error() 中来分别处理加载成功和失败的逻辑,加载成功则将 bitmap 封装成 PicassoDrawable 后设置给 ImageView,加载失败则显示设置的失败图资源。

    总结

    • 二级缓存,LruCache 的内存缓存和基于 Okhttp Cache 的磁盘缓存,内存缓存只会缓存最终处理后的 bitmap,不会对原 bitmap 也进行缓存

    • 线程池线程数可根据网络状态进行动态切换

    • 不支持 Gif 格式加载

    • 记录缓存命中率

    相关文章

      网友评论

          本文标题:加载库源码浅析之 Picasso

          本文链接:https://www.haomeiwen.com/subject/bulxjktx.html