前言
使用的是picasso最新版本 github地址:https://github.com/square/picasso
版本:2.71828
简单例子
代码
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Picasso.get().setIndicatorsEnabled(true) Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest) Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest2) }复制代码
代码很简单,令大家比较惊讶的应该是左上角的蓝三角,其实原图是没有的,由于我加入了Picasso.get().setIndicatorsEnabled(true)
,打开了指示标志。
这里先直接说明下代表的意思,后面我们再慢慢深入。 Picasso.java
public enum LoadedFrom { //内存加载,绿色 MEMORY(Color.GREEN), //磁盘加载,蓝色 DISK(Color.BLUE), //网络加载,红色 NETWORK(Color.RED); final int debugColor; LoadedFrom(int debugColor) { this.debugColor = debugColor; } }复制代码
一般来说,绝大多数的图片框架都是三级缓存,Picasso也不例外。 Glide
,Fresco
我还未深入了解,但是Picasso
这个标识还是很有用的。很容易让我们能够明白是哪种加载方式。
先简单的说明下这是如何去实现的。
PicassoDrawable.java
@Override public void draw(Canvas canvas) { if (!animating) { super.draw(canvas); } else { float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION; if (normalized >= 1f) { animating = false; placeholder = null; super.draw(canvas); } else { if (placeholder != null) { placeholder.draw(canvas); } // setAlpha will call invalidateSelf and drive the animation. int partialAlpha = (int) (alpha * normalized); super.setAlpha(partialAlpha); super.draw(canvas); super.setAlpha(alpha); } } //前面都是绘制原图的 if (debugging) { //这里判断下,绘制下标识 drawDebugIndicator(canvas); } } private void drawDebugIndicator(Canvas canvas) { DEBUG_PAINT.setColor(WHITE); Path path = getTrianglePath(0, 0, (int) (16 * density)); canvas.drawPath(path, DEBUG_PAINT); //根据加载方式 DEBUG_PAINT.setColor(loadedFrom.debugColor); path = getTrianglePath(0, 0, (int) (15 * density)); canvas.drawPath(path, DEBUG_PAINT); }复制代码
源码解析
前面只是简单的介绍了一下Picasso的一个小功能,下面还是通过上面那个简单的加载图片代码,一步步跟入源码,来介绍下是如何实现图片加载的,如何做到三级缓存的。
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest)复制代码
- get Picasso.java
public static Picasso get() { if (singleton == null) { synchronized (Picasso.class) { if (singleton == null) { if (PicassoProvider.context == null) { throw new IllegalStateException("context == null"); } singleton = new Builder(PicassoProvider.context).build(); } } } return singleton; }public Picasso build() { Context context = this.context; if (downloader == null) { downloader = new OkHttp3Downloader(context); } if (cache == null) { cache = new LruCache(context); } if (service == null) { service = new PicassoExecutorService(); } if (transformer == null) { transformer = RequestTransformer.IDENTITY; } Stats stats = new Stats(cache); Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, loggingEnabled); }复制代码
非常简单的一个单例模式,和建造者模式。单例模式就不过多说了,这里主要介绍下建造者模式,一般来说对于参数比较多的构造方法,使用建造者模式,就可以直接使用链式的方式,来配置对象。
这里直接使用Picasso.get
其实是获取了默认的一个Picasso
对象,然后帮你默认的配置了LruCache
,PicassoExecutorService
,RequestTransformer
,OkHttp3Downloader
,Stats
,Dispatcher
。
很显然,一般来说,肯定是会提供一个自定义的方式,不然就太low了。
public static void setSingletonInstance(@NonNull Picasso picasso) { if (picasso == null) { throw new IllegalArgumentException("Picasso must not be null."); } synchronized (Picasso.class) { if (singleton != null) { throw new IllegalStateException("Singleton instance already exists."); } singleton = picasso; } }复制代码
你可以使用Picasso.Builder
先自己构建一个Picasso
对象,然后再调用这个方法,接下来就可以使用Picasso.get()
来获取自己的配置的单例了。
- load load方法有很多重载,这里还是以String为例子。
public RequestCreator load(@Nullable String path) { if (path == null) { return new RequestCreator(this, null, 0); } if (path.trim().length() == 0) { throw new IllegalArgumentException("Path must not be empty."); } return load(Uri.parse(path)); }public RequestCreator load(@Nullable Uri uri) { return new RequestCreator(this, uri, 0); }复制代码
很显然,load方法只是为了获取一个RequestCreator
对象。
RequestCreator(Picasso picasso, Uri uri, int resourceId) { if (picasso.shutdown) { throw new IllegalStateException( "Picasso instance already shut down. Cannot submit new requests."); } this.picasso = picasso; this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig); }复制代码
而RequestCreator
里面最重要的,其实就是data
也就是一个Request.Builder
,从这里其实我们很明显的可以看出,RequestCreator
,顾名思义,就是为了创建一个Request
,最终的Request
肯定是由data.build
生成的。但是目前只是new了一个Request.Builder
对象,并没有调用。这是因为后面我们还需要往Request.Builder
塞入很多不同的参数。
由上图其实我们可以发现,我们常用的一些链式方法,如centerCrop
等,其实就是调用了Request.Builder
对象的方法,只是为了构建一个Request
.
- into 这里才是真正发起请求的地方。
public void into(ImageView target) { into(target, null); }public void into(ImageView target, Callback callback) { long started = System.nanoTime(); //判断下是否为主线程 checkMain(); if (target == null) { throw new IllegalArgumentException("Target must not be null."); } //如果uri为空或者resId为0,则直接取消请求,设置为placeholder图片 if (!data.hasImage()) { picasso.cancelRequest(target); if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } return; } //这里就牛逼了,后面详细讲 if (deferred) { if (data.hasSize()) { throw new IllegalStateException("Fit cannot be used with resize."); } int width = target.getWidth(); int height = target.getHeight(); if (width == 0 || height == 0) { if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } picasso.defer(target, new DeferredRequestCreator(this, target, callback)); return; } data.resize(width, height); } //简单理解,就是调用了data.build(),生成一个Request Request request = createRequest(started); //这里通过request生成一个String,用来后面key-value保存图片在LruCache中 String requestKey = createKey(request); if (shouldReadFromMemoryCache(memoryPolicy)) { //如果前面请求过了,会缓存到内存,这边再请求,还是会生成了相同的key,直接从cache中获取到了Bitmap Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey); if (bitmap != null) { picasso.cancelRequest(target); setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled); if (picasso.loggingEnabled) { log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY); } if (callback != null) { callback.onSuccess(); } return; } } //没有从内存中获取到缓存,先设置placeholder图片 if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } //创建一个action Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); //提交一个action picasso.enqueueAndSubmit(action); }复制代码
这里其实非常简单的分析了下。
这里面有2步单独拿出来说。
- deferred的作用
public RequestCreator fit() { deferred = true; return this; } /** Internal use only. Used by { @link DeferredRequestCreator}. */ RequestCreator unfit() { deferred = false; return this; }....if (deferred) { if (data.hasSize()) { throw new IllegalStateException("Fit cannot be used with resize."); } int width = target.getWidth(); int height = target.getHeight(); //如果说imageview本身已经可以获取到宽高了,都不是0,那么就直接resize一下图片,如果说有一个是0,说明这个Imageview可能还没有布局完成,还没有自己的宽高,那么就在原来的`RequestCreator`外面再包了一层`DeferredRequestCreator ` if (width == 0 || height == 0) { if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } picasso.defer(target, new DeferredRequestCreator(this, target, callback)); return; } data.resize(width, height); }复制代码
用过了fit
方法的人应该知道,调用后可以适配ImageView
的尺寸,这里就是实现方式
下面我们来看看DeferredRequestCreator
是如何实现的
DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) { this.creator = creator; this.target = new WeakReference<>(target); this.callback = callback; //实现很简单,就是给ImageView设置下监听 target.addOnAttachStateChangeListener(this); if (target.getWindowToken() != null) { onViewAttachedToWindow(target); } } @Override public void onViewAttachedToWindow(View view) { view.getViewTreeObserver().addOnPreDrawListener(this); }//这里才是最关键的部分 @Override public boolean onPreDraw() { ImageView target = this.target.get(); if (target == null) { return true; } ViewTreeObserver vto = target.getViewTreeObserver(); if (!vto.isAlive()) { return true; } int width = target.getWidth(); int height = target.getHeight(); if (width <= 0 || height <= 0) { return true; } target.removeOnAttachStateChangeListener(this); vto.removeOnPreDrawListener(this); this.target.clear(); //获取到了ImageView的宽高后,调用resize重新设置了下宽高。 this.creator.unfit().resize(width, height).into(target, callback); return true; }复制代码
- 真正去加载图片的地方
public void into(ImageView target, Callback callback) { ... Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); picasso.enqueueAndSubmit(action); }/**下面是每一步的方法***/void enqueueAndSubmit(Action action) { Object target = action.getTarget(); if (target != null && targetToAction.get(target) != action) { // This will also check we are on the main thread. cancelExistingRequest(target); targetToAction.put(target, action); } submit(action); }void submit(Action action) { dispatcher.dispatchSubmit(action); }void dispatchSubmit(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action)); } @Override public void handleMessage(final Message msg) { switch (msg.what) { case REQUEST_SUBMIT: { Action action = (Action) msg.obj; dispatcher.performSubmit(action); break; } ... void performSubmit(Action action) { performSubmit(action, true); }复制代码
前面其实讲到了这里,我们再往后继续。从enqueueAndSubmit
一步步往下,虽然调用了很多方法,但是最终,其实就是调用Dispatcher
中的performSubmit
方法。下面我们来具体分析下这个方法。
void performSubmit(Action action, boolean dismissFailed) { ... BitmapHunter hunter = hunterMap.get(action.getKey()); if (hunter != null) { hunter.attach(action); return; } ... //一开始hunterMap肯定不包含action的key,所以会创建一个BitmapHunter hunter = forRequest(action.getPicasso(), this, cache, stats, action); //其实我们会发现BitmapHunter是一个Runnable,service是ExecutorService,可以理解为一个线程池,这里就直接执行一个Runnable hunter.future = service.submit(hunter); hunterMap.put(action.getKey(), hunter); ... }//通过传入的参数生成一个BitmapHunterstatic BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) { Request request = action.getRequest(); ListrequestHandlers = picasso.getRequestHandlers(); for (int i = 0, count = requestHandlers.size(); i < count; i++) { RequestHandler requestHandler = requestHandlers.get(i); //最重要的地方在这里,遍历所有的requestHandler,看哪个requestHandler能够处理request,后面再详细介绍 if (requestHandler.canHandleRequest(request)) { return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler); } } return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER); }复制代码
为了让后面我们可以更好的理解,我们先回过头来,看一下requestHandlers是什么东西,为什么要先找出能够处理当前Request
的RequestHandler
.
一直往前找发现是在Picasso的构造方法里面初始化的
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener, RequestTransformer requestTransformer, ListextraRequestHandlers, Stats stats, Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) { ... List allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount); //resource图片处理,比如R.drawable这种 allRequestHandlers.add(new ResourceRequestHandler(context)); if (extraRequestHandlers != null) { //自定义处理 allRequestHandlers.addAll(extraRequestHandlers); } //联系人图片处理 allRequestHandlers.add(new ContactsPhotoRequestHandler(context)); //媒体资源处理 allRequestHandlers.add(new MediaStoreRequestHandler(context)); //流资源处理 allRequestHandlers.add(new ContentStreamRequestHandler(context)); //asset资源处理 allRequestHandlers.add(new AssetRequestHandler(context)); //文件资源处理 allRequestHandlers.add(new FileRequestHandler(context)); //网络资源处理 allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats)); ... }复制代码
都是通过load方法之后的参数来判断的。我们这里以NetworkRequestHandler
为例
@Override public boolean canHandleRequest(Request data) { String scheme = data.uri.getScheme(); return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme)); }复制代码
如果说uri是http
或者https
就可以由NetworkRequestHandler
来处理。
那么我们继续回到刚才那个地方
void performSubmit(Action action, boolean dismissFailed) {... hunter = forRequest(action.getPicasso(), this, cache, stats, action); //其实我们会发现BitmapHunter是一个Runnable,service是ExecutorService,可以理解为一个线程池,这里就直接执行一个Runnable hunter.future = service.submit(hunter); hunterMap.put(action.getKey(), hunter); ... }复制代码
获取到可以处理RequestHandler
之后创建了一个BitmapHunter
,然后调用service.submit
最终其实是调用Runnable
的run
方法,我们继续跟入。
@Override public void run() { try { ... result = hunt(); ... } }Bitmap hunt() throws IOException { Bitmap bitmap = null; //在真正发起请求之前,再次判断下,是否从内存中获取图片 if (shouldReadFromMemoryCache(memoryPolicy)) { bitmap = cache.get(key); if (bitmap != null) { stats.dispatchCacheHit(); loadedFrom = MEMORY; if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache"); } return bitmap; } } networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy; //前面其实我们已经分析过,requestHandler其实是NetworkRequestHandler,等下单独提出load方法来讲 RequestHandler.Result result = requestHandler.load(data, networkPolicy); if (result != null) { loadedFrom = result.getLoadedFrom(); //获取下exif格式信息,一般情况用不到,这里不深入 exifOrientation = result.getExifOrientation(); //获取到真正的bitmap bitmap = result.getBitmap(); if (bitmap == null) { Source source = result.getSource(); try { bitmap = decodeStream(source, data); } finally { try { source.close(); } catch (IOException ignored) { } } } } //下面一大串其实是对原来的图片进行一些变换,这里先不深入 if (bitmap != null) { ... if (data.needsTransformation() || exifOrientation != 0) { synchronized (DECODE_LOCK) { if (data.needsMatrixTransform() || exifOrientation != 0) { bitmap = transformResult(data, bitmap, exifOrientation); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId()); } } if (data.hasCustomTransformations()) { bitmap = applyCustomTransformations(data.transformations, bitmap); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations"); } } } if (bitmap != null) { stats.dispatchBitmapTransformed(bitmap); } } } return bitmap;复制代码
下面我们还是具体再看看NetworkRequestHandler
@Override public Result load(Request request, int networkPolicy) throws IOException { okhttp3.Request downloaderRequest = createRequest(request, networkPolicy); Response response = downloader.load(downloaderRequest); ResponseBody body = response.body(); if (!response.isSuccessful()) { body.close(); throw new ResponseException(response.code(), request.networkPolicy); } //从这里可以看出磁盘缓存 Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK; if (loadedFrom == DISK && body.contentLength() == 0) { body.close(); throw new ContentLengthException("Received response with 0 content-length header."); } if (loadedFrom == NETWORK && body.contentLength() > 0) { stats.dispatchDownloadFinished(body.contentLength()); } return new Result(body.source(), loadedFrom); }复制代码
其实这里最关键的部分就是response.cacheResponse()
这一句代码。因为之前我一直以为Picasso
使用的是DiskLruCache
来进行磁盘缓存。但是一直找不到实现的地方。一直找到这里才恍然大悟,Picasso
的磁盘缓存是利用http
协议中的cache-control
去实现的。 然后使用的其实是Okhttp3
实现了http协议
,其中磁盘缓存确实也是用DiskLruCache
来实现的。
总结
后面还会继续深入。