Picasso分析02

1 Downloader

1 Downloader接口

负责下载图片

//A mechanism机制 to load images from external resources such as a disk cache and/or the internet. 
public interface Downloader {
  // Downloader.Response containing either a Bitmap representation of the request or an
// InputStream for the image data. null can be returned to indicate a problem loading the bitmap.
  // 之前分析的NetworkRequestHandler中使用到了这个函数---函数1
  Response load(Uri uri, int networkPolicy) throws IOException;

  // Allows to perform a clean up for this {@link DDDownloader} including closing the disk cache
  // and other resources. 
  void shutdown(); // ---函数2

  /** Thrown for non-2XX responses. 响应码不是2开头*/
  class ResponseException extends IOException {
    final boolean localCacheOnly;
    final int responseCode;

    public ResponseException(String message, int networkPolicy, int responseCode) {
      super(message);
      this.localCacheOnly = NetworkPolicy.isOfflineOnly(networkPolicy);
      this.responseCode = responseCode;
    }
  }

  /** Response stream or bitmap and info. */
  class Response {
    final InputStream stream;
    final Bitmap bitmap;
    final boolean cached;
    final long contentLength;
    // Response stream and info.
    public Response(InputStream stream, boolean loadedFromCache, long contentLength) {
      if (stream == null) {
        throw new IllegalArgumentException("Stream may not be null.");
      }
      this.stream = stream;
      this.bitmap = null;
      this.cached = loadedFromCache;
      this.contentLength = contentLength;
    }
  }
}

2 OkHttpDownloader

如果有Okhttp的包,就用这个downloader进行下载
先来看下NetworkPolicy 和 MemoryPolicy

public enum NetworkPolicy {//网络缓冲管理
  /** Skips checking the disk cache and forces loading through the network. */
  NO_CACHE(1 << 0), // 调用下面的构造函数
  /**
   * Skips storing the result into the disk cache.
   * <em>Note</em>: At this time this is only supported if you are using OkHttp.
   */
  NO_STORE(1 << 1),
  /** Forces the request through the disk cache only, skipping network. */
  OFFLINE(1 << 2);

  public static boolean shouldReadFromDiskCache(int networkPolicy) {
    return (networkPolicy & NetworkPolicy.NO_CACHE.index) == 0;//注意使用了类下的index变量
  }
  public static boolean shouldWriteToDiskCache(int networkPolicy) {
    return (networkPolicy & NetworkPolicy.NO_STORE.index) == 0;
  }
  public static boolean isOfflineOnly(int networkPolicy) {
    return (networkPolicy & NetworkPolicy.OFFLINE.index) != 0;
  }
  final int index;
  private NetworkPolicy(int index) {
    this.index = index;
  }
}
public enum MemoryPolicy { // 内存缓冲管理
  /** Skips memory cache lookup when processing a request. */
  NO_CACHE(1 << 0),
  /**
   * Skips storing the final result into memory cache. Useful for one-off requests
   * to avoid evicting other bitmaps from the cache.
   */
  NO_STORE(1 << 1);

  static boolean shouldReadFromMemoryCache(int memoryPolicy) {
    return (memoryPolicy & MemoryPolicy.NO_CACHE.index) == 0;
  }
  static boolean shouldWriteToMemoryCache(int memoryPolicy) {
    return (memoryPolicy & MemoryPolicy.NO_STORE.index) == 0;
  }

  final int index;
  private MemoryPolicy(int index) {
    this.index = index;
  }
}
/** A Downloader which uses OkHttp to download images. */
public class OkHttpDownloader implements Downloader {
  private static OkHttpClient defaultOkHttpClient() {
    OkHttpClient client = new OkHttpClient();
    client.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);//15000
    client.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);//20000
    client.setWriteTimeout(Utils.DEFAULT_WRITE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);//20000
    return client;
  }

  private final OkHttpClient client;  

  /**
   * Create new downloader that uses OkHttp. This will install an image cache into the specified
   * directory.
   * @param cacheDir The directory in which the cache should be stored
   * @param maxSize The size limit for the cache.
   */
  public OkHttpDownloader(final File cacheDir, final long maxSize) {
    this(defaultOkHttpClient()); // ---
    try {
      client.setCache(new com.squareup.okhttp.Cache(cacheDir, maxSize));
    } catch (Exception ignored) {
    }
  }

  public DDOkHttpDownloader(OkHttpClient client) {//---
    this.client = client;
  }
  protected final OkHttpClient getClient() {
    return client;
  }

  @Override public Response load(Uri uri, int networkPolicy) throws IOException {
    CacheControl cacheControl = null;//com.squareup.okhttp
    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()-->对应的是com.squareup.okhttp.Request
    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());
  }

  @Override public void shutdown() { //closing the disk cache
    com.squareup.okhttp.Cache cache = client.getCache();
    if (cache != null) {
      try {        cache.close();      } catch (IOException ignored) {      }
    }
  }
}

3 UrlConnectionDownloader

// cache of 2% of the total available space will be used (capped at 50MB) will automatically be
// installed in the application's cache directory, when available. 
public class UrlConnectionDownloader implements Downloader {
  static final String RESPONSE_SOURCE = "X-Android-Response-Source";
  static volatile Object cache;

  private static final Object lock = new Object();
  private static final String FORCE_CACHE = "only-if-cached,max-age=2147483647";//2^31-1
  private static final ThreadLocal<StringBuilder> CACHE_HEADER_BUILDER =
      new ThreadLocal<StringBuilder>() { // ThreadLocal的使用方法!!!
        @Override protected StringBuilder initialValue() {
          return new StringBuilder();
        }
      };
  private final Context context; // final
  public UrlConnectionDownloader(Context context) {
    this.context = context.getApplicationContext(); // 获取App的
  }
  protected HttpURLConnection openConnection(Uri path) throws IOException {
    HttpURLConnection connection = (HttpURLConnection) new URL(path.toString()).openConnection();
    connection.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS);
    connection.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS);
    return connection;
  }
  @Override public Response load(Uri uri, int networkPolicy) throws IOException {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {//14
      installCacheIfNeeded(context); //缓存
    }
    HttpURLConnection connection = openConnection(uri);
    connection.setUseCaches(true);
    if (networkPolicy != 0) {
      String headerValue;
      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        headerValue = FORCE_CACHE; // "only-if-cached,max-age=2147483647"
      } else {
        StringBuilder builder = CACHE_HEADER_BUILDER.get();//ThreadLocal
        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);
    // Returns true if header indicates the response body was loaded from the disk cache.
 boolean fromCache = Utils.parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));
    //RESPONSE_SOURCE = "X-Android-Response-Source"
    return new Response(connection.getInputStream(), fromCache, contentLength);
  }
  @Override public void shutdown() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && cache != null) {
      ResponseCacheIcs.close(cache);
    }
  }

  private static void installCacheIfNeeded(Context context) {
    // DCL + volatile should be safe after Java 5.---double checked locking
    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) {      }    }  }
}

2 Cache

1 Cache接口

A memory cache

/*A memory cache for storing the most recently used images.
Note: The Cache is accessed by multiple threads. You must ensure your Cache implementation is thread safe when get(String) or set(String, Bitmap) is called.*/
public interface Cache {
  Bitmap get(String key);  
  void set(String key, Bitmap bitmap);
  /** Returns the current size of the cache in bytes. */
  int size();
  /** Returns the maximum size in bytes that the cache can hold. */
  int maxSize();
  void clear();
  /** Remove items whose key is prefixed with {@code keyPrefix}. */
  void clearKeyUri(String keyPrefix);
  /** A cache which does not store any values. */
  ICache NONE = new ICache() {  ...  };
}

2 LruCache

A memory cache which uses a least-recently used eviction收回 policy

public class LruCache implements Cache {
  final LinkedHashMap<String, Bitmap> map;
  private final int maxSize;

  private int size;
  private int putCount;
  private int evictionCount;
  private int hitCount;
  private int missCount;

  /** Create a cache using an appropriate portion of the available RAM as the maximum size. */
  public LruCache(Context context) {
    this(Utils.calculateMemoryCacheSize(context));
  }

  /** Create a cache with a given maximum size in bytes. */
  public LruCache(int maxSize) {
    if (maxSize <= 0) {
      throw new IllegalArgumentException("Max size must be positive.");
    }
    this.maxSize = maxSize;
    /*  initialCapacity--the initial capacity of this hash map.
        loadFactor--the initial load factor.
        accessOrder--true if the ordering should be done based on the last access (from least-recently accessed to most-recently accessed从最远用到最近用), and false if the ordering should be the order in which the entries were inserted根据插入.   */
    this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
  }

  @Override public Bitmap get(String key) {//父类说了 thread safe
    if (key == null) {      throw new NullPointerException("key == null");    }
    Bitmap mapValue;
    synchronized (this) {
      mapValue = map.get(key);
      if (mapValue != null) {
        hitCount++;
        return mapValue;
      }
      missCount++;
    }
    return null;
  }

  @Override public void set(String key, Bitmap bitmap) {
    if (key == null || bitmap == null) {
          throw new NullPointerException("key == null || bitmap == null");
    }
    Bitmap previous;
    synchronized (this) {//父类说了 thread safe
      putCount++;
      size += Utils.getBitmapBytes(bitmap);
      previous = map.put(key, bitmap);
      if (previous != null) {
        size -= Utils.getBitmapBytes(previous);
      }
    }
    trimToSize(maxSize);
  }

  private void trimToSize(int maxSize) {
    while (true) {
      String key;
      Bitmap value;
      synchronized (this) {//thread safe
        if (size < 0 || (map.isEmpty() && size != 0)) {
          throw new IllegalStateException(
              getClass().getName() + ".sizeOf() is reporting inconsistent results!");
        }
        if (size <= maxSize || map.isEmpty()) {
          break;
        }//现在的排序是from least-recently accessed to most-recently accessed因此先删除最近没有用的
        Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
        key = toEvict.getKey();
        value = toEvict.getValue();
        map.remove(key);
        size -= Utils.getBitmapBytes(value);
        evictionCount++;
      }
    }
  }

  /** Clear the cache. */// -1 will evict 0-sized elements
  public final void evictAll() {    trimToSize(-1);   }

  @Override public final synchronized int size() {    return size;  }

  @Override public final synchronized int maxSize() {    return maxSize;  }

  @Override public final synchronized void clear() {    evictAll();  }

  @Override public final synchronized void clearKeyUri(String uri) {
    boolean sizeChanged = false;
    int uriLength = uri.length();
    for (Iterator<Map.Entry<String, Bitmap>> i = map.entrySet().iterator(); i.hasNext();) {
      Map.Entry<String, Bitmap> entry = i.next();
      String key = entry.getKey();
      Bitmap value = entry.getValue();
      int newlineIndex = key.indexOf(KEY_SEPARATOR);// char KEY_SEPARATOR = '\n';
      if (newlineIndex == uriLength && key.substring(0, newlineIndex).equals(uri)) {
        i.remove();
        size -= Utils.getBitmapBytes(value);
        sizeChanged = true;
      }
    }
    if (sizeChanged) {
      trimToSize(maxSize);
    }
  } 
}

3 Stats和StatsSnapshot

1 Stats

class Stats {
  private static final int CACHE_HIT = 0;
  private static final int CACHE_MISS = 1;
  private static final int BITMAP_DECODE_FINISHED = 2; // decode
  private static final int BITMAP_TRANSFORMED_FINISHED = 3; // TRANSFORMED
  private static final int DOWNLOAD_FINISHED = 4;
  private static final String STATS_THREAD_NAME = Utils.THREAD_PREFIX + "Stats";
  final HandlerThread statsThread;
  final ICache cache;
  final Handler handler;

  long cacheHits;
  long cacheMisses;
  long totalDownloadSize;
  long totalOriginalBitmapSize;
  long totalTransformedBitmapSize;
  long averageDownloadSize;
  long averageOriginalBitmapSize;
  long averageTransformedBitmapSize;
  int downloadCount;
  int originalBitmapCount;
  int transformedBitmapCount;

  Stats(ICache cache) {
    this.cache = cache;
    this.statsThread = new HandlerThread(STATS_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
    this.statsThread.start();//下面语句功能:每秒发送一个消息让其一直在处理工作中,不要一直停留
   Utils.flushStackLocalLeaks(statsThread.getLooper());
    this.handler = new StatsHandler(statsThread.getLooper(), this);
  }

  void dispatchDownloadFinished(long size) {
    handler.sendMessage(handler.obtainMessage(DOWNLOAD_FINISHED, size));
  }
  void shutdown() {
    statsThread.quit();
  }

  void performBitmapDecoded(long size) {
    originalBitmapCount++;
    totalOriginalBitmapSize += size;
    averageOriginalBitmapSize = getAverage(originalBitmapCount, totalOriginalBitmapSize);
  }

  StatsSnapshot createSnapshot() {
    return new StatsSnapshot(cache.maxSize(), cache.size(), cacheHits, cacheMisses,
 totalDownloadSize, totalOriginalBitmapSize, totalTransformedBitmapSize, averageDownloadSize,
 averageOriginalBitmapSize, averageTransformedBitmapSize, downloadCount, originalBitmapCount,
 transformedBitmapCount, System.currentTimeMillis());
  }

  private static long getAverage(int count, long totalSize) {    return totalSize / count;  }

  private static class StatsHandler extends Handler {
    private final Stats stats;
    public StatsHandler(Looper looper, Stats stats) {
      super(looper);
      this.stats = stats;
    }
    @Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case CACHE_HIT:
          stats.performCacheHit(); //  cacheHits++;
          break;
        case CACHE_MISS:
          stats.performCacheMiss(); //cacheMisses++;
          break;
        case BITMAP_DECODE_FINISHED:
          stats.performBitmapDecoded(msg.arg1);
          break;
        case BITMAP_TRANSFORMED_FINISHED:
          stats.performBitmapTransformed(msg.arg1);
          break;
        case DOWNLOAD_FINISHED:
          stats.performDownloadFinished((Long) msg.obj);
          break;
        default:
          Picasso.HANDLER.post(new Runnable() {
            @Override public void run() {
              throw new AssertionError("Unhandled stats message." + msg.what);
            }
          });
      }
    }
  }
}

2 StatsSnapshot

/** Represents all stats for a {@link Picasso} instance at a single point in time. */

  /** Prints out this {@link StatsSnapshot} into log. */
  public void dump() {
    StringWriter logWriter = new StringWriter();
    dump(new PrintWriter(logWriter));
    Log.i(TAG, logWriter.toString());
  }
  public void dump(PrintWriter writer) {
    writer.println("===============BEGIN PICASSO STATS ===============");
    writer.println("Memory Cache Stats");
    writer.print("  Max Cache Size: ");
    writer.println(maxSize);
    ....}

4 Utils

final class Utils {  
  static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 15 * 1000; // 15s
  private static final int KEY_PADDING = 50; // Determined by exact science.
  private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB
  private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB
  static final int THREAD_LEAK_CLEANING_MS = 1000;
  static final char KEY_SEPARATOR = '\n';

  /** Thread confined to main thread for key creation. */
  static final StringBuilder MAIN_THREAD_KEY_BUILDER = new StringBuilder();

  /** Logging */
  static final String OWNER_MAIN = "Main";
  static final String OWNER_DISPATCHER = "Dispatcher";
  static final String OWNER_HUNTER = "Hunter";
  static final String VERB_CREATED = "created";
  static final String VERB_CHANGED = "changed";
  static final String VERB_IGNORED = "ignored";
  static final String VERB_ENQUEUED = "enqueued";// 入队,排队
  static final String VERB_CANCELED = "canceled";
  static final String VERB_BATCHED = "batched";//批处理
  static final String VERB_RETRYING = "retrying";
  static final String VERB_EXECUTING = "executing";//执行
  static final String VERB_DECODED = "decoded";
  static final String VERB_TRANSFORMED = "transformed";
  static final String VERB_JOINED = "joined";
  static final String VERB_REMOVED = "removed";
  static final String VERB_DELIVERED = "delivered";//交付; 交出
  static final String VERB_REPLAYING = "replaying";//重演
  static final String VERB_COMPLETED = "completed";
  static final String VERB_ERRORED = "errored";
  static final String VERB_PAUSED = "paused";
  static final String VERB_RESUMED = "resumed";

  // WebP格式,谷歌(google)开发的一种旨在加快图片加载速度的图片格式。
  // 图片压缩体积大约只有JPEG的2/3,并能节省大量的服务器带宽资源和数据空间。
  // Facebook Ebay等知名网站已经开始测试并使用WebP格式。

  /* WebP file header
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |      'R'      |      'I'      |      'F'      |      'F'      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                           File Size                           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |      'W'      |      'E'      |      'B'      |      'P'      |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  */
  private static final int WEBP_FILE_HEADER_SIZE = 12;
  private static final String WEBP_FILE_HEADER_RIFF = "RIFF";
  private static final String WEBP_FILE_HEADER_WEBP = "WEBP";

  private Utils() {// 暗示
    // No instances.
  }

  static int getBitmapBytes(Bitmap bitmap) {
    int result;
    if (SDK_INT >= HONEYCOMB_MR1) {//12
      result = BitmapHoneycombMR1.getByteCount(bitmap);
    } else {
      result = bitmap.getRowBytes() * bitmap.getHeight();
    }
    if (result < 0) {      throw new IllegalStateException("Negative size: " + bitmap);    }
    return result;
  }
  private static class BitmapHoneycombMR1 {
    static int getByteCount(Bitmap bitmap) {
      return bitmap.getByteCount();    }
  }

  static <T> T checkNotNull(T value, String message) {
    if (value == null) {      throw new NullPointerException(message);    }
    return value;
  }
  static void checkNotMain() {    if (isMain()) {
      throw new IllegalStateException("Method call should not happen from the main thread.");   }
  }
  static boolean isMain() {    
      return Looper.getMainLooper().getThread() == Thread.currentThread();
  }

  static String getLogIdsForHunter(BitmapHunter hunter, String prefix) {
    StringBuilder builder = new StringBuilder(prefix);
    Action action = hunter.getAction();
    if (action != null) {
      builder.append(action.request.logId());
    }
    List<Action> actions = hunter.getActions();
    if (actions != null) {
      for (int i = 0, count = actions.size(); i < count; i++) {
        if (i > 0 || action != null) builder.append(", ");
        builder.append(actions.get(i).request.logId());
      }
    }
    return builder.toString();
  }

  static void log(String owner, String verb, String logId, String extras) {
    Log.d(TAG, format("%1$-11s %2$-12s %3$s %4$s", owner, verb, logId, extras));
  }

  static String createKey(Request data) {
    String result = createKey(data, MAIN_THREAD_KEY_BUILDER);
    MAIN_THREAD_KEY_BUILDER.setLength(0); //setLength
    return result;
  }

  static String createKey(Request data, StringBuilder builder) {
    if (data.stableKey != null) {//确保空间足够,不够就增加increased to the largest value of either 
      // the minimumCapacity or the current capacity multiplied by two plus two
      builder.ensureCapacity(data.stableKey.length() + KEY_PADDING); //KEY_PADDING = 50
      builder.append(data.stableKey);
    } else if (data.uri != null) {
      String path = data.uri.toString();
      builder.ensureCapacity(path.length() + KEY_PADDING);
      builder.append(path);
    } else {
      builder.ensureCapacity(KEY_PADDING);
      builder.append(data.resourceId);
    }
    builder.append(KEY_SEPARATOR); //KEY_SEPARATOR = '\n'
    if (data.rotationDegrees != 0) {
      builder.append("rotation:").append(data.rotationDegrees);
      if (data.hasRotationPivot) {
        builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
      }
      builder.append(KEY_SEPARATOR);
    }
    if (data.hasSize()) {
      builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
      builder.append(KEY_SEPARATOR);
    }
    if (data.centerCrop) {
      builder.append("centerCrop").append(KEY_SEPARATOR);
    } else if (data.centerInside) {
      builder.append("centerInside").append(KEY_SEPARATOR);
    }

    if (data.transformations != null) {
      /*noinspection ForLoopReplaceableByForEach:noinspection is an IntelliJ specific annotation. 
      It's similar to Java's @SupressWarnings except that it can be used for a single statement 
      instead of declaring it at class or method level as @SupressWarnings. In this case, it is 
      It means that you're using a counter to run through the list, when you could just do:
      for (Object obj : list)*/
      for (int i = 0, count = data.transformations.size(); i < count; i++) {
        builder.append(data.transformations.get(i).key());
        builder.append(KEY_SEPARATOR);
      }
    }
    return builder.toString();
  }
  // good 常见了已经
  static void closeQuietly(InputStream is) {
    if (is == null) return;
    try {
      is.close();
    } catch (IOException ignored) {
    }
  }

  /*Returns {@code true} if header indicates the response body was loaded from the disk cache. */
  static boolean parseResponseSourceHeader(String header) {
    /*摘自 http://zhidao.baidu.com/link?url=gzpcDl3s2gVRWccT9FYa35QHfgQEtwiPHaSekTufJSfwnbRo8kCxLIvJdNCs00cFi16G-3Fb4dWqbD4JMaoZ6txTg_Jj4SiDk2UA3ifgZrW
    当浏览器第一次加载资源的时候,返回一般为200,意思是成功获取资源,并会在浏览器的缓存中记录下max-age,第二次访问的时
果没有过期,则直接读缓存,根本不会和服务器进行交互,换句话说,断网都能打开,就和本地跑一样!如果已经过期了,那就去服务器
请求,等待服务器响应,这是很费时间的,服务器如果发现资源没有改变过,那么就会返回304,告诉浏览器,我没变过,你去读缓存
吧,于是浏览器也不用从服务器拉数据了,然而,等待服务器响应也是一个很要命的问题,在网速发达的今天,等一个响应,有时比下载
还慢。
如果是用浏览器刷新的,那么浏览器不会去判断max-age了,直接去服务器拿,如果服务器判断资源没变过,则还是会返回304,和上面
是一样的,所以刷新一下,其实很可怕,等于把所有的资源都要去服务器请求一边,问问服务器我过期了没有。
综上,尽量减少网页的资源数量!尽量合并JS CSS 图片!响应速度将会猛增!
当今,响应速度比网速重要!!*/
    if (header == null) {      return false;    }
    String[] parts = header.split(" ", 2);
    if ("CACHE".equals(parts[0])) {
      return true;
    }
    if (parts.length == 1) {
      return false;
    }
    try {
      return "CONDITIONAL_CACHE".equals(parts[0]) && Integer.parseInt(parts[1]) == 304;
    } catch (NumberFormatException e) {
      return false;
    }
  }

  static Downloader createDefaultDownloader(Context context) {
    if (SDK_INT >= GINGERBREAD) { // 9
        try { // 没有异常说明用户添加了Okhttp的包
          Class.forName("com.squareup.okhttp.OkHttpClient");
          return OkHttpLoaderCreator.create(context);
        } catch (ClassNotFoundException ignored) {
        }
    }
    return new DDUrlConnectionDownloader(context);
  }
  private static class OkHttpLoaderCreator {
    static Downloader create(Context context) {
      return new OkHttpDownloader(context);
    }
  }

  static File createDefaultCacheDir(Context context) {
    File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);
    if (!cache.exists()) {
      //noinspection ResultOfMethodCallIgnored
      cache.mkdirs();
    }
    return cache;
  }

  static long calculateDiskCacheSize(File dir) {
    long size = MIN_DISK_CACHE_SIZE;
    try { // 计算SD卡缓存的大小,需要在5M和50M之间
      StatFs statFs = new StatFs(dir.getAbsolutePath());
      long available = ((long) statFs.getBlockCount()) * statFs.getBlockSize();
      // Target 2% of the total space.占用总空间的2%
      size = available / 50;
    } catch (IllegalArgumentException ignored) {
    }
    // Bound inside min/max size for disk cache.
    return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);
  }

  static int calculateMemoryCacheSize(Context context) {
    ActivityManager am = getService(context, ACTIVITY_SERVICE);//获取服务,泛型时不用转换了
    // true when the application has requested a large heap for its processes
    boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
    int memoryClass = am.getMemoryClass(); // megabytes单位是MB
    if (largeHeap && SDK_INT >= HONEYCOMB) { // 11
      memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
    }
    // Target ~15% of the available heap.-----------
    return 1024 * 1024 * memoryClass / 7;
  }
  private static class ActivityManagerHoneycomb {
    static int getLargeMemoryClass(ActivityManager activityManager) {
      return activityManager.getLargeMemoryClass();
    }
  }

  static boolean isAirplaneModeOn(Context context) {//飞行模式
    ContentResolver contentResolver = context.getContentResolver();
    try {
      return Settings.System.getInt(contentResolver, AIRPLANE_MODE_ON, 0) != 0;
    } catch (NullPointerException e) {
      // https://github.com/square/picasso/issues/761, some devices might crash here, assume that
      // airplane mode is off.
      return false;
    }
  }

  @SuppressWarnings("unchecked") //获取一个服务,利用泛型
  static <T> T getService(Context context, String service) {
    return (T) context.getSystemService(service);
  }

  static boolean hasPermission(Context context, String permission) {//是否有权限
    return context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
  }
  // InputStream----byte[]
  static byte[] toByteArray(InputStream input) throws IOException {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024 * 4];
    int n;
    while (-1 != (n = input.read(buffer))) {
      byteArrayOutputStream.write(buffer, 0, n);
    }
    return byteArrayOutputStream.toByteArray();
  }

  static boolean isWebPFile(InputStream stream) throws IOException {
    byte[] fileHeaderBytes = new byte[WEBP_FILE_HEADER_SIZE];//12
    boolean isWebPFile = false;
    if (stream.read(fileHeaderBytes, 0, WEBP_FILE_HEADER_SIZE) == WEBP_FILE_HEADER_SIZE) {
      // If a file's header starts with RIFF and end with WEBP, the file is a WebP file
      isWebPFile = WEBP_FILE_HEADER_RIFF.equals(new String(fileHeaderBytes, 0, 4, "US-ASCII"))
          && WEBP_FILE_HEADER_WEBP.equals(new String(fileHeaderBytes, 8, 4, "US-ASCII"));
    }
    return isWebPFile;
  }

  static int getResourceId(Resources resources, Request data) throws FileNotFoundException {
    if (data.resourceId != 0 || data.uri == null) {
      return data.resourceId;
    }
    String pkg = data.uri.getAuthority();
    if (pkg == null) throw new FileNotFoundException("No package provided: " + data.uri);
    int id;
    List<String> segments = data.uri.getPathSegments();
    if (segments == null || segments.isEmpty()) {
      throw new FileNotFoundException("No path segments: " + data.uri);
    } else if (segments.size() == 1) {
      try {
        id = Integer.parseInt(segments.get(0));
      } catch (NumberFormatException e) {
        throw new FileNotFoundException("Last path segment is not a resource ID: " + data.uri);
      }
    } else if (segments.size() == 2) {
      String type = segments.get(0);
      String name = segments.get(1);
      /**Return a resource identifier for the given resource name. A fully qualified resource name is of the form "package:type/entry". The first two components (package and type) are optional if defType and defPackage, respectively, are specified here.*/
      id = resources.getIdentifier(name, type, pkg);
    } else {
      throw new FileNotFoundException("More than two path segments: " + data.uri);
    }
    return id;
  }

  static Resources getResources(Context context, Request data) throws FileNotFoundException {
    if (data.resourceId != 0 || data.uri == null) {
      return context.getResources();
    }
    String pkg = data.uri.getAuthority();
    if (pkg == null) throw new FileNotFoundException("No package provided: " + data.uri);
    try {
      PackageManager pm = context.getPackageManager();
      return pm.getResourcesForApplication(pkg);//---------------
    } catch (PackageManager.NameNotFoundException e) {
      throw new FileNotFoundException("Unable to obtain resources for package: " + data.uri);
    }
  }

  /**Prior to 在…之前;
   * Prior to Android 5, HandlerThread always keeps a stack local reference to the last message
   * that was sent to it. This method makes sure that stack local reference never stays there
   * for too long by sending new messages to it every second.
   */
  static void flushStackLocalLeaks(Looper looper) {
    Handler handler = new Handler(looper) {
      @Override public void handleMessage(Message msg) {
        sendMessageDelayed(obtainMessage(), THREAD_LEAK_CLEANING_MS); //1000
      }
    };
    handler.sendMessageDelayed(handler.obtainMessage(), THREAD_LEAK_CLEANING_MS);
  }
  // 供线程池PicassoExecutorService使用
  static class PicassoThreadFactory implements ThreadFactory {
    @SuppressWarnings("NullableProblems")
    public Thread newThread(Runnable r) {
      return new PicassoThread(r);
    }
  }

  private static class PicassoThread extends Thread {
    public PicassoThread(Runnable r) {
      super(r);
    }
    @Override public void run() {
      Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
      super.run();
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值