Android Picasso图片加载库源码剖析
来源:互联网 发布:mac 207 编辑:程序博客网 时间:2024/05/17 06:40
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遍历这个集合完成图形的变换。
- Android Picasso图片加载库源码剖析
- Android 源码解析: 图片加载库Picasso 1
- Android 源码解析: 图片加载库Picasso 2 Cache机制
- Android 源码解析: 图片加载库Picasso 3 核心类
- Android之图片加载库Picasso源码分析
- picasso Android图片加载
- Android图片加载库:Picasso详解
- Android:图片加载库Glide VS Picasso
- Android图片加载库:Picasso讲解
- Android图片加载库Picasso和Glide
- Picasso 图片加载库
- Android 源码解析: 图片加载库Picasso 4 任务调度 Dispatcher
- Picasso--Android图片加载开源库
- Android图片加载框架Picasso
- Android加载图片框架Picasso
- Android-Picasso库使用详解-从入门到源码剖析
- Picasso图片加载框架源码解析
- 图片加载框架Picasso源码解析
- 上楼梯
- android layout 的小知识
- Shell 脚本中如何使用make命令
- 第十一周【项目3 - 警察和厨师】
- oracle 数据库查询-关联查询-2
- Android Picasso图片加载库源码剖析
- Grunt 之 watch 和 livereload
- View
- 事件处理模型
- CAN与CANOpen(二)
- Matlab分析音频
- 高仿QQ源码 界面(3)
- 【Android】图像增强
- Spark Shuffle内幕