フラグメントonCreateViewとonActivityCreatedが2回呼び出される


101

Android 4.0 ICSとフラグメントを使用してアプリを開発しています。

ICS 4.0.3(APIレベル15)APIのデモサンプルアプリからの次の変更例を検討してください。

public class FragmentTabs extends Activity {

private static final String TAG = FragmentTabs.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final ActionBar bar = getActionBar();
    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);

    bar.addTab(bar.newTab()
            .setText("Simple")
            .setTabListener(new TabListener<SimpleFragment>(
                    this, "mysimple", SimpleFragment.class)));

    if (savedInstanceState != null) {
        bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
        Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab"));
        Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number"));
    }

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
}

public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private final Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;
    private final Bundle mArgs;
    private Fragment mFragment;

    public TabListener(Activity activity, String tag, Class<T> clz) {
        this(activity, tag, clz, null);
    }

    public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        mArgs = args;

        // Check to see if we already have a fragment for this tab, probably
        // from a previously saved state.  If so, deactivate it, because our
        // initial state is that a tab isn't shown.
        mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
        if (mFragment != null && !mFragment.isDetached()) {
            Log.d(TAG, "constructor: detaching fragment " + mTag);
            FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
            ft.detach(mFragment);
            ft.commit();
        }
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
            Log.d(TAG, "onTabSelected adding fragment " + mTag);
            ft.add(android.R.id.content, mFragment, mTag);
        } else {
            Log.d(TAG, "onTabSelected attaching fragment " + mTag);
            ft.attach(mFragment);
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            Log.d(TAG, "onTabUnselected detaching fragment " + mTag);
            ft.detach(mFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show();
    }
}

public static class SimpleFragment extends Fragment {
    TextView textView;
    int mNum;

    /**
     * When creating, retrieve this instance's number from its arguments.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(FragmentTabs.TAG, "onCreate " + (savedInstanceState != null ? ("state " + savedInstanceState.getInt("number")) : "no state"));
        if(savedInstanceState != null) {
            mNum = savedInstanceState.getInt("number");
        } else {
            mNum = 25;
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Log.d(TAG, "onActivityCreated");
        if(savedInstanceState != null) {
            Log.d(TAG, "saved variable number: " + savedInstanceState.getInt("number"));
        }
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        Log.d(TAG, "onSaveInstanceState saving: " + mNum);
        outState.putInt("number", mNum);
        super.onSaveInstanceState(outState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(FragmentTabs.TAG, "onCreateView " + (savedInstanceState != null ? ("state: " + savedInstanceState.getInt("number")) : "no state"));
        textView = new TextView(getActivity());
        textView.setText("Hello world: " + mNum);
        textView.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
        return textView;
    }
}

}

この例を実行してから電話を回転させて取得した出力は次のとおりです。

06-11 11:31:42.559: D/FragmentTabs(10726): onTabSelected adding fragment mysimple
06-11 11:31:42.559: D/FragmentTabs(10726): onCreate no state
06-11 11:31:42.559: D/FragmentTabs(10726): onCreateView no state
06-11 11:31:42.567: D/FragmentTabs(10726): onActivityCreated
06-11 11:31:45.286: D/FragmentTabs(10726): onSaveInstanceState saving: 25
06-11 11:31:45.325: D/FragmentTabs(10726): onCreate state 25
06-11 11:31:45.340: D/FragmentTabs(10726): constructor: detaching fragment mysimple
06-11 11:31:45.340: D/FragmentTabs(10726): onTabSelected attaching fragment mysimple
06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate tab: 0
06-11 11:31:45.348: D/FragmentTabs(10726): FragmentTabs.onCreate number: 0
06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView state: 25
06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated
06-11 11:31:45.348: D/FragmentTabs(10726): saved variable number: 25
06-11 11:31:45.348: D/FragmentTabs(10726): onCreateView no state
06-11 11:31:45.348: D/FragmentTabs(10726): onActivityCreated

私の質問は、なぜonCreateViewとonActivityCreatedが2回呼び出されるのですか?保存された状態のバンドルで1回目、nullのsavedInstanceStateで2回目?

これにより、回転時にフラグメントの状態を保持する際に問題が発生します。


2
私はこの質問が関連することができると思うstackoverflow.com/a/8678705/404395
marioosh

回答:


45

私もしばらくこのことについて頭を悩ませていました。Daveの説明は少し理解しにくいので、(明らかに機能している)コードを投稿します。

private class TabListener<T extends Fragment> implements ActionBar.TabListener {
    private Fragment mFragment;
    private Activity mActivity;
    private final String mTag;
    private final Class<T> mClass;

    public TabListener(Activity activity, String tag, Class<T> clz) {
        mActivity = activity;
        mTag = tag;
        mClass = clz;
        mFragment=mActivity.getFragmentManager().findFragmentByTag(mTag);
    }

    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if (mFragment == null) {
            mFragment = Fragment.instantiate(mActivity, mClass.getName());
            ft.replace(android.R.id.content, mFragment, mTag);
        } else {
            if (mFragment.isDetached()) {
                ft.attach(mFragment);
            }
        }
    }

    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        if (mFragment != null) {
            ft.detach(mFragment);
        }
    }

    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}

ご覧のとおり、コンストラクターでデタッチしないこと、およびaddではなくreplaceを使用することを除けば、Androidサンプルとほとんど同じです。

多くのスクラッチと試行錯誤の後に、コンストラクターでフラグメントを見つけると、二重のonCreateView問題が魔法のように消えるように見えることがわかりました(私はActionBar.setSelectedNavigationItem()パスを介して呼び出されたときにonTabSelectedに対してnullになるだけだと思います状態の保存/復元)。


完璧に動作します!あなたは私の夜の眠りを救った!ありがとうございます:)
jaibatrik

また、クラス変数を削除し、呼び出しからパラメーターを削除する場合は、fragment.getClass()。getName()を使用することもできます
Ben Sewards

「以前のリファレンスTabListener」Androidサンプル-tnxで完全に動作します。最新のAndroid「TabListener ref。sample」[2013年4月6日現在]は本当に、本当に間違っています。
Grzegorz Dev

ft.commit()メソッド呼び出しはどこにありますか?
MSaudi 2013年

1
@ MuhammadBabar、stackoverflow.com / questions / 23248789 /…を参照してください。のadd代わりにを使用してreplace画面を回転させると、多くのフラグメントが発生しonCreateView()ます。
CoolMind 2018年

26

はい、わかりました。

私が理解していなかったことは、構成の変更が発生した(電話が回転する)ときにアクティビティに関連付けられているすべてのフラグメントが再作成され、アクティビティに追加されることです。(理にかなっています)

TabListenerコンストラクターで起こっていたのは、タブが見つかり、アクティビティにアタッチされた場合、タブが切り離されたことです。下記参照:

mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
    if (mFragment != null && !mFragment.isDetached()) {
        Log.d(TAG, "constructor: detaching fragment " + mTag);
        FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
        ft.detach(mFragment);
        ft.commit();
    }

アクティビティonCreateの後半で、以前に選択されたタブが、保存されたインスタンスの状態から選択されました。下記参照:

if (savedInstanceState != null) {
    bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
    Log.d(TAG, "FragmentTabs.onCreate tab: " + savedInstanceState.getInt("tab"));
    Log.d(TAG, "FragmentTabs.onCreate number: " + savedInstanceState.getInt("number"));
}

タブが選択されると、onTabSelectedコールバックで再アタッチされます。

public void onTabSelected(Tab tab, FragmentTransaction ft) {
    if (mFragment == null) {
        mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
        Log.d(TAG, "onTabSelected adding fragment " + mTag);
        ft.add(android.R.id.content, mFragment, mTag);
    } else {
        Log.d(TAG, "onTabSelected attaching fragment " + mTag);
        ft.attach(mFragment);
    }
}

添付されているフラグメントは、onCreateViewメソッドとonActivityCreatedメソッドの2番目の呼び出しです。(最初はシステムがアクティビティとすべての添付されたフラグメントを再作成しているときです)最初にonSavedInstanceState Bundleがデータを保存したが、2回目は保存しなかったはずです。

解決策は、TabListenerコンストラクターでフラグメントをデタッチせず、アタッチしたままにすることです。(タグによってFragmentManagerでそれを見つける必要があります)また、onTabSelectedメソッドで、アタッチする前にフラグメントがデタッチされているかどうかを確認します。このようなもの:

public void onTabSelected(Tab tab, FragmentTransaction ft) {
            if (mFragment == null) {
                mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs);
                Log.d(TAG, "onTabSelected adding fragment " + mTag);
                ft.add(android.R.id.content, mFragment, mTag);
            } else {

                if(mFragment.isDetached()) {
                    Log.d(TAG, "onTabSelected attaching fragment " + mTag);
                    ft.attach(mFragment);
                } else {
                    Log.d(TAG, "onTabSelected fragment already attached " + mTag);
                }
            }
        }

4
前述の「TabListenerコンストラクターでフラグメントを切り離さない」という解決策により、タブフラグメントが互いにオーバーラップします。他の断片の内容を見ることができます。私にはうまくいきません。
Aksel Fatih

@ flock.dux重複することの意味がわかりません。Androidはそれらがどのように配置されるかを処理するので、アタッチまたはデタッチを指定するだけです。もっと起こっているに違いない。コード例を使って新しい質問をすると、何が起こっているのかがわかります。
Dave、

1
同じ問題がありました(Androidからの複数のフラグメントコンストラクター呼び出し)。あなたの発見は私の問題を解決します:私が理解しなかったことは、構成の変更が発生した(電話が回転する)ときにアクティビティに関連付けられているすべてのフラグメントが再作成され、アクティビティに戻されることです。(これは理にかなっています)
eugene 14

26

フラグメントが1つしかない単純なアクティビティで同じ問題が発生しました(これは時々置き換えられます)次に、アクティビティではなくフラグメントでのみonSaveInstanceStateを使用し(savedInstanceStateをチェックするためにonCreateViewを使用)、気づきました。

デバイスをオンにすると、フラグメントを含むアクティビティが再開され、onCreatedが呼び出されます。そこで、必要なフラグメントをアタッチしました(これは最初の起動では正しい)。

デバイスでAndroidをオンにすると、最初に可視のフラグメントが再作成され、次にフラグメントがアタッチされているアクティビティのonCreateが呼び出され、元の可視のアクティビティが置き換えられました。

これを回避するには、savedInstanceStateを確認するようにアクティビティを変更します。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

if (savedInstanceState != null) {
/**making sure you are not attaching the fragments again as they have 
 been 
 *already added
 **/
 return; 
 }
 else{
  // following code to attach fragment initially
 }

 }

アクティビティのonSaveInstanceStateも上書きしませんでした。


ありがとうございました。AppCompatActivity + PreferenceFragmentCompatを使用すると、向きの変更後に設定フラグメントでダイアログを表示するときにクラッシュしました。これは、2番目のフラグメントの作成時にフラグメントマネージャーがnullだったためです。
RoK

12

ここでの2つの回答は、ナビゲーションモードNAVIGATION_MODE_TABSでのアクティビティの解決策を示していますが、同じ問題が発生しましたNAVIGATION_MODE_LIST。画面の向きが変わったときに、私のFragmentsはどういうわけかそれらの状態を失いました。これは本当に厄介でした。ありがたいことに、それらの有用なコードにより、私はなんとかそれを理解することができました。

基本的に、リストナビゲーションを使用する場合、「onNavigationItemSelected()is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment'sonCreateView()from being called twice, this initial automatic call toonNavigationItemSelected()should check whether the Fragment is already in existence inside your Activity. If it is, return immediately, because there is nothing to do; if it isn't, then simply construct the Fragment and add it to the Activity like you normally would. Performing this check prevents your Fragment from needlessly being created again, which is what causesonCreateView() `が2回呼び出されます!

onNavigationItemSelected()以下の私の実装を参照してください。

public class MyActivity extends FragmentActivity implements ActionBar.OnNavigationListener
{
    private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item";

    private boolean mIsUserInitiatedNavItemSelection;

    // ... constructor code, etc.

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState)
    {
        super.onRestoreInstanceState(savedInstanceState);

        if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM))
        {
            getActionBar().setSelectedNavigationItem(savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState)
    {
        outState.putInt(STATE_SELECTED_NAVIGATION_ITEM, getActionBar().getSelectedNavigationIndex());

        super.onSaveInstanceState(outState);
    }

    @Override
    public boolean onNavigationItemSelected(int position, long id)
    {    
        Fragment fragment;
        switch (position)
        {
            // ... choose and construct fragment here
        }

        // is this the automatic (non-user initiated) call to onNavigationItemSelected()
        // that occurs when the activity is created/re-created?
        if (!mIsUserInitiatedNavItemSelection)
        {
            // all subsequent calls to onNavigationItemSelected() won't be automatic
            mIsUserInitiatedNavItemSelection = true;

            // has the same fragment already replaced the container and assumed its id?
            Fragment existingFragment = getSupportFragmentManager().findFragmentById(R.id.container);
            if (existingFragment != null && existingFragment.getClass().equals(fragment.getClass()))
            {
                return true; //nothing to do, because the fragment is already there 
            }
        }

        getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).commit();
        return true;
    }
}

このソリューションのインスピレーションをここから借りました


この解決策は、ナビゲーションドロワーに関する同様の問題に対して機能します。IDで既存のフラグメントを見つけ、再作成する前に、新しいフラグメントと同じクラスがあるかどうかを確認します。
ウィリアム

8

それはあなたが毎回TabListenerをインスタンス化しているからだと私には思えます...そのため、システムはsavedInstanceStateからフラグメントを再作成していて、onCreateでもう一度それを実行しています。

これをにラップして、if(savedInstanceState == null)savedInstanceStateがない場合にのみ起動するようにする必要があります。


私はそれが正しいとは思いません。ifブロックでaddTabコードをラップすると、フラグメントがアクティビティに添付されますが、タブがありません。onCreateメソッドで毎回タブを追加する必要があるようです。私はこれを引き続き調査し、理解が深まったらさらに投稿します。
デイブ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.