Androidの主要画像系ライブラリのTread Poolをみる

Androidで画像の非同期読み込みするスレッドを管理をしたくなったので、参考に調べた主要な画像系ライブラリのTread Pool周りのメモです。
調べ方は各ライブラリのレポジトリで`Executor`、`ThreadPoolExecutor `などで検索してヒットしたものを載せています。（詳しく読み込んでいないので、細かい動作による初期値の変化についてはあまり触れません）

**※調べたのはExecutorを自分で設定しない場合のデフォルトの値です。**

今回調べた対象は以下のライブラリです。

- [picasso](http://square.github.io/picasso/)
- [glide](https://github.com/bumptech/glide)
- [fresco](http://frescolib.org/)

## 早見表

比較用に`AsyncTask`も調べました。

|   |picasso|glide|fresco|AsyncTask|
|:-:|:-----:|:---:|:----:|:-------:|
|core pool size|1~4|１~ CPU数 |3|2~4|
|maximum pool size|同上|同上|同上|1~ (CPU数 * 2 + 1)|
|work queue size|11|Integer.MAX_VALUE|Integer.MAX_VALUE|128|
|queue|PriorityBlockingQueue|PriorityBlockingQueue|LinkedBlockingQueue|LinkedBlockingQueue|
_CPU数は`Runtime.getRuntime().availableProcessors()`の値です_

上の表だけでは説明が不十分なのでそれぞれ少しだけ補足します。

## picasso

参照クラスはこちら
[PicassoExecutorService](https://github.com/square/picasso/blob/303366b749b2b58ea3a1561ff64e093941ebfbbd/picasso/src/main/java/com/squareup/picasso/PicassoExecutorService.java)
picassoはデフォルトでは3ですが、WiFiでは4にするなど通信状況に応じてpool sizeを変更しています。

```java:PicassoExecutorService.java

private static final int DEFAULT_THREAD_COUNT = 3;

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

```

## Glide

参照クラスはこちら
[FifoPriorityThreadPoolExecutor.java](https://github.com/bumptech/glide/blob/v3.7.0/library/src/main/java/com/bumptech/glide/load/engine/executor/FifoPriorityThreadPoolExecutor.java)
[GlideBuilder.java](https://github.com/bumptech/glide/blob/3.0/library/src/main/java/com/bumptech/glide/GlideBuilder.java)

3系以前は`GlideExecutor`というクラスでしたが、`FifoPriorityThreadPoolExecutor`に置き換わっています。

```java:FifoPriorityThreadPoolExecutor.java
public FifoPriorityThreadPoolExecutor(int poolSize) {
        this(poolSize, UncaughtThrowableStrategy.LOG);
}

public FifoPriorityThreadPoolExecutor(int poolSize, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
        this(poolSize, poolSize, 0, TimeUnit.MILLISECONDS, new DefaultThreadFactory(),
            uncaughtThrowableStrategy);
}

public FifoPriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAlive, TimeUnit timeUnit,
            ThreadFactory threadFactory, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
        super(corePoolSize, maximumPoolSize, keepAlive, timeUnit, new PriorityBlockingQueue<Runnable>(), threadFactory);
        this.uncaughtThrowableStrategy = uncaughtThrowableStrategy;
}
```

コンストラクタはこのようになっており、`poolSize　==　maximumPoolSize`になるようですね。
このコンストラクタを呼び出しているのは`GlideBuilder`です。

```java:GlideBuulder.java
…
Glide createGlide() {
        if (sourceService == null) {
            final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
            sourceService = new FifoPriorityThreadPoolExecutor(cores);
        }
        if (diskCacheService == null) {
            diskCacheService = new FifoPriorityThreadPoolExecutor(1);
        }
        …
        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
}
```

`sourceService`が画像読み込みに使われるプールです。よってここでは1~ availableProcessorsの数ですね。

## fresco

参照クラスはこちら
[HttpUrlConnectionNetworkFetcher.java](https://github.com/facebook/fresco/blob/84aeab6895f3177cee473e85656cf898112f263f/imagepipeline/src/main/java/com/facebook/imagepipeline/producers/HttpUrlConnectionNetworkFetcher.java)

frescoではファイルIOやデコード、バックグラウンド処理など細かくExecutorを分けており、探すのが大変でした…これが正確でない場合はコメントいただきたいです。スレッド数以外は特に指定していないので`ExecutorService`の`newFixedThreadPool`で指定される値が入るようです。

```java:HttpUrlConnectionNetworkFetcher.java
private static final int NUM_NETWORK_THREADS = 3;

public HttpUrlConnectionNetworkFetcher() {
    this(Executors.newFixedThreadPool(NUM_NETWORK_THREADS));
  }

  @VisibleForTesting
  HttpUrlConnectionNetworkFetcher(ExecutorService executorService) {
    mExecutorService = executorService;
}
```

## AsyncTask

**※APIレベルによって挙動が異なるので詳しくはドキュメントを読んでください**
https://developer.android.com/reference/android/os/AsyncTask.html

```java:AsyncTask.java
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
```

実装を見る限りとても便利ですが、使用する上でいくつか注意点はあります。
@hkurokawa さんのこちら資料が参考になりました。
http://www.slideshare.net/hiroshikurokawa79/asynctask

---

参考になる記事を見つけたら貼ります
[モバイルアプリのスレッドプールサイズの最適化（画像読み込み編）](http://techlife.cookpad.com/entry/2015/12/21/110416)