图片加载框架Picasso - 源码分析

前一篇文章讲了Picasso的详细用法,Picasso 是一个强大的图片加载缓存框架,一个非常优秀的开源库,学习一个优秀的开源库,,我们不仅仅是学习它的用法,停留在使用API层面,我们也要试着去阅读源码,有两个方面的原因,第一,熟悉了源码我们才能更好的驾驭,项目中做我们需要的定制。第二,学习它的设计思想、编码风格、代码的架构,然后在项目中对这些好的思想和架构加以实践,变成自己的知识,这样才会对我们有更多的提升和帮助。这也是我们学习的目的,因此这篇文章对Picasso 的源码和流程做一个分析。

一、Picasso 加载图片流程图

Picasso-图片加载流程.png

上面就是Picasso加载图片的流程,图画的丑,各位见谅。

二、重要的类介绍

(0)Picasso: 图片加载、转换、缓存的管理类。单列模式 ,通过with方法获取实例,也是加载图片的入口。
(1)RequestCreator: Request构建类,Builder 模式,采用链式设置该Request的属性(如占位图、缓存策略、裁剪规则、显示大小、优先级等等)。最后调用build()方法生成一个请求(Request)。
(2)DeferredRequestCreator:RequestCreator的包装类,当创建请求的时候还不能获取ImageView的宽和高的时候,则创建一个DeferredRequestCreator,DeferredRequestCreator里对 target 设置监听,直到可以获取到宽和高的时候重新执行请求创建。
(3) Action: 请求包装类,存储了该请求和RequestCreator设置的这些属性,最终提交给线程执行下载。
(4)Dispatcher:分发器,分发执行各种请求、分发结果等等。
(5)PicassoExecutorService:Picasso使用的线程池,默认池大小为3。
(6)LruCache:一个使用最近最少使用策略的内存缓存。
(7)BitmapHunter:这是Picasso的一个核心的类,开启线程执行下载,获取结果后解码成Bitmap,然后做一些转换操作如图片旋转、裁剪等,如果请求设置了转换器Transformation,也会在BitmapHunter里执行这些转换操作。
(8)NetworkRequestHandler:网络请求处理器,如果图片需要从网络下载,则用这个处理器处理。
(9)FileRequestHandler:文件请求处理器,如果请求的是一张存在文件中的图片,则用这个处理器处理。
(10)AssetRequestHandler: Asset 资源图片处理器,如果是加载asset目录下的图片,则用这个处理器处理。
(11)ResourceRequestHandler:Resource资源图片处理器,如果是加载res下的图片,则用这个处理器处理。
(12)ContentStreamRequestHandler: ContentProvider 处理器,如果是ContentProvider提供的图片,则用这个处理器处理
(13)MediaStoreRequestHandler: MediaStore 请求处理器,如果图片是存在MediaStore上的则用这个处理器处理。
(14)ContactsPhotoRequestHandler:ContactsPhoto 请求处理器,如果加载com.android.contacts/ 下的tu图片用这个处理器处理。如:

// e.g. content://com.android.contacts/contacts/38)
//匹配的路径如下:
static {
    matcher = new UriMatcher(UriMatcher.NO_MATCH);
    matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", ID_LOOKUP);
    matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*", ID_LOOKUP);
    matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/photo", ID_THUMBNAIL);
    matcher.addURI(ContactsContract.AUTHORITY, "contacts/#", ID_CONTACT);
    matcher.addURI(ContactsContract.AUTHORITY, "display_photo/#", ID_DISPLAY_PHOTO);
  }

上面8-14 是默认的提供的几个处理器,分别处理不同来源的请求。

(15)Response: 返回的结果信息,Stream流或者Bitmap。
(16)Request: 请求实体类,存储了应用在图片上的信息。
(17)Target:图片加载的监听器接口,有3个回调方法,onPrepareLoad 在请求提交前回调,onBitmapLoaded 请求成功回调,并返回Bitmap,onBitmapFailed请求失败回调。
(18)PicassoDrawable:继承BitmapDrawable,实现了过渡动画和图片来源的标识(就是图片来源的指示器,要调用 setIndicatorsEnabled(true)方法才生效),请求成功后都会包装成BitmapDrawable显示到ImageView 上。
(19)OkHttpDownloader:用OkHttp实现的图片下载器,默认就是用的这个下载器。
(20)UrlConnectionDownloader:使用HttpURLConnection 实现的下载器。
(21)MemoryPolicy: 内存缓存策略,一个枚举类型。
(22)NetworkPolicy: 磁盘缓存策略,一个枚举类型。
(23) Stats: 这个类相当于日志记录,会记录如:内存缓存的命中次数,丢失次数,下载次数,转换次数等等,我们可以通过StatsSnapshot类将日志打印出来,看一下整个项目的图片加载情况。
(24)StatsSnapshot :状态快照,和上面的Stats对应,打印Stats纪录的信息。

以上就是Picasso 的一些关键的类的介绍(还有一些简单的没有列举)。

三、流程分析

上一节介绍了Picasso 的一些关键类,接下来就以加载网络图片为例,分析Picasso加载图片的整个流程

Picasso.with(this).load(URL)
                .placeholder(R.drawable.default_bg)
                .error(R.drawable.error_iamge)
                .into(mBlurImage);

1, 获取Picasso instance

首先要获取一个Picasso对象,采用的单例模式

//单例模式获取Picasso 对象
public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }

// 真正new 的地方在build()方法里
 public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        //配置默认的下载器,首先通过反射获取OkhttpClient,如果获取到了,就使用OkHttpDwownloader作为默认下载器
        //如果获取不到就使用UrlConnectionDownloader作为默认下载器
        downloader = Utils.createDefaultDownloader(context);
      }
      if (cache == null) {
       // 配置内存缓存,大小为手机内存的15%
        cache = new LruCache(context);
      }
      if (service == null) {
       // 配置Picaso 线程池,核心池大小为3
        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);
    }

2, 通过load方法生成一个RequestCreator

通过load方法生成一个RequestCreator,用链式api 来构建一个图片下载请求

//load有几个重载方法,参数为string和File 的重载最重都会包装成一个Uri 调用这个方法
 public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
  }
// 如果是加载资源id 的图片会调用这个方法
 public RequestCreator load(int resourceId) {
    if (resourceId == 0) {
      throw new IllegalArgumentException("Resource ID must not be zero.");
    }
    return new RequestCreator(this, null, resourceId);
  }

RequestCreator提供了很多的API 来构建请求,如展位图、大小、转换器、裁剪等等,这些API其实是为对应的属性赋值,最终会在into方法中构建请求。

// 配置占位图,在加载图片的时候显示
public RequestCreator placeholder(int placeholderResId) {
    if (!setPlaceholder) {
      throw new IllegalStateException("Already explicitly declared as no placeholder.");
    }
    if (placeholderResId == 0) {
      throw new IllegalArgumentException("Placeholder image resource invalid.");
    }
    if (placeholderDrawable != null) {
      throw new IllegalStateException("Placeholder image already set.");
    }
    this.placeholderResId = placeholderResId;
    return this;
  }

// 配置真正显示的大小
 public RequestCreator resize(int targetWidth, int targetHeight) {
    data.resize(targetWidth, targetHeight);
    return this;
  }

3,into 添加显示的View,并且提交下载请求

into方法里面干了3件事情:

1, 判断是否设置了fit 属性,如果设置了,再看是否能够获取ImageView 的宽高,如果获取不到,生成一个DeferredRequestCreator(延迟的请求管理器),然后直接return,在DeferredRequestCreator中当监听到可以获取ImageView 的宽高的时候,再执行into方法。

2, 判断是否从内存缓存获取图片,如果没有设置NO_CACHE,则从内存获取,命中直接回调CallBack 并且显示图片。

3, 如果缓存未命中,则生成一个Action,并提交Action。

 public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
   // 检查是否在主线程
    checkMain();

    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }
   //如果没有url或者resourceId 则取消请求
    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }
    //判断是否设置了fit属性
    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());
        }
       //如果获取不到宽高,生成一个DeferredRequestCreator(延迟的请求管理器),然后直接return,
      //在DeferredRequestCreator中当监听到可以获取ImageView 的宽高的时候,再执行into方法。
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      data.resize(width, height);
    }

    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 (picasso.loggingEnabled) {
          log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
        }
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }

    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
   //内存缓存未命中或者设置了不从内存缓存获取,则生成一个Action ,提交执行。
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);// 提交请求
  }

4, 提交、分发、执行请求。

会经过下面这一系列的操作,最重将Action 交给BitmapHunter 执行。
enqueueAndSubmit -> submit -> dispatchSubmit -> performSubmit:

//将action 保存到了一个Map 中,目标View作为key
 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);
  }

 
//执行请求提交
//1, 先查看保存暂停tag表里面没有包含Action的tag,如果包含,则将Action 存到暂停Action表里
//2,从BitmapHunter表里查找有没有对应action的hunter,如果有直接attach
//3, 为这个请求生成一个BitmapHunter,提交给线程池执行
 void performSubmit(Action action, boolean dismissFailed) {
    // 先查看保存暂停tag表里面没有包含Action的tag,如果包含,则将Action 存到暂停Action表里
    if (pausedTags.contains(action.getTag())) {
      pausedActions.put(action.getTarget(), action);
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
            "because tag '" + action.getTag() + "' is paused");
      }
      return;
    }

    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
      hunter.attach(action);
      return;
    }
   // 如果线程池北shutDown,直接return
    if (service.isShutdown()) {
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
      }
      return;
    }
    // 为请求生成一个BitmapHunter
    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);//提交执行
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
    }
  }

5,指定对应的处理器(RequestHandler)

在上面执行的请求的performSubmit 方法里,调用了forRequest 方法为对应的Action 生成一个BitmapHunter,里面有一个重要的步骤,指定请求处理器(在上面一节介绍Picasso有7种请求处理器,看一下对应的代码:

static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
      Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    // Index-based loop to avoid allocating an iterator.
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
     //  循环请求处理器列表,如果找到有能处理这个请求的请求处理器
     // 则生成BitmapHunter
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }

从Picasso里获取一个处理器列表,然后循环列表,看是否有能处理该请求的处理器,如果有,则生成BitmapHunter,那么这个请求处理器的列表在哪儿初始化的呢?请看源码:

// 1,首先调用了getRequestHandlers
 List<RequestHandler> getRequestHandlers() {
    return requestHandlers;
  }

// 2 requestHandlers 列表是在Picasso 构造函数里出实话的
 Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
 
    ....
   //前面代码省略
 
   // 添加了7个内置的请求处理器
  // 如果你自己通过Builder添了额外的处理器,也会添加在这个列表里面

    int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
    int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
    List<RequestHandler> allRequestHandlers =
        new ArrayList<RequestHandler>(builtInHandlers + extraCount);

    // ResourceRequestHandler needs to be the first in the list to avoid
    // forcing other RequestHandlers to perform null checks on request.uri
    // to cover the (request.resourceId != 0) case.
    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));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);
   
  //后面代码省略 
    ...

  }

小结: 在Picasso 的构造函数里 初始化了内置的7中请求处理器,然后在生成BitmapHunter的时候,循环列表,找到可以处理对应请求的处理器。

6, 重点:BitmapHunter (图片捕获器)

上一节重要类介绍的时候介绍过BitmapHunter,BitmapHunter继承Runnable,其实就是开启一个线程执行最终的下载。看一下源码:
1, run() 方法

 @Override public void run() {
    try {
      updateThreadName(data);

      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
      }
     //  调用hunt() 方法获取最终结果
      result = hunt();

      if (result == null) {
        dispatcher.dispatchFailed(this);//如果为null,分发失败的消息
      } else {
        dispatcher.dispatchComplete(this);//如果不为null,分发成功的消息
      }
    } catch (Downloader.ResponseException e) {
      if (!e.localCacheOnly || e.responseCode != 504) {
        exception = e;
      }
      dispatcher.dispatchFailed(this);
    } catch (NetworkRequestHandler.ContentLengthException e) {
      exception = e;
      dispatcher.dispatchRetry(this);
    } catch (IOException e) {
      exception = e;
      dispatcher.dispatchRetry(this);
    } catch (OutOfMemoryError e) {
      StringWriter writer = new StringWriter();
      stats.createSnapshot().dump(new PrintWriter(writer));
      exception = new RuntimeException(writer.toString(), e);
      dispatcher.dispatchFailed(this);
    } catch (Exception e) {
      exception = e;
      dispatcher.dispatchFailed(this);
    } finally {
      Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
    }
  }

当将一个bitmapHunter submit 给一个线程池执行的时候,就会执行run() 方法,run里面调用的是hunt方法来获取结果,看一下hunt方法:

 Bitmap hunt() throws IOException {
    Bitmap bitmap = null;
   // 是否从内存缓存获取Bitmap
    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;
      }
    }

    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
   // 请求处理器处理请求,获取结果,Result里可能是Bitmap,可能是Stream
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifRotation = result.getExifOrientation();

      bitmap = result.getBitmap();

      // If there was no Bitmap then we need to decode it from the stream.
      if (bitmap == null) {
        InputStream is = result.getStream();
        try {
          bitmap = decodeStream(is, data);
        } finally {
          Utils.closeQuietly(is);
        }
      }
    }

    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_DECODED, data.logId());
      }
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifRotation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifRotation != 0) {
            //如果需要做转换,则在这里做转换处理,如角度旋转,裁剪等。
            bitmap = transformResult(data, bitmap, exifRotation);
            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;
  }

7,Downloader 下载器下载图片

上面的hunt方法获取结果的时候,最终调用的是配置的处理器的load方法,如下:

RequestHandler.Result result = requestHandler.load(data, networkPolicy);

加载网络图片用的是NetworkRequestHandler,匹配处理器,有个canHandleRequest 方法:

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

判断的条件是,Uri带有"http://" 或者 https:// 前缀则可以处理

我们接下来看一下NetworkRequestHandler的load方法:

 @Override public Result load(Request request, int networkPolicy) throws IOException {
    //最终调用downloader的load方法获取结果
    Response response = downloader.load(request.uri, request.networkPolicy);
    if (response == null) {
      return null;
    }

    Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;

    Bitmap bitmap = response.getBitmap();
    if (bitmap != null) {
      return new Result(bitmap, loadedFrom);
    }

    InputStream is = response.getInputStream();
    if (is == null) {
      return null;
    }
    // Sometimes response content length is zero when requests are being replayed. Haven't found
    // root cause to this but retrying the request seems safe to do so.
    if (loadedFrom == DISK && response.getContentLength() == 0) {
      Utils.closeQuietly(is);
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && response.getContentLength() > 0) {
      stats.dispatchDownloadFinished(response.getContentLength());
    }
    return new Result(is, loadedFrom);
  }

NetworkRequestHandler最终是调用的downloader 的load方法下载图片。内置了2个Downloader,OkhttpDownloader和UrlConnectionDownloader 。我们以UrlConnectionDownloader为例,来看一下load方法:

 @Override public Response load(Uri uri, int networkPolicy) throws IOException {
   // 如果SDK 版本大于等于14,安装磁盘缓存,用的是HttpResponseCache(缓存http或者https的response到文件系统)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      installCacheIfNeeded(context);
    }

    HttpURLConnection connection = openConnection(uri);
   //设置使用缓存
    connection.setUseCaches(true);

    if (networkPolicy != 0) {
      String headerValue;
     // 下面一段代码是设置缓存策略
      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        headerValue = FORCE_CACHE;
      } else {
        StringBuilder builder = CACHE_HEADER_BUILDER.get();
        builder.setLength(0);

        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
          builder.append("no-cache");
        }
        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
          if (builder.length() > 0) {
            builder.append(',');
          }
          builder.append("no-store");
        }

        headerValue = builder.toString();
      }

      connection.setRequestProperty("Cache-Control", headerValue);
    }

    int responseCode = connection.getResponseCode();
    if (responseCode >= 300) {
      connection.disconnect();
      throw new ResponseException(responseCode + " " + connection.getResponseMessage(),
          networkPolicy, responseCode);
    }

    long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
    boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));
    // 最后获取InputStream流包装成Response返回
    return new Response(connection.getInputStream(), fromCache, contentLength);
  }

小结:梳理一下调用链, BitmapHunter -> NetworkRequestHandler -> UrlConnectionDownloader(也有可能是OkHttpDownloader),经过这一系列的调用,最后在BitmapHunter 的run 方法中就可以获取到我们最终要的Bitmap。

8,返回结果并显示在Target上

在BitmapHunter获取结果后,分发器分发结果,通过Hander处理后,执行performComplete方法:

//1,
void performComplete(BitmapHunter hunter) {
    // 这里将结果缓存到内存
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    hunterMap.remove(hunter.getKey());// 请求完毕,将hunter从表中移除
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
    }
  }

// 2,然后将BitmapHunter添加到一个批处理列表,通过Hander发送一个批处理消息
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);
    }
  }
// 3,最后执行performBatchComplete 方法,通过主线程的Handler送处理完成的消息
void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
  }

// 4,最后在Picasso 中handleMessage,显示图片
 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;
        }
      //后面代码省略
      ...
  };
// 5,最后回调到ImageViewAction 的complete方法显示图片
@Override 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 并显示
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

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

小结:通过上面一系列的方法调用, performComplete -> batch —> performBatchComplete -> handleMessage -> complete 把BitmapHunter中获取到的结果回调到主线程,并且显示在Target上。

通过以上的8个步骤,就把图片从加载到显示的整个过程分析完了。

四,缓存特别说明

内存缓存很简单,用的是LRUCache,大小为 手机内存的15% ,上面代码中已经分析过了,这里不过多说明,这里重点说一下Disk Cahce。Picasso内存了2个默认的下载器,UrlConnectionDownloader和OkHttpDownloader,它们的磁盘缓存实现还是有一些差异的,看一下代码:

 public OkHttpDownloader(final File cacheDir, final long maxSize) {
    this(defaultOkHttpClient());
    try {
      client.setCache(new com.squareup.okhttp.Cache(cacheDir, maxSize));
    } catch (IOException ignored) {
    }
  }

在OkHttpDownloader 的构造方法里设置了磁盘缓存,使用的okHttp 的 DiskLruCache 实现的。

然后看一下UrlConnectionDownloader的磁盘缓存实现,代码:

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      installCacheIfNeeded(context);
    }
private static void installCacheIfNeeded(Context context) {
    // DCL + volatile should be safe after Java 5.
    if (cache == null) {
      try {
        synchronized (lock) {
          if (cache == null) {
            cache = ResponseCacheIcs.install(context);
          }
        }
      } catch (IOException ignored) {
      }
    }
  }

  private static class ResponseCacheIcs {
    static Object install(Context context) throws IOException {
      File cacheDir = Utils.createDefaultCacheDir(context);
      HttpResponseCache cache = HttpResponseCache.getInstalled();
      if (cache == null) {
        long maxSize = Utils.calculateDiskCacheSize(cacheDir);
        cache = HttpResponseCache.install(cacheDir, maxSize);
      }
      return cache;
    }

    static void close(Object cache) {
      try {
        ((HttpResponseCache) cache).close();
      } catch (IOException ignored) {
      }
    }
  }

** UrlConnectionDownloader 的磁盘缓存是用HttpResponseCache实现的**

尽管2种磁盘缓存实现的方式不一样,但是它们的最后结果都是一样的:

1,磁盘缓存的地址: 磁盘缓存的地址在:data/data/your package name/cache/picasso-cache /
2,磁盘缓存的大小:磁盘缓存的大小为 手机磁盘大小的2% ,不超过50M不小于5M。
3, 缓存的控制方式一样:都是在请求的header设置Cache-Control的值来控制是否缓存。

缓存清除:
有同学在前一篇文章(图片加载框架-Picasso最详细的使用指南)下面留言问怎么清除缓存,这里统一说一下:
1, 清除内存缓存:调用invalidate方法,如:

 Picasso.with(this)
                .invalidate("http://ww3.sinaimg.cn/large/610dc034jw1fasakfvqe1j20u00mhgn2.jpg");

清除指定url 的内存缓存。

但是Picasso没有提供清除全部内存缓存的方法,那就没有办法了吗?办法还是有的,LRUCahce 提供了clear方法的,只是Picasso没有向外部提供这个接口,因此可以通过反射获取到Picasso的cache字段,然后调用clear方法清除。

2, 清除磁盘缓存
很遗憾Picasso没有提供清除磁盘缓存的方法。它没有提供方法我们就自己想办法呗。

思路:很简单,既然我们知道磁盘缓存是存在:data/data/your package name/cache/picasso-cache 这个路径下的,那我们把这个文件夹下面的所有文件清除不就行了。

实现:

 private void clearDiskCache(){
        File cache = new File(this.getApplicationContext().getCacheDir(), "picasso-cache");
        deleteFileOrDirectory(cache.getPath());
    }

    public static void deleteFileOrDirectory(String filePath){
        if(TextUtils.isEmpty(filePath)){
            return;
        }
        try {
            File file = new File(filePath);
            if(!file.exists()){
                return;
            }
            if(file.isDirectory()){
                File files[] = file.listFiles();
                for(int i=0;i<files.length;i++){
                    deleteFileOrDirectory(files[i].getAbsolutePath());
                }
            }else{
                file.delete();
                Log.e("zhouwei","delete cache...");
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }

好了,就用上面一段代码就可以实现删除磁盘缓存了。

最后

以上就是对Picasso的源码分析,代码中的关键部分也有添加注释,到此,Picasso的使用和源码分析就讲完了,还没有看前一篇文章(图片加载框架-Picasso最详细的使用指南)的可以去看一下,如有问题,欢迎留言交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容