图片加载框架Picasso源码解析
来源:互联网 发布:数据库物理设计怎么写 编辑:程序博客网 时间:2024/06/07 03:46
转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/76645535
本文出自:【顾林海的博客】
前言
picasso是Square公司开源的一个Android图形缓存库,地址http://square.github.io/picasso/,可以实现图片下载和缓存功能。仅仅只需要一行代码就能完全实现图片的异步加载,然而这里我并不讨论它的使用,只是围绕这行件简简单单的代码,对它的源码进行梳理,阅读picasso源码时,并不会非常深入,只是对它的整体流程有个清晰的认识,picasso在很早以前就使用过了,但没有对它过多的了解,趁现在有时间就深入了解下,也不枉使用过它。
本篇源码解析是基于Picasso 2.5.2版本。
源码解析
说到阅读源码,但源码这么多,从哪里入手呢?这里我喜欢从使用的方法上入手,picasso加载图片通常只需要下面这一行代码就能实现异步加载:
Picasso.with(this).load("url").into(imageView);
好了,入口找到了,我们就可以一步步的了解它的实现原理,先从Picasso的静态方法with说起,源码如下:
static volatile Picasso singleton = null;public static Picasso with(Context context) { if (singleton == null) { synchronized (Picasso.class) { if (singleton == null) { singleton = new Builder(context).build();(1) } } } return singleton;}
上面with方法的作用是获取Picasso实例,在获取Picasso实例时,使用到了两种设计模式,一是单例模式,二是建造者模式。使用单例模式时内存中只有一个实例从而达到减少内存的开支,降低系统的性能开销,而实现单例模式的手段有很多,上面使用了基于volatile的双重检查锁定的方案,这里的singleton声明为volatile型,为什么需要volatile呢?如果不使用volatile修饰singleton,在多线程环境下使用是危险的,在(1)的代码可以被分成三部分,这里面就涉及到重排序的问题,大家有空的话可以自信查看,这里只是讲到了单例,随便扯扯。Picasso实例的创建是通过Builder模式实现的,Builder模式使数据的构建和展示进行分离,在Builder类中的build方法,主要是对Picasso的一些参数进行配置。
Picasso.Builder:public Picasso build() { Context context = this.context; /** * 1、创建下载方式 */ if (downloader == null) { downloader = Utils.createDefaultDownloader(context); } /** * 2、构建内存缓存策略(LruCache) */ if (cache == null) { cache = new LruCache(context); } /** * 3、构建线程池 */ if (service == null) { service = new PicassoExecutorService(); } /** * 4、请求转换器,用于请求前更改请求信息 */ if (transformer == null) { transformer = Picasso.RequestTransformer.IDENTITY; } /** * 5、快照,统计某一时间Picasso中的各项数据 */ Stats stats = new Stats(cache); /** * 6、分发器 */ Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); /** * 7、创建Picasso实例 */ return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, loggingEnabled);}
build方法主要是参数的配置,从上面可以看出配置的参数有哪些,这里稍微了解下这些参数的作用。
网络加载方式
private Downloader downloader;if (downloader == null) { downloader = Utils.createDefaultDownloader(context);}
downloader是Picasso的图片网络加载方式,DownLoader是一个接口。
可以看出DownLoader接口定义了两个方法,分别是load和shutdown方法,从方法名也可以看出load方法是用于加载图片的,shutdown方法是对磁盘和其他资源进行清理,DownLoader接口的实现类有两个,分别是UrlConnectionDownloader和OkHttpDownloader,这里可以从Utils的静态方法createDefaultDownloader(context)方法看出。
Utils.java:static Downloader createDefaultDownloader(Context context) { try { Class.forName("com.squareup.okhttp.OkHttpClient"); return OkHttpLoaderCreator.create(context); } catch (ClassNotFoundException ignored) { } return new UrlConnectionDownloader(context);}private static class OkHttpLoaderCreator { static Downloader create(Context context) { return new OkHttpDownloader(context); }}
也就是说Picasso内部会通过反射,看看有没有集成OkHttp,如果集成了就使用OkHttpDownloader,反之使用UrlConnectionDownloader。Picasso的磁盘缓存的实现就是在OkHttpDownloader和UrlConnectionDownloader中实现的。
OkHttpDownloader.java:public OkHttpDownloader(final Context context) { this(Utils.createDefaultCacheDir(context));}public OkHttpDownloader(final File cacheDir) { this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));}public OkHttpDownloader(final File cacheDir, final long maxSize) { this(defaultOkHttpClient()); try { client.setCache(new com.squareup.okhttp.Cache(cacheDir, maxSize)); } catch (IOException ignored) { }}
在创建OkHttpDownloader对象时,会执行Utils的静态方法createDefaultCacheDir获取缓存的目录以及calculateDiskCacheSize方法获取缓存大小,最后通过OkHttpClient的setCache方法设置缓存。
再看看UrlConnectionDownloader内的磁盘缓存实现方式。
UrlConnectionDownloader.java:static volatile Object cache;@Override public Downloader.Response load(Uri uri, int networkPolicy) throws IOException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { installCacheIfNeeded(context); } //省略XX行代码 ....}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) { } }}
上面在load方法中会先判断当前Android的版本,HttpURLConnection在Android 4.0之后增加了一个Response Cache,因此这里会做版本的判断是否开启缓存。
内存缓存策略
private Cache cache;if (cache == null) { cache = new LruCache(context);}
Picasso的内存缓存策略用的是LruCache,只不过Picasso对其进行改造。
小知识:
LruCache中Lru算法的实现就是通过LinkedHashMap来实现的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,这种顺序有两种,一种是LRU顺序,一种是插入顺序,这可以由其构造函数public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定。所以,对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。
public LruCache(Context context) { this(Utils.calculateMemoryCacheSize(context));}public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("Max size must be positive."); } this.maxSize = maxSize; this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);}
这里面通过Utils的类方法calculateMemoryCacheSize(context)获取可用的最大缓存数。
static int calculateMemoryCacheSize(Context context) { ActivityManager am = getService(context, ACTIVITY_SERVICE); boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;//(1) int memoryClass = am.getMemoryClass();//(2) if (largeHeap && SDK_INT >= HONEYCOMB) { memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);//(3) } // Target ~15% of the available heap. return 1024 * 1024 * memoryClass / 7;}
在(1)处会去获取largeHeap是否开启,largeHeap开启是通过在manifest中设置largeHeap=true设置的,设置为true后应用可以使用最大内存值, Android官方给的建议是,作为程序员的我们应该努力减少内存的使用,想回收和复用的方法,而不是想方设法增大内存。当内存很大的时候,每次gc的时间也会长一些,性能会下降。
如果largeHeap没有开启或是当前设备的版本小于3.1使用的是(2)处的am.getMemoryClass()获取应用正常情况下的内存大小;反之执行(3)处,这里调用Utils的静态内部类ActivityManagerHoneycomb的静态方法getLargeMemoryClass(am),在该方法中通过ActivityManger的getLargeMemory方法获取 开启largeHeap最大的内存大小。最后会取它的百分之15的大小。
@Override public void set(String key, Bitmap bitmap) { if (key == null || bitmap == null) { throw new NullPointerException("key == null || bitmap == null"); } Bitmap previous; synchronized (this) { putCount++; size += Utils.getBitmapBytes(bitmap); previous = map.put(key, bitmap); if (previous != null) { size -= Utils.getBitmapBytes(previous); } } trimToSize(maxSize);}
set方法的作用是进行Bitmap的缓存,通过LinkedHashMap存储,每次存储完毕后会调用trimToSize方法,该方法内部会开启一个无限循环,用于判断当前缓存大小是否大于最大缓存数,如果大于就执行近期最少用到的进行清楚,具体实现如下:
private void trimToSize(int maxSize) { while (true) { String key; Bitmap value; synchronized (this) { if (size < 0 || (map.isEmpty() && size != 0)) { throw new IllegalStateException( getClass().getName() + ".sizeOf() is reporting inconsistent results!"); } if (size <= maxSize || map.isEmpty()) { break; } Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= Utils.getBitmapBytes(value); evictionCount++; } }}
其中evictionCount是回收次数,我们可以看到,如果当前缓存size如果大于最大缓存数maxSize时,对LinkedHashMap迭代,执行清除操作。
@Override public Bitmap get(String key) { 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;}
get(key)方法用于缓存的获取,其中hitCount是命中次数,根据相应的key从LinkedHashMap中获取对应的Bitmap,如果Bitmap不为空,说明缓存命中,直接返回该Bitmap,反之Bitmap为空时,说明缓存失效,missCount自增。
public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements}
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); if (newlineIndex == uriLength && key.substring(0, newlineIndex).equals(uri)) { i.remove(); size -= Utils.getBitmapBytes(value); sizeChanged = true; } } if (sizeChanged) { trimToSize(maxSize); }}
clearKeyUri(uri)方法通过for循环遍历LinkedHashMap,查找是否包含该uri,如果存在该Uri,就执行清除对应的缓存操作。
线程池
private ExecutorService service;if (service == null) { service = new PicassoExecutorService();}
我们知道在UI线程不能进行耗时操作,尤其是网络操作,因此 ,访问网络的操作需要在子线程中进行,但开启 过多的线程会降低系统的性能,提高内存的开销,所以我们需要线程池来管理这些线程,在Picasso中它自己定义了线程池的类PicassoExecutorService,PicassoExecutorService继承自ThreadPoolExecutor。
PicassoExecutorService.java:class PicassoExecutorService extends ThreadPoolExecutor { private static final int DEFAULT_THREAD_COUNT = 3; PicassoExecutorService() { super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory()); } void adjustThreadCount(NetworkInfo info) { if (info == null || !info.isConnectedOrConnecting()) { setThreadCount(DEFAULT_THREAD_COUNT); return; } switch (info.getType()) { case ConnectivityManager.TYPE_WIFI: case ConnectivityManager.TYPE_WIMAX: case ConnectivityManager.TYPE_ETHERNET: setThreadCount(4); break; case ConnectivityManager.TYPE_MOBILE: switch (info.getSubtype()) { case TelephonyManager.NETWORK_TYPE_LTE: // 4G case TelephonyManager.NETWORK_TYPE_HSPAP: case TelephonyManager.NETWORK_TYPE_EHRPD: setThreadCount(3); break; case TelephonyManager.NETWORK_TYPE_UMTS: // 3G case TelephonyManager.NETWORK_TYPE_CDMA: case TelephonyManager.NETWORK_TYPE_EVDO_0: case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_EVDO_B: setThreadCount(2); break; case TelephonyManager.NETWORK_TYPE_GPRS: // 2G case TelephonyManager.NETWORK_TYPE_EDGE: setThreadCount(1); break; default: setThreadCount(DEFAULT_THREAD_COUNT); } break; default: setThreadCount(DEFAULT_THREAD_COUNT); } } private void setThreadCount(int threadCount) { setCorePoolSize(threadCount); setMaximumPoolSize(threadCount); } @Override public Future<?> submit(Runnable task) { PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task); execute(ftask); return ftask; } private static final class PicassoFutureTask extends FutureTask<BitmapHunter> implements Comparable<PicassoFutureTask> { private final BitmapHunter hunter; public PicassoFutureTask(BitmapHunter hunter) { super(hunter, null); this.hunter = hunter; } @Override public int compareTo(PicassoFutureTask other) { Picasso.Priority p1 = hunter.getPriority(); Picasso.Priority p2 = other.hunter.getPriority(); // High-priority requests are "lesser" so they are sorted to the front. // Equal priorities are sorted by sequence number to provide FIFO ordering. return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal()); } }}
在PicassoExecutorService线程池类中,提供了一个adjustThreadCount方法,在这个方法中通过当前的网络环境,给当前线程池分配合理的线程,这样的话将性能和网络开销尽量降到最低,我们在使用线程池时,也可以参考这种做法。
PicassoExecutorService在继承自ThreadPoolExecutor时,重写了submit方法,在这个方法中会将Runnable通过PicassoFutureTask包装,而PicassoFutureTask继承自FutureTask并实现了Comparable接口,其中实现了Comparable接口的compareTo方法对请求线程(BitmapHunter)进行优先级排序,当优先级相同时,按照先进先出来排序,否则按照线程(BitmapHunter)优先级排序。
分发器
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
Dispatcher类用于一系列事件的分发,内部开启线程,通过Handler发送消息。
final Handler handler;void dispatchSubmit(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));}void dispatchCancel(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_CANCEL, action));}void dispatchPauseTag(Object tag) { handler.sendMessage(handler.obtainMessage(TAG_PAUSE, tag));}void dispatchResumeTag(Object tag) { handler.sendMessage(handler.obtainMessage(TAG_RESUME, tag));}void dispatchComplete(BitmapHunter hunter) { handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));}void dispatchRetry(BitmapHunter hunter) { handler.sendMessageDelayed(handler.obtainMessage(HUNTER_RETRY, hunter), RETRY_DELAY);}void dispatchFailed(BitmapHunter hunter) { handler.sendMessage(handler.obtainMessage(HUNTER_DECODE_FAILED, hunter));}void dispatchNetworkStateChange(NetworkInfo info) { handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info));}void dispatchAirplaneModeChange(boolean airplaneMode) { handler.sendMessage(handler.obtainMessage(AIRPLANE_MODE_CHANGE, airplaneMode ? AIRPLANE_MODE_ON : AIRPLANE_MODE_OFF, 0));}
通过上面方法,可以看出Dispatcher的作用是对一系列事件的分发,如果请求提交操作、请求取消操作、请求暂停操作、请求恢复操作、网络状态变化(通过注册广播监听)、请求成功等等,在后面讲加载流程时会讲到这里。
实例化Picasso
build方法中的参数已经全部了解完毕,除了Stats和RequestTransformer,Stats类起着快照的所用,主要保存一些加载图片时的参数,比如缓存命中数、缓存失效数、下载总大小、下载图片解码后的总大小、下载次数等,而RequestTransformer用于在执行真正请求前的转换,主要是对请求信息的更改。最后就是实例化Picasso了。
Picasso.Builderbuild:return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
最后build方法会返回Picasso的实例,Picasso的构造器中,有这么一段代码:
int builtInHandlers = 7;int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);List<RequestHandler> allRequestHandlers = new ArrayList<RequestHandler>(builtInHandlers + extraCount);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);
上面的requestHandler是一个请求来源的集合,我们在使用Picasso时,可以加载网络图片、asset中的图片或是本地图片等等,都是靠这个请求来源的集合,在这个集合已经集成了7种来源途径,比如说最熟悉的网络加载,就是通过NetworkRequestHandler类来加载的,在创建NetworkRequestHandler对象时,会将Downloader对象传入进去,而从上面分析网络加载方式已经说了Downloader只是接口,真正实现类是OkHttpDownloader和UrlConnectionDownloader。
加载途径讲完后,我们再看看Picasso构造器另外一个重要的点:
this.cleanupThread = new Picasso.CleanupThread(referenceQueue, HANDLER);this.cleanupThread.start();
在创建Picasso实例时,会开启一个线程,这里的HANDLER是主线程的Handler。
private static class CleanupThread extends Thread { private final ReferenceQueue<Object> referenceQueue; private final Handler handler; CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) { this.referenceQueue = referenceQueue; this.handler = handler; setDaemon(true); setName(THREAD_PREFIX + "refQueue"); } @Override public void run() { Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND); while (true) { try { Action.RequestWeakReference<?> remove = (Action.RequestWeakReference<?>) referenceQueue.remove(THREAD_LEAK_CLEANING_MS); Message message = handler.obtainMessage(); if (remove != null) { message.what = REQUEST_GCED; message.obj = remove.action; handler.sendMessage(message); } else { message.recycle(); } } catch (InterruptedException e) { break; } catch (final Exception e) { handler.post(new Runnable() { @Override public void run() { throw new RuntimeException(e); } }); break; } } } void shutdown() { interrupt(); }}
CleanupThread线程是一个守护线程,在后台开启无限循环,通过Handler发送消息给主线程将一些无用的请求任务(Action)回收掉,这里通过WeakReference弱引用方便回收。这里看看它是如何回收的。通过Handler发送一个标识为REQUEST_GCED给主线程。
static final Handler HANDLER = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case HUNTER_BATCH_COMPLETE: { ... break; } case REQUEST_GCED: { Action action = (Action) msg.obj; if (action.getPicasso().loggingEnabled) { log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), "target got garbage collected"); } action.picasso.cancelExistingRequest(action.getTarget()); break; } case REQUEST_BATCH_RESUME: ... break; default: throw new AssertionError("Unknown handler message received: " + msg.what); } }};
在switch中找到REQUEST_GCED分支,调用了Picasso的cancelExistingRequest方法。
private void cancelExistingRequest(Object target) { checkMain(); Action action = targetToAction.remove(target); if (action != null) { action.cancel(); dispatcher.dispatchCancel(action); } if (target instanceof ImageView) { ImageView targetImageView = (ImageView) target; DeferredRequestCreator deferredRequestCreator = targetToDeferredRequestCreator.remove(targetImageView); if (deferredRequestCreator != null) { deferredRequestCreator.cancel(); } }}
在cancelExistingRequest方法中,从目标请求任务targetToAction集合中移除,并通过dispatcher分发器通知该任务取消,以便做一些清除操作。
加载流程
加载图片时会调用Picasso的load方法:
这里我们重点看load(String)方法:
public RequestCreator load(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));}
将String类型的URI转换成Uri对象,并调用load(Uri)方法:
public RequestCreator load(Uri uri) { return new RequestCreator(this, uri, 0);}
这里只是创建RequestCreator对象,也就说RequestCreator中的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."); } if (!data.hasImage()) {//(1) picasso.cancelRequest(target);//(2) 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); } 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 = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); picasso.enqueueAndSubmit(action);}
into方法比较长,这里一步步进行分析。
into方法:if (!data.hasImage()) { picasso.cancelRequest(target); if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } return;}
在判断语句中data是Request.Builder对象,Request类用于构建请求参数。
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);}
这里面的resourceId的值是根据Picasso的load方法来决定,调用load(int resourceId)方法时,resourceId才会有值,如果加载的是String型的uri、File以及Uri最后都会被转换成Uri。在(1)调用Request的内部类Builder的hasImage方法,此方法用于判断load方法传入的Uri或是resourceId是否为空,如果为空执行取消此次的请求,就像(2)处调用Picasso的cancelRequest方法。
public void cancelRequest(ImageView view) { cancelExistingRequest(view);}private void cancelExistingRequest(Object target) { checkMain(); Action action = targetToAction.remove(target); if (action != null) { action.cancel(); dispatcher.dispatchCancel(action); } if (target instanceof ImageView) { ImageView targetImageView = (ImageView) target; DeferredRequestCreator deferredRequestCreator = targetToDeferredRequestCreator.remove(targetImageView); if (deferredRequestCreator != null) { deferredRequestCreator.cancel(); } }}
cancelExistingRequest方法的作用是取消请求任务,通过Dispatcher分发器发送取消请求事件以便资源的清除。在上面的into方法中setPlaceholder方法起着一个占位符的作用,当我们调用placeholder方法设置占位图片时,会在请求结果前显示该占位图。
into方法: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);}
deferred默认值为false,当调用fit()方法时值为true,fit方法用于调整图像的大小。
into方法:Request request = createRequest(started);String requestKey = createKey(request);
createRequest方法用于构建请求类。
private Request createRequest(long started) { int id = nextId.getAndIncrement(); Request request = data.build(); request.id = id; request.started = started; boolean loggingEnabled = picasso.loggingEnabled; if (loggingEnabled) { log(OWNER_MAIN, VERB_CREATED, request.plainId(), request.toString()); } Request transformed = picasso.transformRequest(request); if (transformed != request) { // If the request was changed, copy over the id and timestamp from the original. transformed.id = id; transformed.started = started; if (loggingEnabled) { log(OWNER_MAIN, VERB_CHANGED, transformed.logId(), "into " + transformed); } } return transformed;}
到这里我们的Request请求类创建完毕,在构建完请求类,又调用了Picasso的transformRequest方法,而这个方法又会Picasso中的requestTransformer的transformRequest方法,达到在真正执行请求前,还可以更改请求参数,这个在上面讲起Picasso.Builder类的build方法时提到过。
Request请求类构建完毕后,通过createKey方法创建key值,这个key可以用于内存缓存LruCache中LinkedHashMap保存Bitmap的依据(key)。
into方法: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; }}
shouldReadFromMemoryCache方法用于判断是否从内存中读取,如果从内存中读取,执行Picasso的quickMemoryCacheCheck方法。
Bitmap quickMemoryCacheCheck(String key) { Bitmap cached = cache.get(key); if (cached != null) { stats.dispatchCacheHit(); } else { stats.dispatchCacheMiss(); } return cached;}
quickMemoryCacheCheck方法根据请求的key值从LruCache中取Bitmap,如果Bitmap为空,说明缓存失效,否则缓存命中。
回到上面的into方法中,如果缓存中的bitmap不为空,执行Picasso的cancelRequest方法取消当前请求,并加载该bitmap并显示,如果Callback不为空,回调主线程加载成功,最后返回结束请求。
其中Picasso的cancelRequest方法如下:
public void cancelRequest(ImageView view) { cancelExistingRequest(view);}private void cancelExistingRequest(Object target) { checkMain(); Action action = targetToAction.remove(target); if (action != null) { action.cancel(); dispatcher.dispatchCancel(action); } if (target instanceof ImageView) { ImageView targetImageView = (ImageView) target; DeferredRequestCreator deferredRequestCreator = targetToDeferredRequestCreator.remove(targetImageView); if (deferredRequestCreator != null) { deferredRequestCreator.cancel(); } }}
cancelExistingRequest方法作用取消当前的请求任务,并通过分发器发送取消请求事件以便清除资源。
into方法:if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable());}Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade);picasso.enqueueAndSubmit(action);
如果不从缓存中读取或是读取的bitmap为空,就将这次的请求任务(Action)提交。
Picasso.java: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);}
enqueueAndSubmit方法中判断同一个ImageView当请求任务不一致,从请求任务集合rargetToAction中移除,重新添加到rargetToAction,最后调用submit方法。
void submit(Action action) { dispatcher.dispatchSubmit(action);}
submit方法中通过分发器执行dispatchSubmit方法进行任务的提交。
Dispatcher.javavoid dispatchSubmit(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));}
通过Handler发送REQUEST_SUBMIT标示。
@Overridepublic void handleMessage(final Message msg) { switch (msg.what) { case REQUEST_SUBMIT: { Action action = (Action) msg.obj; dispatcher.performSubmit(action); break; } //省略XX行代码 ... default: Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new AssertionError("Unknown handler message received: " + msg.what); } }); }}
接着调用performSubmit方法。
void performSubmit(Action action) { performSubmit(action, true);}void performSubmit(Action action, boolean dismissFailed) { 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; } if (service.isShutdown()) { if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down"); } return; } 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()); }}
最终会判断暂停任务集合中是否存在该请求任务,如果存在,存入pausedActions集合中并执行返回,此次任务结束。反之会从hunterMap获取BitmapHunter实例,这个BitmapHunter继承自Runnable,图片的网络加载就是通过它来执行的,如果执行的请求任务(线程)已经存在,调整该请求任务的优先级。
如果以上条件都不满足,就创建BitmapHunter,并提交给PicassoExecutorService这个线程池执行。
这里重点看看BitmapHunter中的静态方法forRequest:
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); if (requestHandler.canHandleRequest(request)) { return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler); } } return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);}
requestHandlers就是我们之前在实例化Picasso时讲到的请求来源集合,也就是说这个方法会从请求来源集合中查找符合此次请求任务的请求类。
BitmapHunter继承自Runnable,并提交给PicassoExecutorService线程池,查看BitmapHunter的run方法:
@Override public void run() { try { updateThreadName(data); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this)); } result = hunt(); if (result == null) { dispatcher.dispatchFailed(this); } else { dispatcher.dispatchComplete(this); } } 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); }}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; } } data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy; 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;}
run方法中执行hunt方法,在hunt方法中先判断是否从缓存中读取,当不从缓存读取或是读取的bitmap为空时,就会通过请求来源类(请求来源集合中的一种)的load方法获取Bitmap。这里以NetworkRequestHandler网络请求类的load方法为例。
@Override public Result load(Request request, int networkPolicy) throws IOException { 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的load方法中调用Dowloader(OkHttpDownloader或是UrlConnectionDownloader)的load方法获取Response。最后将获取到的结果(Bitmap或是InputStream)包装成Result对象,并返回。
最后回到BitmapHunter线程中的hunt方法中。从Result中取出Bitmap,如果bitmap为空,取出InputStream,并将InputStream转换成Bitmap。拿到bitmap后将它赋值给result,再通过分发器Dispatcher发送事件。如果result不为空,说明请求成功,执行Dispatcher的dispatchComplete方法通过Handler发送消息,最后会调用Dispatcher的performComplete(hunter)。
void performComplete(BitmapHunter hunter) { if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) { cache.set(hunter.getKey(), hunter.getResult()); } hunterMap.remove(hunter.getKey()); batch(hunter); if (hunter.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion"); }}
以上代码会根据条件判断是否执行缓存内存操作,接着执行batch(hunter)方法。
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); }}
通过Handler发送消息,最后会调用performBatchComplete方法。
void performBatchComplete() { List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch); batch.clear(); mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy)); logBatch(copy);}
到这里通过主线程的Handler将我们请求到的结果发送到主线程中并处理。
@Overridepublic void handleMessage(final 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; } //省略XX行代码 ... default: Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new AssertionError("Unknown handler message received: " + msg.what); } }); }}void complete(BitmapHunter hunter) { Action single = hunter.getAction(); List<Action> joined = hunter.getActions(); boolean hasMultiple = joined != null && !joined.isEmpty(); boolean shouldDeliver = single != null || hasMultiple; if (!shouldDeliver) { return; } Uri uri = hunter.getData().uri; Exception exception = hunter.getException(); Bitmap result = hunter.getResult(); LoadedFrom from = hunter.getLoadedFrom(); if (single != null) { deliverAction(result, from, single); } if (hasMultiple) { //noinspection ForLoopReplaceableByForEach for (int i = 0, n = joined.size(); i < n; i++) { Action join = joined.get(i); deliverAction(result, from, join); } } if (listener != null && exception != null) { listener.onImageLoadFailed(this, uri, exception); }}private void deliverAction(Bitmap result, LoadedFrom from, Action action) { if (action.isCancelled()) { return; } if (!action.willReplay()) { targetToAction.remove(action.getTarget()); } if (result != null) { if (from == null) { throw new AssertionError("LoadedFrom cannot be null."); } action.complete(result, from); if (loggingEnabled) { log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from); } } else { action.error(); if (loggingEnabled) { log(OWNER_MAIN, VERB_ERRORED, action.request.logId()); } }}
从上面方法一直追踪下去会发现调用action.complete,而这个action是我们在RequestCreator中执行into方法是定义的。
Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade);
也就是说最终会调用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.setBitmap(target, context, result, from, noFade, indicatorsEnabled); if (callback != null) { callback.onSuccess(); }}
接着调用PicassoDrawable的静态方法setBitmap。
static void setBitmap(ImageView target, Context context, Bitmap bitmap, Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) { Drawable placeholder = target.getDrawable(); if (placeholder instanceof AnimationDrawable) { ((AnimationDrawable) placeholder).stop(); } PicassoDrawable drawable = new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging); target.setImageDrawable(drawable);}
PicassDrawable继承自BitmapDrawable,最终将请求到的bitmap封装成PicassDrawable,通过ImageView的setImageDrawable方法给当前的ImageView加载图片。
至此Picasso源码解析完毕。
- Picasso图片加载框架源码解析
- 图片加载框架Picasso源码解析
- 图片加载框架Picasso解析
- 图片加载框架Picasso解析
- picasso图片框架源码解析
- 图片加载框架-Picasso
- 图片加载框架Picasso
- Picasso 图片加载框架
- 图片加载框架-Picasso
- Android 图片加载框架Picasso基本使用和源码完全解析
- Android 源码解析: 图片加载库Picasso 1
- Android 源码解析: 图片加载库Picasso 2 Cache机制
- Android 源码解析: 图片加载库Picasso 3 核心类
- 图片加载利器之Picasso(四)源码解析
- 图片加载框架之Picasso
- Android图片加载框架Picasso
- 图片加载框架之Picasso
- Android加载图片框架Picasso
- 【JavaScript】事件
- <QNX> Linux Host开发环境搭建
- 面试小结之IO篇
- css-alert-normalize
- python 实现 kNN 算法
- 图片加载框架Picasso源码解析
- TestLink的安装使用
- C++输入输出格式控制
- 笔记本连接电视屏幕的屏幕适配问题
- kettel 中文插入数据库乱码
- Oracle 多表连接
- HDU6077-Time To Get Up
- POJ2492 带权并查集
- 学习bootstrap的总结