当前位置: 首页 > 工具软件 > Picasso > 使用案例 >

Picasso

连志义
2023-12-01

1.简介

github链接
实现图片加载功能的第三方图片加载库,需要添加依赖

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

1.1 功能特性

  1. 使用默认的内存缓存和磁盘缓存(可自定义)
  2. 可自定义图片的变换操作、解析格式等
  3. 可以选择裁剪方式、大小等
  4. 可以加载网络资源、本地资源等
  5. 可以选择内存缓存和磁盘缓存的策略
  6. 可以暂停、恢复、取消加载
  7. 可以设置加载成功失败的占位图及相应监听

1.2 使用方式

Picasso.with(context)//拿到Picasso对象
				.load(url)//load一个url
                .resize(width, height)//重新裁剪大小
                .memoryPolicy(MemoryPolicy.NO_STORE)//内存缓存策略(不存入内存)
				.networkPolicy(NetworkPolicy.OFFLINE)//磁盘缓存策略(不通过网络请求)
                .into(imageView);//显示到ImageView中

2. 源码解析

2.1 Picasso对象

public static Picasso with(Content context) {
     if (singleton == null) {
          synchronized (Picasso.class) {
               if (singleton == null) {
                    singleton = new Builder().build();
               }
          }
     }
     return singleton;
}

Picasso使用的是单例模式,并且使用Builder模式创建了一个Picasso的实例。有了实例之后,直接调用它的load函数,Picasso重载了几个不同参数的load函数,用以从不同的地点来加载图片

public RequestCreator load(Uri uri) {
    ...
}
public RequestCreator load(String path) {
    ...
}
public RequestCreator load(int resourceId) {
    ...
}

通过load函数,我们最终得到了一个RequestCreator对象,RequestCreator对象提供了各种占位图的设置方法以及缓存策略的方法。其内部封装了一个Request.Builder对象,用于设置图片相关参数,也是通过builder模式创建;如加载的uri、resize的大小、裁剪方式等
最后我们调用了into函数,将加载到的图片赋给一个ImageView控件:

public void into(Target target) {
    long started = System.nanoTime();
    checkMain();

    if (target == null) {
        throw new IllegalArgumentException("Target must not be null.");
    }
    if (deferred) {
        throw new IllegalStateException("Fit cannot be used with a Target.");
    }

    if (!data.hasImage()) {
        picasso.cancelRequest(target);
        target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);
        return;
    }

    Request request = createRequest(started);
    String requestKey = createKey(request);

    if (shouldReadFromMemoryCache(memoryPolicy)) {
        Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
        if (bitmap != null) {
            picasso.cancelRequest(target);
            target.onBitmapLoaded(bitmap, MEMORY);
            return;
        }
    }

    target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);

    Action action =
        new TargetAction(picasso, target, request, memoryPolicy, networkPolicy, errorDrawable,
            requestKey, tag, errorResId);
    picasso.enqueueAndSubmit(action);
}
  1. into会检查当前是否是在主线程上执行。
  2. 如果我们没有提供一个图片资源并且有设置placeholder,那么就会把我们设置的placeholder显示出来,并中断执行。
  3. defered属性我们一般情况下不需要关注,只有当我们调用了RequestCreator的fit方法时defered才为true,但我们几乎不会这样做。
  4. 接下来就是创建了一个Request对象,我们在前面做得一些设置都会被封装到这个Request对象里面。
  5. 检查我们要显示的图片是否可以直接在缓存中获取,如果有就直接显示出来好了。
  6. 缓存没命中,那就只能费点事把源图片down下来了。这个过程是异步的,并且通过一个Action来完成请求前后的衔接工作。

至此,Picasso在主线程中的工作就结束了:首先通过Picasso创建了一个RequestCreator对象,通过这个对象我们可以针对不同的场景来设置一些属性,之后创建出Request对象,最后通过Action来确定异步请求并对请求结果做处理。

2.2 build方法

public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        downloader = Utils.createDefaultDownloader(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);
      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
  • Downloader:它是一个接口,规定了一些通用的方法,所以可以提供自己的下载器,只要实现这个接口即可,Picasso默认使用OkHttpClient来作为下载器
  • Cache:Picasso的缓存,这里实例化的是LruCache,其内部使用的是LinkedHashMap
  • ExecutorService:这里Picasso实现了自己的PicassoExecutorService,它继承了ThreadPoolExecutor,也就是Picasso自己维护了一个线程池,用于异步加载图片。
  • RequestTransformer:主要是对RequestCreator创建的Request进行转换,默认对Request对象不做处理。
  • Stats:这个类只要是维护图片的一些状态
  • Dispatcher:调度器的作用,图片要不要开始下载以及下载后Bitmap的返回都是通过这个调度器来执行的

2.3 Dispatcher对象

  1. 内部会创建新的线程及其handler,即任何的派发、调度的请求都会在这个新的线程的handler上进行
  2. 创建时会保存住主线程的handler,在结束加载后,将结果返回到主线程上进行
  3. 在发起加载请求时,找到相应处理的RequestHandle,构建BitmapHunter对象,并将其放入到ExecutorService线程池进行执行

RequestCreator在into方法的最后会创建一个Action实例,然后调用PicassoenqueueAndSubmit方法,而最终是调用了DispatcherdispatchSubmit方法。在Dispatcher内部,Dispatcher定义了DispatcherThreadDispatcherHandler两个内部类,并在Dispatcher的构造函数中对他们经行了实例化,所有的调度也都是通过handler异步的执行的,handler最终调用performSubmit方法来触发一个图片的加载,在这个方法中主要是获取BitmapHunter实例,由这个实例来执行实际的下载操作。BitmapHunter本身是Runnable的一个实现,而这个实例最终是交由Picasso线程池进行运行的。加载完成或失败时,还是通过Dispatcher调度器实现UI更新操作,最终会执行performComplete方法。这个方法会自动处理图片的缓存问题

dispatcher.dispatchComplete(this)

void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}

public void handleMessage(final Message msg) {
      switch (msg.what) {
        ...
        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");
    }
}

这个时候,Picasso并不是立即将图片显示出来,而是用到了一个批处理,其实就是把操作先暂存在一个list中,等空闲的时候再拿出来处理,这样做得好处也是尽量减少主线程的执行时间,一方面防止ANR,另一方面快速返回,响应页面的其他渲染操作,防止卡顿用户界面。

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

public void handleMessage(final Message msg) {
      switch (msg.what) {
        ...
        case HUNTER_DELAY_NEXT_BATCH: {
          dispatcher.performBatchComplete();
          break;
        }
        ...
      }
}

最后还是调用了Dispatcher的方法来处理,但由于这个处理并非是在主线程,因此还需要通过一个主线程的Handler来处理这个请求

void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
}

这个mainThreadHandler是在Dispatcher实例化时由外部传递进来的,Picasso在通过Builder创建时会对Dispatcher进行实例化,在那个地方将主线程的handler传了进来,回到Picasso这个类,看到其有一个静态成员变量HANDLER。

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);
          }
          break;
        }
        ...
      }
    }
};

Picasso中一个Action提供了请求前后的衔接工作,对于现在的情况,Picasso使用了ImageViewAction来进行处理,也就是在ImageViewAction中的complete方法完成了最后的图片渲染工作。

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

    if (callback != null) {
      callback.onSuccess();
    }
}

学习借鉴链接 Android Picasso 原理解析
android中的三级缓存

 类似资料: