您的位置:首页 > 移动开发 > Android开发

Picasso源码分析(三):快照功能实现和HandlerThread的使用

2016-06-13 10:35 736 查看
Picasso源码分析(一):单例模式、建造者模式、面向接口编程

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方法后再获取其looper

public 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();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 源码