欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android图片加载利器之Picasso源码解析

程序员文章站 2023-12-05 12:48:58
看到了这里,相信大家对picasso的使用已经比较熟悉了,本篇博客中将从基本的用法着手,逐步的深入了解其设计原理。 picasso的代码量在众多的开源框架中算得上非常少的...

看到了这里,相信大家对picasso的使用已经比较熟悉了,本篇博客中将从基本的用法着手,逐步的深入了解其设计原理。

picasso的代码量在众多的开源框架中算得上非常少的一个了,一共只有35个class文件,但是麻雀虽小,五脏俱全。好了下面跟随我的脚步,出发了。

基本用法

picasso.with(this).load(imageurl).into(imageview);

with(this)方法

 public static picasso with(context context) {
  if (singleton == null) {
   synchronized (picasso.class) {
    if (singleton == null) {
     singleton = new builder(context).build();
    }
   }
  }
  return singleton;
 }

非常经典的单例模式,双重校验锁

在这多说一句,关于单例模式的实现方式一共有五种,分别是懒汉式,饿汉式,双重校验锁,内部静态类和枚举,其中使用的最多的就是双重校验锁和内部静态类的两种实现方式,主要优点是程序执行效率高,适应多线程操作。
接下来看下builder的实现

 public static class builder {
  private final context context;
  private downloader downloader;
  private executorservice service;
  private cache cache;
  private listener listener;
  private requesttransformer transformer;
  private list<requesthandler> requesthandlers;
  private bitmap.config defaultbitmapconfig;

  private boolean indicatorsenabled;
  private boolean loggingenabled;

  /** 
   * 根据context获取application的context
   * 此方式主要是为了避免context和单例模式的生命周期不同而造成内存泄漏的问题 
   */
  public builder(context context) {
   ...
   this.context = context.getapplicationcontext();
  }

   /** 设置图片的像素格式,默认为argb_8888 */
  public builder defaultbitmapconfig(bitmap.config bitmapconfig) {
   ...
   this.defaultbitmapconfig = bitmapconfig;
   return this;
  }

  /** 自定义下载器,默认okhttp,具体的实现类是okhttpdownloader */
  public builder downloader(downloader downloader) {
   ...
   this.downloader = downloader;
   return this;
  }

  /** 自定义线程池,默认的实现是picassoexecutorservice */
  public builder executor(executorservice executorservice) {
   ...
   this.service = executorservice;
   return this;
  }

  /** 自定义缓存策略,默认实现为lrucache */
  public builder memorycache(cache memorycache) {
   ...
   this.cache = memorycache;
   return this;
  }

  /** 图片加载失败的一个回调事件 */
  public builder listener(listener listener) {
   ...
   this.listener = listener;
   return this;
  }

  /** 请求的转换,在request被提交之前进行转换 */
  public builder requesttransformer(requesttransformer transformer) {
   ...
   this.transformer = transformer;
   return this;
  }

  /** 自定义加载图片的来源 */
  public builder addrequesthandler(requesthandler requesthandler) {
   ...
   requesthandlers.add(requesthandler);
   return this;
  }

  //省略调试相关方法

  /** create the {@link picasso} instance. */
  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);
   // 返回picasso的对象
   return new picasso(context, dispatcher, cache, listener, transformer, requesthandlers, stats,
     defaultbitmapconfig, indicatorsenabled, loggingenabled);
  }
 }

又是一个非常经典的设计模式,建造者模式或者被称为buider模式,最大的特点就是链式调用,使调用者的代码逻辑简洁,同时扩展性非常好。

我们阅读优秀框架源码的好处就在于学习里面的设计思想,最终能够使用到自己的项目中

with方法分析完了,我们得到了一个picasso的对象

load(imageurl)方法

 public requestcreator load(uri uri) {
  return new requestcreator(this, uri, 0);
 }

load重载方法比较多,但是都比较简单就是创建了一个requestcreator对象

 requestcreator(picasso picasso, uri uri, int resourceid) {
  this.picasso = picasso;
  this.data = new request.builder(uri, resourceid, picasso.defaultbitmapconfig);
 }

又是一个建造者模式,得到了一个request.builder对象赋值给了data变量。

into(imageview)方法

这个方法相对复杂一些,注释尽量描述的清楚一些,看代码

 public void into(imageview target, callback callback) {
  long started = system.nanotime();
  // 只能在主线程中调用
  checkmain();

  // hasimage()的判断逻辑是设置了uri或者resourceid返回true
  // 如果都未设置则判断是否设置了placeholder,也就是默认显示的图片
  if (!data.hasimage()) {
   picasso.cancelrequest(target);
   if (setplaceholder) {
    setplaceholder(target, getplaceholderdrawable());
   }
   return;
  }

  // 当设置了fit()时deferred值为true,也就是完全填充
  if (deferred) {
   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;
   }
   // 根据target也就是imageview的大小下载图片
   data.resize(width, height);
  }
  // 见下方详解1
  request request = createrequest(started);
  // 这个方法的作用就是根据上面的到的request对象里面绑定的一些参数来生成一个字符串作为key值,
  // 逻辑比较清晰,主要包括stablekey(这个是用户自定义的key值,在第二篇文章中有介绍)、uri、旋转角度、大小、填充方式。
  string requestkey = createkey(request);
  // 根据用户的设置是否从缓存里取图片信息
  if (shouldreadfrommemorycache(memorypolicy)) {
   // 在lrucache中使用linkedhashmap<string, bitmap>来保存图片信息,key就是上面生成的requestkey
   // 在lrucache的get方法中返回bitmap对象,并记录命中或者未命中。
   bitmap bitmap = picasso.quickmemorycachecheck(requestkey);
   if (bitmap != null) {
    picasso.cancelrequest(target);
    setbitmap(target, picasso.context, bitmap, memory, nofade, picasso.indicatorsenabled);
    // 这个callback是异步加载图片的一个回调,之前忘记介绍了,看来需要再补充一篇文章来介绍异步和同步请求
    if (callback != null) {
     callback.onsuccess();
    }
    return;
   }
  }
  // 如果有设置了默认显示的图片,则先将其显示出来
  if (setplaceholder) {
   setplaceholder(target, getplaceholderdrawable());
  }

  // 又出来一个imageviewaction,可以看到里面传递了前面准备好的全部数据,那么这个对象又是做什么的呢?
  // 在imageviewaction代码中提供了三个方法complete、error、cancel,所以可以猜想这个是用作处理最后的下载结果的
  // 如果成功了就将其显示出来,如果失败则显示用户通过error方法设置的图片
  action action =
    new imageviewaction(picasso, target, request, memorypolicy, networkpolicy, errorresid,
      errordrawable, requestkey, tag, callback, nofade);
  // 这里又回到了picasso类中,见下方详解2
  picasso.enqueueandsubmit(action);
 }

详解1 createrequest

 private request createrequest(long started) {
  // 返回nextid的值并将其+1,有一个与之对应的方法是incrementandget,这个表示先+1再返回
  int id = nextid.getandincrement();

  // 这里面构造了一个request对象,它是一个实体类用来存放我们请求图片的一些参数
  // 包括地址,大小,填充方式,旋转参数,优先级等等
  request request = data.build();
  request.id = id;
  request.started = started;

  // 判断是否有进行request转化,在上一篇文章中介绍了转换的方法
  request transformed = picasso.transformrequest(request);
  if (transformed != request) {
   transformed.id = id;
   transformed.started = started;
  }

  return transformed;
 }

详解2 enqueueandsubmit

从名字可以看到是将action加入到了一个队列中,经过几次转换过程,从picasso类中跑到了dispatcher类中,这个我们在上面提到过,是一个调度器,下面我们进入dispatcher中看看实现逻辑

dispatcher.dispatchsubmit(action);

再次经过几经周转,最终的实现代码如下

 void performsubmit(action action, boolean dismissfailed) {
  // 首先根据tag判断是否已经下发了暂停下载的命令,pausedtags是weakhashmap类型的集合
  if (pausedtags.contains(action.gettag())) {
   pausedactions.put(action.gettarget(), action);
   return;
  }
  // huntermap是linkedhashmap<string, bitmaphunter>()类型的对象,用来保存还未执行的下载请求
  bitmaphunter hunter = huntermap.get(action.getkey());
  if (hunter != null) {
   // 如果新的请求的key值在linkedhashmap中存在,则合并两次请求,并重新处理优先级
   hunter.attach(action);
   return;
  }

  // 这个方法主要用来判断该请求采用哪一种requesthandler,picasso提供了7种,我们也可以自定义
  hunter = forrequest(action.getpicasso(), this, cache, stats, action);
  // 将hunter添加到线程池中,hunter是runnable的一个实现
  hunter.future = service.submit(hunter);
  huntermap.put(action.getkey(), hunter);
  if (dismissfailed) {
   failedactions.remove(action.gettarget());
  }
 }

提交到线程池之后就等待线程池调度了,一旦有空闲线程则将会执行bitmaphunter的run方法

// 这里只保留了关键的代码,调用了hunt方法,得到了result对象,然后再通过dispatcher进行分发
 public void run() {
  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是如何得到的
  // 在构造方法中this.retrycount = requesthandler.getretrycount();
  // 那么来看getretrycount()方法得到的值是否为0,代码中一共有七个类重载了requesthandler
  // 在requesthandler类中默认返回0,而只有networkrequesthandler重写了getretrycount()方法,返回2
  // 因此就是说当不是从网络请求图片时data.networkpolicy = networkpolicy.offline.index
  data.networkpolicy = retrycount == 0 ? networkpolicy.offline.index : networkpolicy;
  // 七个类重载了requesthandler并且都实现了自己的load方法
  // 这里面我们只看网络相关的networkrequesthandler,其余的感兴趣的童鞋可以自己看下代码
  // 我们先看下下面的关于 networkrequesthandler中load方法的代码,再回来继续分析
  requesthandler.result result = requesthandler.load(data, networkpolicy);
  if (result != null) {
   loadedfrom = result.getloadedfrom();
   exifrotation = result.getexiforientation();
   // 解析bitmap
   bitmap = result.getbitmap();
   if (bitmap == null) {
    inputstream is = result.getstream();
    try {
     bitmap = decodestream(is, data);
    } finally {
     utils.closequietly(is);
    }
   }
  }
  // 这一段主要是看用户是否设置图片的转换处理
  if (bitmap != null) {
   stats.dispatchbitmapdecoded(bitmap);
   if (data.needstransformation() || exifrotation != 0) {
    synchronized (decode_lock) {
     if (data.needsmatrixtransform() || exifrotation != 0) {
      bitmap = transformresult(data, bitmap, exifrotation);、
     }
     if (data.hascustomtransformations()) {
      bitmap = applycustomtransformations(data.transformations, bitmap);
     }
    }
    if (bitmap != null) {
     stats.dispatchbitmaptransformed(bitmap);
    }
   }
  }
  return bitmap;
 }
/** 
 * okhttpdownloader中的load方法,返回了result对象
 */
 public result load(request request, int networkpolicy) throws ioexception {
  // 这里面如果我们自己没有自定义下载器,则执行的是okhttpdownloader中的load方法,继续深入到load方法中一探究竟,代码在下方了,这里面得到的response是okhttp给我们返回来的
  response response = downloader.load(request.uri, request.networkpolicy);
  // 得到加载位置是sdcard还是网络
  picasso.loadedfrom loadedfrom = response.cached ? disk : network;
  // 下面分别获取了bitmap和inputstream,同时返回了result对象,我们返回到上面继续分析
  bitmap bitmap = response.getbitmap();
  if (bitmap != null) {
   return new result(bitmap, loadedfrom);
  }

  inputstream is = response.getinputstream();
  if (loadedfrom == network && response.getcontentlength() > 0) {
   stats.dispatchdownloadfinished(response.getcontentlength());
  }
  return new result(is, loadedfrom);
 }

/** 
 * 这个方法中主要使用了cachecontrol来承载缓存策略,同时将request对象传入了okhttp中
 * 看到这里picasso源码已经走到了尽头,如果想继续分析,只能查看okhttp的代码了,目前我还没有通读过,
 * 所以我们将得到的结果向上继续看了,以后有时间我也会更新一些关于okhttp的源码解析。
 * but 我们目前只看到了判断内存中是否有缓存,sdcard的缓存还没有判断呢?
 * 没错,关于sdcard的读取和写入都是有okhttp来完成的,当然了我们也可以自定义下载器,
 * 在这里就能看出来picasso和okhttp果然是亲戚啊!连sdcard的缓存都帮忙实现了。
 */
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);
  }

  com.squareup.okhttp.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());
 }

走到了这里我们已经得到了结果,是一个result对象,然后再通过dispatcher进行分发,进入dispatcher类中,最终执行的方法如下

 void performcomplete(bitmaphunter hunter) {
  // 判断用户是否设置了写缓存,默认是需要写入内存的
  if (shouldwritetomemorycache(hunter.getmemorypolicy())) {
   cache.set(hunter.getkey(), hunter.getresult());
  }
  // huntermap我们在前面介绍过了,用来保存还未执行的下载请求,因此下载完成之后将其remove到
  huntermap.remove(hunter.getkey());
  // 接着看batch的实现
  batch(hunter);
 }
 private void batch(bitmaphunter hunter) {
  // 将bitmaphunter对象加入到了batch变量中,batch是一个arraylist类型的集合
  batch.add(hunter);
  // 到这里并没有直接将图片显示出来,而是填加到list中,发送了一个延迟消息,延迟200ms
  // 其实这是一个批处理,让本次事件尽快结束,不影响界面的其他操作
  // 下面我们跟进handler的hunter_delay_next_batch语句中
  if (!handler.hasmessages(hunter_delay_next_batch)) {
   handler.sendemptymessagedelayed(hunter_delay_next_batch, batch_delay);
  }
 }
 void performbatchcomplete() {
  list<bitmaphunter> copy = new arraylist<bitmaphunter>(batch);
  batch.clear();
  // 将batch里的数据复制了一份,又通过mainthreadhandler发送了一个hunter_batch_complete的消息
  // mainthreadhandler是怎么来的呢?原来是在dispatcher的构造方法中传进来的,那么我们就要回头找找什么时候创建的dispatcher对象
  // 原来是在picasso的builder类build的时候创建的,而handler也就是在picasso类中定义,代码如下
  mainthreadhandler.sendmessage(mainthreadhandler.obtainmessage(hunter_batch_complete, copy));
  logbatch(copy);
 }

几经周转,最终我们又回到了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);
     }
     break;
    }
   }
  }
 };

上面的代码比较好理解了,我们传进来的是由多个bitmaphunter对象组成的list,在这里做个遍历调用complete方法。这时候已经回到了主线成中,图片马上就要显示出来了

 void complete(bitmaphunter hunter) {
  action single = hunter.getaction();
  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();
  loadedfrom from = hunter.getloadedfrom();
  // 这里面来说一下single和joined,还记不记得前面分析到dispatcher类中的performsubmit方法时
  // 判断了huntermap中如果有相同的key值则执行hunter.attach(action);
  // 因此single得到的action是huntermap中没有相同的key值时的action
  // 而当huntermap中存在未处理的key与新的请求的key值相同时则将action添加到了bitmaphunter类的actions对象中
  // 因此joined保存的就是与single中具有相同key值的数据,所以要分别处理
  if (single != null) {
   deliveraction(result, from, single);
  }

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

接下来进入deliveraction方法中

 private void deliveraction(bitmap result, loadedfrom from, action action) {
  ...
  // 关键代码就这一句
  action.complete(result, from);
  ...
 }

此时进入到了imageviewaction中的complete方法中,我们在上面提到过imageviewaction类的作用,是用来处理最后的下载结果的,好激动啊!图片马上就显示出来了~~~

 @override 
 public void complete(bitmap result, picasso.loadedfrom from) {
  imageview target = this.target.get();
  context context = picasso.context;
  boolean indicatorsenabled = picasso.indicatorsenabled;
  // 关键代码,进入picassodrawable的setbitmap方法中一探究竟
  picassodrawable.setbitmap(target, context, result, from, nofade, indicatorsenabled);

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

 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对象,继承了bitmapdrawable
  picassodrawable drawable =
    new picassodrawable(context, bitmap, placeholder, loadedfrom, nofade, debugging);
  // 至此图片终于终于显示出来了~~~~~~
  target.setimagedrawable(drawable);
 }

写源码分析太苦了,我已经尽可能的描述的清楚一些,如果有哪块不太理解的,可以和我交流~~~

Android图片加载利器之Picasso源码解析

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。