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

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();
  }

猜你喜欢

转载自blog.csdn.net/shihui512/article/details/51655903
今日推荐