介绍
Picasso(毕加索)是时下网上比较流行的开源图片加载库,性能不错,使用也方便,调用时只需要一行代码就可以:
Picasso.with(context).load(imageUrl).into(imageView);
with()
源码如下
public static Picasso with(Context context) { if (singleton == null) { synchronized (Picasso.class) { if (singleton == null) { singleton = new Builder(context).build(); } } } return singleton; }
构造了一个Bulder的单例,点进去Builder的构造方法和build()方法看看
Builder构造方法
public Builder(Context context) { if (context == null) { throw new IllegalArgumentException("Context must not be null."); } this.context = context.getApplicationContext(); }可见毕加索为了防止内存泄漏,用了应用的上下文
build()方法
public Picasso build() { Context context = this.context; if (downloader == null) { downloader = Utils.createDefaultDownloader(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); }
static final Handler HANDLER = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { ...//处理图片下载结果,一会儿再说 } };
再就是那个dispatcher,它负责消息的中转,具体使用也一会儿再说。
这样,我们就通过with()方法构造了一个毕加索对象
load()
load()方法代码如下
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)); }
假设我们的path不为空,就进入了load(Uri uri)方法
public RequestCreator load(Uri uri) { return new RequestCreator(this, uri, 0); }
进入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); }
而后又是Builder的构造方法
Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) { this.uri = uri; this.resourceId = resourceId; this.config = bitmapConfig; }
原来就是一些属性的赋值,那我们接着看into()方法。不过此时,我们已经获取了一个RequestCreator对象,into()方法是他的方法
into()
into()方法是在RequestCreator类中的,这个方法代码很长,我先贴出来
public void into(ImageView target, Callback callback) { long started = System.nanoTime(); checkMain(); // 必须是主线程调用 .. // uri和requestId合法性检查,一般可忽略 .. // RequestCreator.Builder的resize,一般用不到 Request request = createRequest(started); String requestKey = createKey(request); .. // 检查缓存映射,找到bitmap放入target中,并返回成功, if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); // 设置占位图 } Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); picasso.enqueueAndSubmit(action); }
enqueueAndSubmit()
里面最主要的是最后那几行,实例化一个Action对象(其构造方法也是一堆属性的赋值),并调用picasso.enqueueAndSubmit()方法,我们点进这个enqueueAndSubmit()方法:
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); }
首先尝试从targetToAction这个映射中获取先前的action,如果能获取,就调用cancelExistingRequest()方法,这个方法用来取消前面的请求
而后把新的action保存到映射中
最后调用submit()方法
void submit(Action action) { dispatcher.dispatchSubmit(action); }
调用dispatcher.dispatchSubmit()
void dispatchSubmit(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action)); }
给handler发送提交消息,这时dispatcher的调度作用就显示出来了
我们先看handler的定义,这是Dispatcher内部的一个handler,定义也是在构造方法中
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
我们看handler怎么处理REQUEST_SUBMIT信息
@Override public void handleMessage(final Message msg) { switch (msg.what) { case REQUEST_SUBMIT: { Action action = (Action) msg.obj; dispatcher.performSubmit(action); break; } .. //其他case }
void performSubmit(Action action, boolean dismissFailed) { .. // 暂停、请求重复、service关闭判断并处理 hunter = forRequest(action.getPicasso(), this, cache, stats, action); hunter.future = service.submit(hunter); hunterMap.put(action.getKey(), hunter); .. // 更新failedActions,打日志 }
主要的就是中间那三行,第一行构造一个BitmapHunter对象,构造方法也是一堆属性的赋值;第二行submit这个hunter,第三行把这个hunter保存到映射中,防止重复请求
我们主要就看第二行那个submit的实现,这个在PicassoExecutorService类中
public Future<?> submit(Runnable task) { PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task); execute(ftask); return ftask; }
第一行的PicassoFutureTask类就是一个FutureTask类的子类,只不过多了一个BitmapHunter属性,构造方法也是为这个BitmapHunter赋值
第二行的execute(ftask),就是把这个ftask交给线程池调度,这个不在本文的范围,而线程池调度主要就是调用Runnable的run()方法,而BitmapHunter正是实现了Runnable接口,所以下面,我们将重心,放在BitmapHunter的run()方法中
@Override public void run() { try { .. // 更新线程名+打日志 result = hunt(); if (result == null) { dispatcher.dispatchFailed(this); } else { dispatcher.dispatchComplete(this); } } .. // 异常处理 }
调用hunt()方法获取结果,而后让dispatcher分发结果
hunt()
我们先看hunt()方法
Bitmap hunt() throws IOException { Bitmap bitmap = null; .. // 缓存处理 data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy; RequestHandler.Result result = requestHandler.load(data, networkPolicy); if (result != null) { .. // 其他字段 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); } } } .. // 日志、转换、状态更新 return bitmap; }
主要分三步:1、requestHandler.load()获取结果
2、根据result获取Bitmap。这里就是获取了mBitmap属性
3、如果我们传入的是图片网址,这里获取的位图就是空,需要调用decodeStream()解码输入流
我们这儿只看第一步
requestHandler的load()
这个requestHandler属性由构造方法而来,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); }
canHandleRequest()方法是抽象方法,不同的RequestHandler子类有不同的实现,于是我们主要就在毕加索里找,他在哪里给requestHandlers赋值或添加内容,发现是在构造方法中
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener, RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats, Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) { ... List<RequestHandler> allRequestHandlers = new ArrayList<RequestHandler>(builtInHandlers + extraCount); // ResourceRequestHandler needs to be the first in the list to avoid // forcing other RequestHandlers to perform null checks on request.uri // to cover the (request.resourceId != 0) case. allRequestHandlers.add(new ResourceRequestHandler(context)); if (extraRequestHandlers != null) { allRequestHandlers.addAll(extraRequestHandlers); } allRequestHandlers.add(new ContactsPhotoRequestHandler(context)); allRequestHandlers.add(new MediaStoreRequestHandler(context)); allRequestHandlers.add(new ContentStreamRequestHandler(context)); allRequestHandlers.add(new AssetRequestHandler(context)); allRequestHandlers.add(new FileRequestHandler(context)); allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats)); requestHandlers = Collections.unmodifiableList(allRequestHandlers); ... }
最主要的是load()方法,我们点进去
@Override public Result load(Request request, int networkPolicy) throws IOException { Response response = downloader.load(request.uri, request.networkPolicy); .. // 判空 Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK; Bitmap bitmap = response.getBitmap(); .. // 如果用默认方式获取的话,这里的位图是空 InputStream is = response.getInputStream(); .. // 判空+结果状态更新 return new Result(is, loadedFrom); }
注意,如果我们传入的是图片网址的话,这里我们只能获取图片的输入流。不过我们要先看看downloader.load()方法是怎么获取Response的
downloader的load()
downloader的默认最终来源是在Picasso.Builder的build()方法,代码在文章开始已经展示过了,这里我们只看downloader的赋值相关内容
public Picasso build() { Context context = this.context; if (downloader == null) { downloader = Utils.createDefaultDownloader(context); } .. // 其他组件 Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, loggingEnabled); }
调用了createDefaultDownloader()
static Downloader createDefaultDownloader(Context context) { try { Class.forName("com.squareup.okhttp.OkHttpClient"); return OkHttpLoaderCreator.create(context); } catch (ClassNotFoundException ignored) { } return new UrlConnectionDownloader(context); }
可见,先尝试构造okHttpClient做为downloader,如果没这个类,就用UrlConnectionDownloader做为downloader。
假设我们就没导入okHttp依赖,我们用的是UrlConnectionDownloader,它的load()方法代码如下:
@Override public Response load(Uri uri, int networkPolicy) throws IOException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { installCacheIfNeeded(context); } HttpURLConnection connection = openConnection(uri); connection.setUseCaches(true); .. // 处理networkPolicy不是0的情况,而默认是0 int responseCode = connection.getResponseCode(); if (responseCode >= 300) { .. // 断连接,抛异常 } long contentLength = connection.getHeaderFieldInt("Content-Length", -1); boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE)); return new Response(connection.getInputStream(), fromCache, contentLength); }
其实就是用了我们常用的URLConnection,代码很简单。我再把调用的openConnection()方法代码贴出来,就更清晰了
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; }
好,返回到NetRequestHandler.load()方法中,这是我们已经获取了response对象,而后获取response中的输入流,赋值到Result对象中,就从NetRequestHandler.load()中返回到BitmapHunter.hunt()方法中,进行输入流解码,顺利的话,就返回到run()方法中了。
handler与dispatcher处理结果
run()方法中,获取的result对象不为空,就调用dispatcher.dispatchComplete()方法
void dispatchComplete(BitmapHunter hunter) { handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter)); }
给handler发送完成消息,handler中相应处理如下:
case HUNTER_COMPLETE: { BitmapHunter hunter = (BitmapHunter) msg.obj; dispatcher.performComplete(hunter); break; }
调用了dispatcher.performComplete()方法
void performComplete(BitmapHunter hunter) { .. // hunter写入缓存,同时移除请求 batch(hunter); .. // 日志 }
调用了batch()方法
private void batch(BitmapHunter hunter) { .. // 异常处理 batch.add(hunter); if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) { handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY); } }
而handler对next_batch的处理如下:
case HUNTER_DELAY_NEXT_BATCH: { dispatcher.performBatchComplete(); break; }
调用了dispatcher.performBatchComplete()
void performBatchComplete() { List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch); batch.clear(); mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy)); logBatch(copy); }
主要是给主线程发送了batch_complete消息,并把bitmapHunter列表传了过去
主线程HANDLER对这个消息的处理如下:
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; }
遍历BitmapHunter列表,调用每个的complete()方法
complete()
complete()方法代码如下:
void complete(BitmapHunter hunter) { Action single = hunter.getAction(); List<Action> joined = hunter.getActions(); boolean hasMultiple = joined != null && !joined.isEmpty(); boolean shouldDeliver = single != null || hasMultiple; .. // 异常判断+处理 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); } } .. // 异常处理 }
不管是处理单个还是多个,都调用了deliverAction()方法
private void deliverAction(Bitmap result, LoadedFrom from, Action action) { .. // 异常判断+处理 if (result != null) { .. // 对from的判空 action.complete(result, from); .. // 日志 } else { // .. 异常处理 } }
主要就是action.complete()方法,这儿我们还是只分析ImageViewAction中的实现
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) { .. // 异常判断+处理 ImageView target = this.target.get(); .. // 判空 Context context = picasso.context; boolean indicatorsEnabled = picasso.indicatorsEnabled; PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled); .. // 回调 }
调用了PicassoDrawable.setBitmap()来设置位图,代码如下:
static void setBitmap(ImageView target, Context context, Bitmap bitmap, Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) { .. // 占位图处理 PicassoDrawable drawable = new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging); target.setImageDrawable(drawable); }
其实就是把bitmap包装成PicassoDrawable,这个PicassoDrawable就是一个BitmapDrawable,只不过多了一些渲染,但默认的话其实都没有
最后就是把drawable设置为imageView了。
结语
到此,Picasso的源码我们就简单分析过了,其中两个handler(一个实现图片的批处理,一个设置UI)实现的图片批处理、线程池的运用、dispatcher的调度作用等等内容,都值得我们细细回味。