Picasso源码分析(三):快照功能实现和HandlerThread的使用
2016-06-13 10:35
736 查看
Picasso源码分析(一):单例模式、建造者模式、面向接口编程
Picasso源码分析(二):默认的下载器、缓存、线程池和转换器
Picasso源码分析(三):快照功能实现和HandlerThread的使用
Picasso源码分析(四):不变模式、建造者模式和Request的预处理
Picasso源码分析(五):into方法追本溯源和责任链模式创建BitmapHunter
Picasso源码分析(六):BitmapHunter与请求结果的处理
public class HandlerThread extends Thread {
使用HandlerThread的优点是比较明显的,可以避免频繁的创建线程(耗费资源和性能)用于处理后台任务,取而代之的思路是开启一个具有消息循环功能的线程,达到线程复用的目的,和线程池异曲同工,只不过是排队处理多任务,没有并发。
在Stats的构造函数中,创建了HandlerThread对象statsThread,并调用了其start方法用于开启线程,也就是开启了消息循环,因为在HandlerThread的run方法开头调用了Looper.prepare()用于绑定线程和looper,run方法结尾调用了Looper.loop()用于循环处理消息。
接着处理了一个Android 5中HandlerThread的问题,意思是HandlerThread总是在其线程栈中保持最后一个消息的引用,有可能导致最后一个消息的内存泄露。解决办法就是每秒给handlerThread的looper发送一个消息,这样最后一个消息的引用在HandlerThread栈中的引用并不会保持太久。
handlerThread对象创建并已经启动了,就这需要为其looper创建一个handler
可以看到handler和statsThread的looper进行了绑定,因此handler的handleMessage消息处理方法会在statsThread的线程中执行。
handler是一个StatsHandler类型对象,继承自Handler
重写了Handler的handleMessage方法
这样Stats消息处理的功能有了,那么发送消息的功能应该是Stats暴漏给外部的API
这样外部在处理缓存的时候,如果缓存命中,就调用dispatchCacheHit()方法,缓存没命中就调用dispatchCacheMiss()方法,其他的解码、转换、下载成功等操作结果都会调用相应的方法,而这些方法均会给handler发送个消息,这些消息对应的任务就都会在handler所绑定的looper的线程中排队执行了。
Picasso中的快照Stats主要用于统计维护Picasso的各种操作的数量,包括如下(命名很规范,顾名能思义):
以下载成功为例进行分析。
下载成功后,会调用 dispatchDownloadFinished(long size)方法,该方法会给handler发送一条Message消息,消息的what字段为DOWNLOAD_FINISHED,obj字段为下载成功的文件大小size
在消息处理回调中,会处理DOWNLOAD_FINISHED类型的消息
也就是调用了performDownloadFinished方法
performDownloadFinished维护相应的数据变化,下载成功次数加1,已经下载的总大小加size,重新计算平均大小。
其他操作类似,一旦有操作结果的变化均会调用相应方法进行数据维护。
这样快照中的各项数据均得到及时维护,如果要获取某一时刻的快照,调用createSnapshot()方法即可。
这样就获取到了一个StatsSnapshot类型的对象,调用其dump方法即可将快照内容输出
Picasso源码分析(二):默认的下载器、缓存、线程池和转换器
Picasso源码分析(三):快照功能实现和HandlerThread的使用
Picasso源码分析(四):不变模式、建造者模式和Request的预处理
Picasso源码分析(五):into方法追本溯源和责任链模式创建BitmapHunter
Picasso源码分析(六):BitmapHunter与请求结果的处理
HandlerThread原理和用法
HandlerThread是一个Android系统提供的提供快速开发的工具类,功能为开启一个具有looper的线程,该线程的looper可以和handler绑定,也就是说创建handler的时候可以在handler构造函数传入handlerThread对象的looper,这样此handler的消息处理 方法handleMessage就会在handlerThread所在的线程执行。这样可以在工作线程执行耗时的消息循环。因为HandlerThread继承自Thread因此本质上是一个线程,必须先调用start方法后再获取其looperpublic class HandlerThread extends Thread {
使用HandlerThread的优点是比较明显的,可以避免频繁的创建线程(耗费资源和性能)用于处理后台任务,取而代之的思路是开启一个具有消息循环功能的线程,达到线程复用的目的,和线程池异曲同工,只不过是排队处理多任务,没有并发。
Picasso中HandlerThread的使用
Picasso中有一个提供快照的功能类Stats,用于统计某一时间Picasso中的各项数据。这些统计的功能并不需要和UI交互,在后台线程新创建一个具有可以处理消息循环的线程最好不过了,Picasso正是这么做了,使用Handler和HandlerThread统计维护快照。this.statsThread = new HandlerThread(STATS_THREAD_NAME, THREAD_PRIORITY_BACKGROUND); this.statsThread.start(); Utils.flushStackLocalLeaks(statsThread.getLooper()); this.handler = new StatsHandler(statsThread.getLooper(), this);
在Stats的构造函数中,创建了HandlerThread对象statsThread,并调用了其start方法用于开启线程,也就是开启了消息循环,因为在HandlerThread的run方法开头调用了Looper.prepare()用于绑定线程和looper,run方法结尾调用了Looper.loop()用于循环处理消息。
@Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
接着处理了一个Android 5中HandlerThread的问题,意思是HandlerThread总是在其线程栈中保持最后一个消息的引用,有可能导致最后一个消息的内存泄露。解决办法就是每秒给handlerThread的looper发送一个消息,这样最后一个消息的引用在HandlerThread栈中的引用并不会保持太久。
Utils.flushStackLocalLeaks(statsThread.getLooper()); /** * Prior to Android 5, HandlerThread always keeps a stack local reference to the last message * that was sent to it. This method makes sure that stack local reference never stays there * for too long by sending new messages to it every second. */ static void flushStackLocalLeaks(Looper looper) { Handler handler = new Handler(looper) { @Override public void handleMessage(Message msg) { sendMessageDelayed(obtainMessage(), THREAD_LEAK_CLEANING_MS); } }; handler.sendMessageDelayed(handler.obtainMessage(), THREAD_LEAK_CLEANING_MS); }
handlerThread对象创建并已经启动了,就这需要为其looper创建一个handler
this.handler = new StatsHandler(statsThread.getLooper(), this);
可以看到handler和statsThread的looper进行了绑定,因此handler的handleMessage消息处理方法会在statsThread的线程中执行。
handler是一个StatsHandler类型对象,继承自Handler
private static class StatsHandler extends Handler { ...
重写了Handler的handleMessage方法
@Override public void handleMessage(final Message msg) { switch (msg.what) { case CACHE_HIT: stats.performCacheHit(); break; case CACHE_MISS: stats.performCacheMiss(); break; case BITMAP_DECODE_FINISHED: stats.performBitmapDecoded(msg.arg1); break; case BITMAP_TRANSFORMED_FINISHED: stats.performBitmapTransformed(msg.arg1); break; case DOWNLOAD_FINISHED: stats.performDownloadFinished((Long) msg.obj); break; default: Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new AssertionError("Unhandled stats message." + msg.what); } }); } }
这样Stats消息处理的功能有了,那么发送消息的功能应该是Stats暴漏给外部的API
... void dispatchCacheHit() { handler.sendEmptyMessage(CACHE_HIT); } void dispatchCacheMiss() { handler.sendEmptyMessage(CACHE_MISS); } ...
这样外部在处理缓存的时候,如果缓存命中,就调用dispatchCacheHit()方法,缓存没命中就调用dispatchCacheMiss()方法,其他的解码、转换、下载成功等操作结果都会调用相应的方法,而这些方法均会给handler发送个消息,这些消息对应的任务就都会在handler所绑定的looper的线程中排队执行了。
Picasso快照功能的实现
快照就是维护某一时刻系统各项数据指标,方便获取某一时刻获取这些数据指标,在别的应用中可能还需要根据快照进行数据的恢复和容错。Picasso中的快照Stats主要用于统计维护Picasso的各种操作的数量,包括如下(命名很规范,顾名能思义):
long cacheHits; long cacheMisses; long totalDownloadSize; long totalOriginalBitmapSize; long totalTransformedBitmapSize; long averageDownloadSize; long averageOriginalBitmapSize; long averageTransformedBitmapSize; int downloadCount; int originalBitmapCount; int transformedBitmapCount;
以下载成功为例进行分析。
下载成功后,会调用 dispatchDownloadFinished(long size)方法,该方法会给handler发送一条Message消息,消息的what字段为DOWNLOAD_FINISHED,obj字段为下载成功的文件大小size
void dispatchDownloadFinished(long size) { handler.sendMessage(handler.obtainMessage(DOWNLOAD_FINISHED, size)); }
在消息处理回调中,会处理DOWNLOAD_FINISHED类型的消息
case DOWNLOAD_FINISHED: stats.performDownloadFinished((Long) msg.obj); break;
也就是调用了performDownloadFinished方法
void performDownloadFinished(Long size) { downloadCount++; totalDownloadSize += size; averageDownloadSize = getAverage(downloadCount, totalDownloadSize); } private static long getAverage(int count, long totalSize) { return totalSize / count; }
performDownloadFinished维护相应的数据变化,下载成功次数加1,已经下载的总大小加size,重新计算平均大小。
其他操作类似,一旦有操作结果的变化均会调用相应方法进行数据维护。
这样快照中的各项数据均得到及时维护,如果要获取某一时刻的快照,调用createSnapshot()方法即可。
StatsSnapshot createSnapshot() { return new StatsSnapshot(cache.maxSize(), cache.size(), cacheHits, cacheMisses, totalDownloadSize, totalOriginalBitmapSize, totalTransformedBitmapSize, averageDownloadSize, averageOriginalBitmapSize, averageTransformedBitmapSize, downloadCount, originalBitmapCount, transformedBitmapCount, System.currentTimeMillis()); }
这样就获取到了一个StatsSnapshot类型的对象,调用其dump方法即可将快照内容输出
/** Prints out this {@link StatsSnapshot} with the the provided {@link PrintWriter}. */ public void dump(PrintWriter writer) { writer.println("===============BEGIN PICASSO STATS ==============="); writer.println("Memory Cache Stats"); writer.print(" Max Cache Size: "); writer.println(maxSize); writer.print(" Cache Size: "); writer.println(size); writer.print(" Cache % Full: "); writer.println((int) Math.ceil((float) size / maxSize * 100)); writer.print(" Cache Hits: "); writer.println(cacheHits); writer.print(" Cache Misses: "); writer.println(cacheMisses); writer.println("Network Stats"); writer.print(" Download Count: "); writer.println(downloadCount); writer.print(" Total Download Size: "); writer.println(totalDownloadSize); writer.print(" Average Download Size: "); writer.println(averageDownloadSize); writer.println("Bitmap Stats"); writer.print(" Total Bitmaps Decoded: "); writer.println(originalBitmapCount); writer.print(" Total Bitmap Size: "); writer.println(totalOriginalBitmapSize); writer.print(" Total Transformed Bitmaps: "); writer.println(transformedBitmapCount); writer.print(" Total Transformed Bitmap Size: "); writer.println(totalTransformedBitmapSize); writer.print(" Average Bitmap Size: "); writer.println(averageOriginalBitmapSize); writer.print(" Average Transformed Bitmap Size: "); writer.println(averageTransformedBitmapSize); writer.println("===============END PICASSO STATS ==============="); writer.flush(); }
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories