postメソッドを使うときのPauseHandler実装の注意点

# TL;DR

* `PauseHandler#post`メソッドを使う場合は`handleMessage`メソッドではなく`dispatchMessage`をoverrideする
* 最終的な実装はgistに置いた：[PauseHandler for Handler#post method](https://gist.github.com/quwac/a1d7108921f224bf75ddbbc4fe1f34cf)

# PauseHandlerとは

Fragmentのライフサイクルでは`onSaveInstanceState`以降にFragmentを触ろうとすると`IllegalStateException`が送出されます。
これは、GUIの状態を一時保管するタイミングである`onSaveInstanceState`よりも後にFragmentの状態を変えるために起こります。
アプリがバックグラウンドに回るとUIスレッドは動作を中断しますが、ユーザーが好き勝手に作ったワーカースレッドは特別に操作をしない限り動き続けます。
したがってワーカースレッドからUIスレッドにGUIの更新を要求するアプリだと上記の状況は容易に起こりえます。

これを解決するためには以下のどちらかの処置が必要です。

1. `onSaveInstanceState`が呼ばれた後のUIスレッドへの要求は破棄する
2. `onSaveInstanceState`が呼ばれた後のUIスレッドへの要求はキューに溜めておいて、`onRestoreInstanceState`で復帰した後にキューから取り出して受理する

つまり、捨てるか溜めるかですね。

上記の処置は`Fragment`クラスに実装してもよいですが、UIスレッドへの要求には通常`Handler`クラスを使うことを考えると`Handler`クラスを継承した独自クラスに実装する方が筋が良いといえます。

この独自クラスは俗にPauseHandlerと呼ばれています。

[android - How to handle Handler messages when activity/fragment is paused - Stack Overflow](https://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused)

このPauseHandlerを使用するFragmentクラスでは、`PauseHandler`に`onSaveInstanceState`/`onRestoreInstanceState`のタイミングを知らせるために、`onPause`で`PauseHandler#pause`を、`onResume`で`PauseHandler#resume`を呼び出しておきます[^1]。
この呼び出しさえしておけば、あとは`PauseHandler`に対して通常どおり`sendMessage`を呼び出すだけで`PauseHandler`の中で適切に上述1または2の処置を実行してくれるのです[^2]。

**……`sendMessage`だと？**

[^1]: 「おいおい、`onSaveInstanceState`/`onRestoreInstanceState`じゃなくて`onPause/onResume`で呼ぶってどういうことだよ？　誤記か？」と思われるかもしれませんが、誤記にあらず。`onRestoreInstanceState`はライフサイクルの中で常に呼ばれるコールバックではありません。pauseの設定/解除の確実な状態遷移とタイミングの対称性を保つために、`onSaveInstanceState`/`onRestoreInstanceState`の前後にあり、かつライフサイクルの中で確実に呼び出される`onPause/onResume`を使用しているのです。

[^2]: `PauseHandler#storeMessage`の戻り値が`false`であれば1、`true`であれば2を実行します。

# オリジナルの`PauseHandler`の問題点

`Handler`への要求の出し方は大きく2つあります。
`Message`インスタンスを渡し、別途設けたコールバックで要求内容の中身を把握しながら要求を実行する`Handler#sendMessage`系メソッド[^3]を使う方法と、`Runnable`インスタンスを渡して要求内容の中身を見ずに実行する`Handler#post`系メソッド[^3]を使う方法です。

StackOverflowのPauseHandler実装は`Handler#sendMessage`系メソッドには対応しているものの、**`Handler#post`系メソッドには対応していません**。
実際に`PauseHandler#post`を実行してみると、Fragmentの`onPause`が呼び出された後であってもアクション（引数に指定したRunnableインスタンス）は破棄されずキューにも溜められず、通常どおりUIスレッドで実行されてしまいます。

それもそのはず。
StackOverflowのPauseHandler実装は`Handler#handleMessage`をoverrideすることで破棄またはキューへの積み込みを実現しているのですが、このメソッドが呼ばれるのは`Handler#sendMessage`系メソッドだけで`Handler#post`系メソッドでは呼ばれません。

[^3]: どちらのメソッドにも指定した時刻に実行する`*AtTime`メソッドと、指定した時間だけ遅延させてから実行する`*delayed`メソッドがある。

# 解決方法

解決方法は単純明快。
**`Handler#handleMessage`ではなく`Handler#dispatchMessage`をoverride**すればよいのです。

`Handler`クラスの実装を見てみるとわかりますが、`Handler#dispatchMessage`は`Handler#sendMessage`と`Handler#post`のどちらの場合でも呼び出されます。`Handler#dispatchMessage`はsendMessage経由から呼ばれたかpost経由で呼ばれたかの振り分けを行っており、呼び出し元が`Handler#sendMessage`だった場合は`Handler#handleMessage`を呼び、呼び出し元が`Handler#post`だった場合は`Runnable`インスタンスの`Runnable#run`メソッドを呼ぶ仕組みになっています。
よって`Handler#dispatchMessage`に処理を介入させられればどちらの場合でも対応できるのです。

修正した私家版PauseHandlerのコードを以下のとおりです。
ただし、簡単のため、1, 2の処置の切り換えを実現していた抽象メソッド`storeMessage`は廃し、単純にコンストラクタに引数で与える形にしてあります。

```[java]
public class PauseHandler extends Handler {
    private boolean mIsPause;
    private boolean mStoresMessages;
    private final Queue<Message> mQueue = new LinkedList<>();

    public PauseHandler(boolean storesMessages) {
      super();
      mStoresMessages = storesMessages
    }

    public PauseHandler(Callback callback, boolean storesMessages) {
      super(callback);
      mStoresMessages = storesMessages
    }

    public void resume() {
        mIsPause = false;

        while (!mQueue.isEmpty()) {
            Message msg = mQueue.poll();
            if (msg != null) {
                sendMessage(msg);
            }
        }
    }

    public void pause() {
        mIsPause = true;
    }

    @Override
    public void dispatchMessage(Message msg) {
        if (mIsPause) {
            if (mStoresMessages) {
                Message copied = Message.obtain(msg);
                mQueue.offer(copied);
            }
        } else {
            super.dispatchMessage(msg);
        }
    }
}

```

本当に単純です。
違いはoverrideの対象が`handleMessage`メソッドから`dispatchMessage`メソッドになったことと、`processMessage`がなくなった代わりに親クラスの`dispatchMessage`メソッドを呼ぶようになったことだけです。
このクラスを使用すれば`post`系メソッドであっても上述の1または2の処置どおり期待どおりに動作します。

```[java]
public class AFragment extends Fragment {
    
    // ...

    private PauseHandler handler = new PauseHandler(true);

    // ...

    public void aMethod() {
        // ...
        handler.post(new Runnable() {
            @Override
            public void run() {
                // UIスレッドで実行したい処理。
                // 普通のHandler#postを使う場合と使い方に違いはない。
            }
        });
        // ...
    }
}
```

もちろん、`sendMessage`系メソッドもオリジナルの`PauseHandler`同等に動作します。オリジナルの`PauseHandler`では抽象メソッド`processMessage`がコールバックインタフェースになっていましたが、修正版の`PauseHandler`では`handleMessage`を直接、コールバックにしてしまえばよいです。

```[java]
public class AFragment extends Fragment {
    
    // ...

    private PauseHandler handler = new PauseHandler(true) {
        @Override
        public void handleMessage(Message msg) {
            // 以前はprocessMessageに書いていた処理をここに書く。
        }
    };
}
```

もしくは`Callback`インタフェースを継承したインスタンスを与えてもよいでしょう[^4]。

```[java]
public class AFragment extends Fragment {
    
    // ...

    private PauseHandler handler = new PauseHandler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // 以前はprocessMessageに書いていた処理をここに書く。
            return true;
        }
    }, true);
}
```

上記を使いやすいようにリファクタした最終形はgistに置いたコードを参照してください。Android Studioが自動変換で作ってくれたKotlin版のコードもあります。

[PauseHandler for Handler#post method](https://gist.github.com/quwac/a1d7108921f224bf75ddbbc4fe1f34cf)

[^4]: というよりこれがHandlerでMessageを取り扱う際の推奨されるやり方だ。

# おわりに、あるいは記事を書いた動機

Handlerを使うときはsendMessageよりpostを使うことの方が多いんじゃないかなって思うんですけど、PauseHandlerでググってもdispatchMessageをoverrideするやり方は見つからなかったです。
ので、この記事が誰かの参考になれば嬉しいです。