博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Picasso-源码解析(一)
阅读量:6965 次
发布时间:2019-06-27

本文共 16579 字,大约阅读时间需要 55 分钟。

前言

使用的是picasso最新版本 github地址:https://github.com/square/picasso 版本:2.71828

简单例子

代码
override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        Picasso.get().setIndicatorsEnabled(true)        Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest)        Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest2)    }复制代码

代码很简单,令大家比较惊讶的应该是左上角的蓝三角,其实原图是没有的,由于我加入了Picasso.get().setIndicatorsEnabled(true),打开了指示标志。

这里先直接说明下代表的意思,后面我们再慢慢深入。 Picasso.java

public enum LoadedFrom {    //内存加载,绿色    MEMORY(Color.GREEN),    //磁盘加载,蓝色    DISK(Color.BLUE),    //网络加载,红色    NETWORK(Color.RED);    final int debugColor;    LoadedFrom(int debugColor) {      this.debugColor = debugColor;    }  }复制代码

一般来说,绝大多数的图片框架都是三级缓存,Picasso也不例外。 Glide,Fresco我还未深入了解,但是Picasso这个标识还是很有用的。很容易让我们能够明白是哪种加载方式。

先简单的说明下这是如何去实现的。

PicassoDrawable.java

@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);      }    }  //前面都是绘制原图的    if (debugging) {      //这里判断下,绘制下标识      drawDebugIndicator(canvas);    }  } private void drawDebugIndicator(Canvas canvas) {    DEBUG_PAINT.setColor(WHITE);    Path path = getTrianglePath(0, 0, (int) (16 * density));    canvas.drawPath(path, DEBUG_PAINT);    //根据加载方式    DEBUG_PAINT.setColor(loadedFrom.debugColor);    path = getTrianglePath(0, 0, (int) (15 * density));    canvas.drawPath(path, DEBUG_PAINT);  }复制代码

源码解析

前面只是简单的介绍了一下Picasso的一个小功能,下面还是通过上面那个简单的加载图片代码,一步步跟入源码,来介绍下是如何实现图片加载的,如何做到三级缓存的。

Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest)复制代码
  1. get 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 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);      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);    }复制代码

非常简单的一个单例模式,和建造者模式。单例模式就不过多说了,这里主要介绍下建造者模式,一般来说对于参数比较多的构造方法,使用建造者模式,就可以直接使用链式的方式,来配置对象。

这里直接使用Picasso.get其实是获取了默认的一个Picasso对象,然后帮你默认的配置了LruCache,PicassoExecutorService,RequestTransformer,OkHttp3Downloader,Stats,Dispatcher

很显然,一般来说,肯定是会提供一个自定义的方式,不然就太low了。

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;    }  }复制代码

你可以使用Picasso.Builder先自己构建一个Picasso对象,然后再调用这个方法,接下来就可以使用Picasso.get()来获取自己的配置的单例了。

  1. load load方法有很多重载,这里还是以String为例子。
public RequestCreator load(@Nullable String path) {    if (path == null) {      return new RequestCreator(this, null, 0);    }    if (path.trim().length() == 0) {      throw new IllegalArgumentException("Path must not be empty.");    }    return load(Uri.parse(path));  }public RequestCreator load(@Nullable Uri uri) {    return new RequestCreator(this, uri, 0);  }复制代码

很显然,load方法只是为了获取一个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);  }复制代码

RequestCreator里面最重要的,其实就是data也就是一个Request.Builder,从这里其实我们很明显的可以看出,RequestCreator,顾名思义,就是为了创建一个Request,最终的Request肯定是由data.build生成的。但是目前只是new了一个Request.Builder对象,并没有调用。这是因为后面我们还需要往Request.Builder塞入很多不同的参数。

由上图其实我们可以发现,我们常用的一些链式方法,如centerCrop等,其实就是调用了Request.Builder对象的方法,只是为了构建一个Request.

  1. into 这里才是真正发起请求的地方。
public void into(ImageView target) {    into(target, null);  }public void into(ImageView target, Callback callback) {    long started = System.nanoTime();    //判断下是否为主线程    checkMain();    if (target == null) {      throw new IllegalArgumentException("Target must not be null.");    }    //如果uri为空或者resId为0,则直接取消请求,设置为placeholder图片    if (!data.hasImage()) {      picasso.cancelRequest(target);      if (setPlaceholder) {        setPlaceholder(target, getPlaceholderDrawable());      }      return;    }    //这里就牛逼了,后面详细讲    if (deferred) {      if (data.hasSize()) {        throw new IllegalStateException("Fit cannot be used with resize.");      }      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;      }      data.resize(width, height);    }    //简单理解,就是调用了data.build(),生成一个Request    Request request = createRequest(started);    //这里通过request生成一个String,用来后面key-value保存图片在LruCache中    String requestKey = createKey(request);        if (shouldReadFromMemoryCache(memoryPolicy)) {      //如果前面请求过了,会缓存到内存,这边再请求,还是会生成了相同的key,直接从cache中获取到了Bitmap      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;      }    }    //没有从内存中获取到缓存,先设置placeholder图片    if (setPlaceholder) {      setPlaceholder(target, getPlaceholderDrawable());    }    //创建一个action    Action action =        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,            errorDrawable, requestKey, tag, callback, noFade);     //提交一个action    picasso.enqueueAndSubmit(action);  }复制代码

这里其实非常简单的分析了下。

这里面有2步单独拿出来说。

  1. deferred的作用
public RequestCreator fit() {    deferred = true;    return this;  }  /** Internal use only. Used by {
@link DeferredRequestCreator}. */ RequestCreator unfit() { deferred = false; return this; }....if (deferred) { if (data.hasSize()) { throw new IllegalStateException("Fit cannot be used with resize."); } int width = target.getWidth(); int height = target.getHeight(); //如果说imageview本身已经可以获取到宽高了,都不是0,那么就直接resize一下图片,如果说有一个是0,说明这个Imageview可能还没有布局完成,还没有自己的宽高,那么就在原来的`RequestCreator`外面再包了一层`DeferredRequestCreator ` if (width == 0 || height == 0) { if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } picasso.defer(target, new DeferredRequestCreator(this, target, callback)); return; } data.resize(width, height); }复制代码

用过了fit方法的人应该知道,调用后可以适配ImageView的尺寸,这里就是实现方式

下面我们来看看DeferredRequestCreator是如何实现的

DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) {    this.creator = creator;    this.target = new WeakReference<>(target);    this.callback = callback;    //实现很简单,就是给ImageView设置下监听    target.addOnAttachStateChangeListener(this);    if (target.getWindowToken() != null) {      onViewAttachedToWindow(target);    }  }  @Override public void onViewAttachedToWindow(View view) {    view.getViewTreeObserver().addOnPreDrawListener(this);  }//这里才是最关键的部分 @Override public boolean onPreDraw() {    ImageView target = this.target.get();    if (target == null) {      return true;    }    ViewTreeObserver vto = target.getViewTreeObserver();    if (!vto.isAlive()) {      return true;    }    int width = target.getWidth();    int height = target.getHeight();    if (width <= 0 || height <= 0) {      return true;    }    target.removeOnAttachStateChangeListener(this);    vto.removeOnPreDrawListener(this);    this.target.clear();    //获取到了ImageView的宽高后,调用resize重新设置了下宽高。    this.creator.unfit().resize(width, height).into(target, callback);    return true;  }复制代码
  1. 真正去加载图片的地方
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);  }/**下面是每一步的方法***/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);  }void dispatchSubmit(Action action) {    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));  } @Override public void handleMessage(final Message msg) {      switch (msg.what) {        case REQUEST_SUBMIT: {          Action action = (Action) msg.obj;          dispatcher.performSubmit(action);          break;        }    ... void performSubmit(Action action) {    performSubmit(action, true);  }复制代码

前面其实讲到了这里,我们再往后继续。从enqueueAndSubmit一步步往下,虽然调用了很多方法,但是最终,其实就是调用Dispatcher中的performSubmit方法。下面我们来具体分析下这个方法。

void performSubmit(Action action, boolean dismissFailed) {   ...       BitmapHunter hunter = hunterMap.get(action.getKey());    if (hunter != null) {      hunter.attach(action);      return;    }    ...   //一开始hunterMap肯定不包含action的key,所以会创建一个BitmapHunter    hunter = forRequest(action.getPicasso(), this, cache, stats, action);   //其实我们会发现BitmapHunter是一个Runnable,service是ExecutorService,可以理解为一个线程池,这里就直接执行一个Runnable    hunter.future = service.submit(hunter);    hunterMap.put(action.getKey(), hunter);  ...  }//通过传入的参数生成一个BitmapHunterstatic BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,      Action action) {    Request request = action.getRequest();    List
requestHandlers = picasso.getRequestHandlers(); for (int i = 0, count = requestHandlers.size(); i < count; i++) { RequestHandler requestHandler = requestHandlers.get(i); //最重要的地方在这里,遍历所有的requestHandler,看哪个requestHandler能够处理request,后面再详细介绍 if (requestHandler.canHandleRequest(request)) { return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler); } } return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER); }复制代码

为了让后面我们可以更好的理解,我们先回过头来,看一下requestHandlers是什么东西,为什么要先找出能够处理当前RequestRequestHandler.

一直往前找发现是在Picasso的构造方法里面初始化的

Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,      RequestTransformer requestTransformer, List
extraRequestHandlers, Stats stats, Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) { ... List
allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount); //resource图片处理,比如R.drawable这种 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)); //asset资源处理 allRequestHandlers.add(new AssetRequestHandler(context)); //文件资源处理 allRequestHandlers.add(new FileRequestHandler(context)); //网络资源处理 allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats)); ... }复制代码

都是通过load方法之后的参数来判断的。我们这里以NetworkRequestHandler为例

@Override public boolean canHandleRequest(Request data) {    String scheme = data.uri.getScheme();    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));  }复制代码

如果说uri是http或者https就可以由NetworkRequestHandler来处理。

那么我们继续回到刚才那个地方

void performSubmit(Action action, boolean dismissFailed) {...    hunter = forRequest(action.getPicasso(), this, cache, stats, action);   //其实我们会发现BitmapHunter是一个Runnable,service是ExecutorService,可以理解为一个线程池,这里就直接执行一个Runnable    hunter.future = service.submit(hunter);    hunterMap.put(action.getKey(), hunter);  ...  }复制代码

获取到可以处理RequestHandler之后创建了一个BitmapHunter,然后调用service.submit最终其实是调用Runnablerun方法,我们继续跟入。

@Override public void run() {    try {      ...      result = hunt();     ...    }  }Bitmap hunt() throws IOException {    Bitmap bitmap = null;    //在真正发起请求之前,再次判断下,是否从内存中获取图片    if (shouldReadFromMemoryCache(memoryPolicy)) {      bitmap = cache.get(key);      if (bitmap != null) {        stats.dispatchCacheHit();        loadedFrom = MEMORY;        if (picasso.loggingEnabled) {          log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");        }        return bitmap;      }    }      networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;    //前面其实我们已经分析过,requestHandler其实是NetworkRequestHandler,等下单独提出load方法来讲    RequestHandler.Result result = requestHandler.load(data, networkPolicy);    if (result != null) {      loadedFrom = result.getLoadedFrom();      //获取下exif格式信息,一般情况用不到,这里不深入      exifOrientation = result.getExifOrientation();      //获取到真正的bitmap      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) {     ...      if (data.needsTransformation() || exifOrientation != 0) {        synchronized (DECODE_LOCK) {          if (data.needsMatrixTransform() || exifOrientation != 0) {            bitmap = transformResult(data, bitmap, exifOrientation);            if (picasso.loggingEnabled) {              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());            }          }          if (data.hasCustomTransformations()) {            bitmap = applyCustomTransformations(data.transformations, bitmap);            if (picasso.loggingEnabled) {              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");            }          }        }        if (bitmap != null) {          stats.dispatchBitmapTransformed(bitmap);        }      }    }    return bitmap;复制代码

下面我们还是具体再看看NetworkRequestHandler

@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);  }复制代码

其实这里最关键的部分就是response.cacheResponse()这一句代码。因为之前我一直以为Picasso使用的是DiskLruCache来进行磁盘缓存。但是一直找不到实现的地方。一直找到这里才恍然大悟,Picasso的磁盘缓存是利用http协议中的cache-control去实现的。 然后使用的其实是Okhttp3实现了http协议,其中磁盘缓存确实也是用DiskLruCache来实现的。

总结

后面还会继续深入。

转载地址:http://uxwsl.baihongyu.com/

你可能感兴趣的文章
sqlserver 数据库死锁
查看>>
linux 下 apache tomcat 高可用负载集群
查看>>
正则表达式总结
查看>>
mysql-mm(双主)
查看>>
Ubuntu安装Trac+svn+apache+ldap+[mysql]认证
查看>>
WIN7的IE已停止工作故障的一次处理
查看>>
FMDB的使用
查看>>
telnet ctrl+c 不退出问题解决
查看>>
Linux LVM的创建及扩展
查看>>
玩转树莓派——制作包含Windows 10 IoT Core和Raspbian的离线安装介质
查看>>
C# 枚举
查看>>
CSS兼容hack常用方法
查看>>
配置管理小报100525:使用CVS中的常见故障
查看>>
JDK的安装[Centos]
查看>>
Java生产者消费者模式
查看>>
Android窗口机制(四)ViewRootImpl与View和WindowManager
查看>>
maven仓库地址
查看>>
NodeJs:使用connect构建简单的用户登录
查看>>
推荐一款C/C++在线编译器
查看>>
Maven的微信公众号项目部署到SAE用户消息无响应
查看>>