Fragment使用時のIllegalStateException回避

# 前置き
このページの内容は[stackoverflowの素晴らしい回答](http://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused)を参考にしている。

# IllegalStateException?
Fragmentを使っていると、画面遷移の際に以下の様が例外が発生することがある。

````java
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
````

これは非同期処理などでFragmentの操作タイミングが`onSaveInstanceState()`よりも後になってしまった場合に発生するものだ。
`onSaveInstanceState()`ではFragmentの状態も含めた画面情報を保存するため、それ以降のタイミングでFragmentを操作するとおかしくなるぞ！ということだろう(多分)。
詳しいことは(解決策も)下記リンクに詳しくまとめられているので、じっくり読んで欲しい。
http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

# commitAllowingStateLoss？
`FragmentTransaction.commit()`する代わりに`FragmentTransaction.commitAllowingStateLoss()`することでこの例外は発生しなくなる。
これは文字通り「画面状態が失われてもいいからコミット」ということで、この時のコミット内容は`onSaveInstanceState()`で保存されなくなる。
Activityが再生成されたりすると、そのFragmentの状態が正しく復元されなくなるということだ。
これでは弊害も大きくなってしまうし、`FragmentManager.popBackStack()`のような処理にはそもそもAllowingStateLossなメソッドが存在しない。

# じゃあどうするの？
この問題を根本的に回避するには、`onSaveInstanceState()`より後、savedInstanceStateから復元されるまでの間にFragmentを操作しなければ良い。
`onSaveInstanceState()`は`onPause()`/`onStop()`の直後に発生するため、`onPause()`から`onResume()`の間に操作しなければ良い、ということになる。
つまり、`onPause()`より後のFragment操作リクエストは`onResume()`以降で反映されるようにタイミングをずらそうということになる。

# 実装

[stackoverflowにスゴク綺麗なのが書いてあった](http://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused)
↑これ見てやれば間違いない

間違いないのだが、若干わかりにくいかな？と思ったのでサンプルプロジェクトをgithubに置いてみた。
https://github.com/nein37/FragmentHandlerDemo

stackoverflowの例では、Handlerを利用するActivity/Fragmentそれぞれによって実装し直す必要があったが、
サンプル内では`BaseHandlerFragment`がHandlerによってFragment操作タイミングをずらす機能を実装している。
これによって、Handlerの存在を意識せずに`sendMessage`と`processMessage`の実装だけで良くなる。

このクラスを継承している場合、非同期処理からのFragment操作は以下のようになる。

````java
Bundle bundle = new Bundle();
// 対象フラグメントに渡したいパラメータはこのタイミングで設定できる
bundle.putString(ToFragment.BUNDLE_TOAST_TEXT,"hogehogehogehoge");
// Fragment操作処理を登録
// このタイミングで直接Fragment操作を行うと落ちる場合がある
sendMessage(WHAT_REPLACE_FRAGMENT, bundle);
````


`sendMessage()`が`onPause()`より前に呼ばれた場合、`BaseHandlerFragment.processMessage()`が即座に実行される。
`onPause()`より後に呼ばれた場合は次に`onResume()`が呼ばれた直後にこのメソッドが実行されることになる。
`Message.what`によって対象操作の判別、`Message.getDate()`によってパラメータの受け渡しを行っている。

````java
    @Override
    public void processMessage(Message message) {
        switch (message.what) {
            case WHAT_REPLACE_FRAGMENT:
                // bundleも取り出す
                ToFragment fragment = new ToFragment();
                fragment.setArguments(message.getData());
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.replace(getId(), fragment);
                ft.addToBackStack("hoge");
                ft.commit();
                break;
            default:
                break;
        }
    }
````

`processMessage()`の中には`FragmentTransaction.commit()`に限らず`FragmentManager.popBackStack()`も書くことができるため、非同期処理から発生するFragment操作をすべてここに集約することで`IllegalStateException`を回避することができる。
Fragment操作のタイミングをずらせれば良いのでこの通り実装する必要はないが、[元記事](http://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused)の`Handler`と`Message`によるタイミングのずらし方が美しいのでぜひ参考にしてほしい。
そして、長々と書いたがそれを隠蔽しようとするこのサンプルはクソだ。

# まとめ
Fragment最高。[stackoverflowの実装](http://stackoverflow.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused)は美しい。このページとサンプルはクソ。