google-code-prettify

2013-04-18

Fragmentでloaderを使っている場合は、setRetainInstance(true)してはいけない

FragmentでsetRetainInstance(true)としていると、loaderのonLoadFinished()が呼ばれないことがある。
そのため、loaderを使っている場合は、setRetainInstance(true)としてはいけない。

自作アプリでloaderとsetRetainInstance(true)を混ぜて使っていて、なぜかonLoadFinished()が呼ばれないことがあるのでググってみたら、Dianne Hackborn(Googleの人)もloaderとsetRetainInstance()を混ぜて使うなと言っていた。

以下の方法で、「onLoadFinished()が呼ばれない場合」を再現できる(GalaxyNexus + JellyBeanで確認)。

ソースコード

Googleによるサンプルコードに従った書き方をしている。onActivityCreated()でinitLoader()をし、onLoaderReset()でswapCursor(null)している。

public class MyFragment extends ListFragment implements LoaderManager.LoaderCallbacks {

    private MyAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        adapter = new MyAdapter(getActivity());
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        setListAdapter(adapter);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        setListAdapter(null);
    }

    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        return new CursorLoader(getActivity(), ...);
    }

    @Override
    public void onLoadFinished(Loader loader, Cursor data) {
        adapter.swapCursor(data);
    }

    @Override
    public void onLoaderReset(Loader loader) {
        adapter.swapCursor(null);
    }
}

手順

  1. 携帯を横(landscape)にしておく。この状態ではListFragmentにCursorの中身が表示されている。
  2. 電源ボタンを押して、スリープに入れる。
  3. 携帯を縦に持ちかえる。電源ボタンを押してスリープを解除し、ロックも解除する。すると、ListFragmentの表示が空になっている。

何が起きているのか

電源ボタンを押してスリープに入れた時に、以下の順でcallbackが呼ばれる。

  1. onLoaderReset
  2. onDestroyView
  3. onViewCreated
  4. onActivityCreated

ロック画面が縦だからだと思うのだが、landscapeのActivityが破棄され、portlaitのActivityが再生成される。 onLoaderReset()は呼ばれているが、Activity再生成後のonLoaderFinish()は呼ばれていない。そのため、スリープとロックを解除すると、ListFragmentが空になっている。

対処方法

setRetainInstance(true)を呼ばない、というのが一番確実な対処方法だが、onActivityCreatedでinitLoader()ではなくrestartLoader()を呼ぶという方法でも、試した限り動作するようだ。ただしrestartLoader()を使う方法だと、多少無駄にqueryを行うことになる。

0 件のコメント: