Picasso分析02
2015-10-02 19:41
1256 查看
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(); } } }
相关文章推荐
- Picasso源码解析
- 从Zero分析Picasso源码
- android开源项目:图片下载缓存库picasso
- Picasso分析01
- android-------非常好的图片加载框架和缓存库(Picasso)
- 关于picasso中的OkHttpDownloader类中引用okhttp,在okhttp3中的错误
- Glide 对比 Picasso
- 【FastDev4Android框架开发】BaseAdapterHelper的基本使用介绍,让你摆脱狂写一堆Adapter烦恼(二十四)
- 使用Picasso 来 实现 圆形头像
- Android图片加载框架(universal-image-loader,Picasso,Glide,Fresco)的用法
- Android Picasso 图片缓存库
- Picasso的基本用法及如何加载网络图片和本地图片
- Android图片加载库—Picasso一个强大的图像下载和缓存库
- 剖析Picasso加载压缩本地图片流程(解决Android 5.0部分机型无法加载本地图片的问题)
- 剖析Picasso中的内存缓存机制——LruCache
- 解决Picasso在Android 5.0以下版本不兼容https导致图片不显示
- 交叉编译onecoolx-picasso二维矢量图形库
- Picasso 2.5.2的fit、resize在android5.1无法加载图片的解决方案
- Picasso的基本使用