Picasso是一个优秀的轻量级网络图片加载缓存库。花了两天时间研读了下的阅读了下他的源码。做一下的剖析:
Picasso的优点:
- 足够轻量级:maven打包出来的jar只有130kb左右
- 二级缓存策略,分别缓存内存和磁盘空间
- 自动监控内存大小数据
- 很好的线程控制,根据网络状态控制线程数量、具有优先级调度策略。
- 图片适应、压缩处理策略
- 预加载功能
- 代码质量高、易拓展。
1 Picasso整体画像
1 流程图
2 Picasso 基本使用和概括流程
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
看下初始化的方法。with()获的Picasso的全局单例。
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
使用Builder模式获得实例,看起来比较清晰明了。
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);
}
建造者获得实例的时候会初始好Download(网络下载模块)、LruCache(缓存核心)、RequestTransformer(Request运输类)、Stats(检测类)、Dispatch(事务分发中心)。
调用load(uri)开始执行图片加载
load(Uri)
load(String)
load(File)
其中#load(Uri)
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
RequestCreator提供了图片相关处理相关的所有API,RequestCreator所有的api方法结果return this。可以理解他同样为一个builder模式的建造者。着重看下装载图片的into()方法实现。
public void into(Target target) {
long started = System.nanoTime();
//检查是否运行在主线程
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
if (deferred) {
throw new IllegalStateException("Fit cannot be used with a Target.");
}
if (!data.hasImage()) {
picasso.cancelRequest(target);
target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);
return;
}
Request request = createRequest(started);
String requestKey = createKey(request);
//优先从内存缓存读取
if (shouldReadFromMemoryCache(memoryPolicy)) {
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
//取消网络的加载
picasso.cancelRequest(target);
target.onBitmapLoaded(bitmap, MEMORY);
return;
}
}
target.onPrepareLoad(setPlaceholder ? getPlaceholderDrawable() : null);
Action action =
new TargetAction(picasso, target, request, memoryPolicy, networkPolicy, errorDrawable,
requestKey, tag, errorResId);
//提交动作执行下载
picasso.enqueueAndSubmit(action);
}
到这里可以大体看到的图片加载的流程代码,Picasso模块初始化之后,初始了各个核心模块,并创建RequestCreator提供出图片相关的所有操作API,在执行启动into下载图片的时机优先使用缓存中的数据。那么他们各个模块是怎么协调工作的呢?下面分块来揭秘。
2 线程控制
BitmapHunter implements Runnable
这是一个单独的图片处理的线程单元。 run()方法中调用hunt方法获取bitmap执行的核心代码
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();
exifOrientation = 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);
//图片适配的处理,由于是多线程所以做了同步加锁的处理DECODE_LOCK
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;
}
Dispatch类是一个控制的中心,控制线程的加载和取消、网络监听、消息处理等。
Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
Downloader downloader, Cache cache, Stats stats) {
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();
Utils.flushStackLocalLeaks(dispatcherThread.getLooper());
this.context = context;
this.service = service;
···
代码省略
}
其构造中获得service 即为PicassoExecutorService ,而PicassoExecutorService 集成自ThreadPoolExecutor,是一个线程池。
Picasso具有根据网络状况控制线程数量的方法就是有PicassoExecutorService来控制完成的
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);
}
}
上一节我们在into方法中提交执行下载enqueueAndSubmit的过程最终交由控制中心Dispatch中performSubmit来完成
void performSubmit(Action action, boolean dismissFailed) {
if (pausedTags.contains(action.getTag())) {
···
省略代码
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
hunter.future = service.submit(hunter);
hunterMap.put(action.getKey(), hunter);
···
代码省略
}
}
叫BitmapHunter的线程放入线程池中控制执行
hunter.future = service.submit(hunter);
@Override
public Future<?> submit(Runnable task) {
PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
execute(ftask);
return ftask;
}
3 回收任务
为了避免oom,缓存中Target使用了weakReference弱引用,方便被系统回收。但是有些Target(比如说ImageView)已经被回收,但是所对应的Request请求还在继续任务(Action),就会浪费资源。Picasso中引入了一个叫CleanupThread的内部线程,CleanupThread是一个daemon线程,它的工作是找到那些Target(比如说ImageView)已经被回收的取消相应的任务Action。
看线程代码
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 {
// Prior to Android 5.0, even when there is no local variable, the result from
// remove() & obtainMessage() is kept as a stack local variable.
// We're forcing this reference to be cleared and replaced by looping every second
// when there is nothing to do.
// This behavior has been tested and reproduced with heap dumps.
RequestWeakReference<?> remove =
(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();
}
}
由此看出此线程一直在遍历ReferenceQueue,从中找到这样的reference,就交给handler,handler会从reference中拿到action.
取消:
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();
}
}
}
4 LruCache缓存
Picasso 采用LruCache缓存方式,借鉴了volley。本质是使用LinkedHashMap缓存。使用LinkedHashMap是因为其具有存取快,易遍历的数据结构。
this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
初始化缓存内存的大小,在LurCache初始化的时候可以传入自定义的大小控件。默认的大小为内存的15%。
static int calculateMemoryCacheSize(Context context) {
ActivityManager am = getService(context, ACTIVITY_SERVICE);
boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
int memoryClass = am.getMemoryClass();
if (largeHeap && SDK_INT >= HONEYCOMB) {
memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
}
// Target ~15% of the available heap.
return (int) (1024L * 1024L * memoryClass / 7);
}
存取很简单就是简单的从map中存取缓存对象。
@Override public void set(String key, Bitmap bitmap) {
if (key == null || bitmap == null) {
throw new NullPointerException("key == null || bitmap == null");
}
Bitmap previous;
//set、put可能为并发的操作,需要同步加锁。
synchronized (this) {
putCount++;
size += Utils.getBitmapBytes(bitmap);
previous = map.put(key, bitmap);
if (previous != null) {
size -= Utils.getBitmapBytes(previous);
}
}
//是否超过最大控件
trimToSize(maxSize);
}
5 图形变化
图片变化由Transformation定义了接口。交由BitmapHunter的hunt核心代码中执行。
static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {
for (int i = 0, count = transformations.size(); i < count; i++) {
final Transformation transformation = transformations.get(i);
Bitmap newResult;
try {
newResult = transformation.transform(result);
} catch (final RuntimeException e) {
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new RuntimeException(
"Transformation " + transformation.key() + " crashed with exception.", e);
}
});
return null;
}
if (newResult == null) {
final StringBuilder builder = new StringBuilder() //
.append("Transformation ")
.append(transformation.key())
.append(" returned null after ")
.append(i)
.append(" previous transformation(s).\n\nTransformation list:\n");
for (Transformation t : transformations) {
builder.append(t.key()).append('\n');
}
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new NullPointerException(builder.toString());
}
});
return null;
}
if (newResult == result && result.isRecycled()) {
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new IllegalStateException("Transformation "
+ transformation.key()
+ " returned input Bitmap but recycled it.");
}
});
return null;
}
// If the transformation returned a new bitmap ensure they recycled the original.
if (newResult != result && !result.isRecycled()) {
Picasso.HANDLER.post(new Runnable() {
@Override public void run() {
throw new IllegalStateException("Transformation "
+ transformation.key()
+ " mutated input Bitmap but failed to recycle the original.");
}
});
return null;
}
result = newResult;
}
return result;
}
Request 维护了一个图形变换的列表。图片加载成功后 BitmapHunter遍历这个集合完成图形的变换。