Android 片段 onCreateView 和 onActivityCreated 调用了两次

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10983396/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 05:39:25  来源:igfitidea点击:

Fragment onCreateView and onActivityCreated called twice

androidandroid-fragmentsandroid-4.0-ice-cream-sandwich

提问by Dave

I'm developing an app using Android 4.0 ICS and fragments.

我正在使用 Android 4.0 ICS 和片段开发应用程序。

Consider this modified example from the ICS 4.0.3 (API level 15) API's demo example app:

考虑来自 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;
    }
}

}

}

Here is the output retrieved from running this example and then rotating the phone:

这是从运行此示例然后旋转手机检索到的输出:

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

My question is, why is the onCreateView and onActivityCreated called twice? The first time with a Bundle with the saved state and the second time with a null savedInstanceState?

我的问题是,为什么 onCreateView 和 onActivityCreated 被调用两次?第一次使用具有保存状态的捆绑包,第二次使用空的savedInstanceState?

This is causing problems with retaining the state of the fragment on rotation.

这会导致在旋转时保持片段状态的问题。

采纳答案by Dave

Ok, Here's what I found out.

好的,这是我发现的。

What I didn't understand is that all fragments that are attached to an activity when a config change happens (phone rotates) are recreated and added back to the activity. (which makes sense)

我不明白的是,当发生配置更改(手机旋转)时,附加到活动的所有片段都会重新创建并添加回活动。(这是有道理的)

What was happening in the TabListener constructor was the tab was detached if it was found and attached to the activity. See below:

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

Later in the activity onCreate the previously selected tab was selected from the saved instance state. See below:

稍后在 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"));
}

When the tab was selected it would be reattached in the onTabSelected callback.

选择选项卡后,它将重新附加到 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);
    }
}

The fragment being attached is the second call to the onCreateView and onActivityCreated methods. (The first being when the system is recreating the acitivity and all attached fragments) The first time the onSavedInstanceState Bundle would have saved data but not the second time.

附加的片段是对 onCreateView 和 onActivityCreated 方法的第二次调用。(第一次是在系统重新创建活动和所有附加片段时)第一次 onSavedInstanceState Bundle 会保存数据,但不会第二次。

The solution is to not detach the fragment in the TabListener constructor, just leave it attached. (You still need to find it in the FragmentManager by it's tag) Also, in the onTabSelected method I check to see if the fragment is detached before I attach it. Something like this:

解决方案是不要在 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);
                }
            }
        }

回答by Staffan

I was scratching my head about this for a while too, and since Dave's explanation is a little hard to understand I'll post my (apparently working) code:

我也为此苦恼了一段时间,由于 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) {
    }
}

As you can see it's pretty much like the Android sample, apart from not detaching in the constructor, and using replace instead of add.

正如您所看到的,它与 Android 示例非常相似,除了不在构造函数中分离,并且使用replace 而不是 add

After much headscratching and trial-and-error I found that finding the fragment in the constructor seems to make the double onCreateView problem magically go away (I assume it just ends up being null for onTabSelected when called through the ActionBar.setSelectedNavigationItem() path when saving/restoring state).

经过大量的头疼和反复试验,我发现在构造函数中找到片段似乎使双重 onCreateView 问题神奇地消失了(我假设当通过 ActionBar.setSelectedNavigationItem() 路径调用时,它最终为 onTabSelected 为 null保存/恢复状态)。

回答by Gunnar Bernstein

I have had the same problem with a simple Activity carrying only one fragment (which would get replaced sometimes). I then realized I use onSaveInstanceState only in the fragment (and onCreateView to check for savedInstanceState), not in the activity.

我在一个仅携带一个片段(有时会被替换)的简单 Activity 中遇到了同样的问题。然后我意识到我只在片段中使用 onSaveInstanceState(和 onCreateView 来检查savedInstanceState),而不是在活动中。

On device turn the activity containing the fragments gets restarted and onCreated is called. There I did attach the required fragment (which is correct on the first start).

在设备上,包含片段的活动将重新启动并调用 onCreated。在那里我确实附加了所需的片段(第一次开始时是正确的)。

On the device turn Android first re-created the fragment that was visible and then called onCreate of the containing activity where my fragment was attached, thus replacing the original visible one.

在设备上,Android 首先重新创建可见的片段,然后调用我的片段所附加的包含活动的 onCreate,从而替换原来的可见片段。

To avoid that I simply changed my activity to check for savedInstanceState:

为了避免这种情况,我只是将我的活动更改为检查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
 }

 }

I did not even Overwrite onSaveInstanceState of the activity.

我什至没有覆盖活动的 onSaveInstanceState。

回答by X?pplI'-I0llwlg'I -

The two upvoted answers here show solutions for an Activity with navigation mode NAVIGATION_MODE_TABS, but I had the same issue with a NAVIGATION_MODE_LIST. It caused my Fragments to inexplicably lose their state when the screen orientation changed, which was really annoying. Thankfully, due to their helpful code I managed to figure it out.

此处的两个赞成的答案显示了具有导航模式的 Activity 的解决方案NAVIGATION_MODE_TABS,但我对NAVIGATION_MODE_LIST. 当屏幕方向改变时,它导致我的 Fragments 莫名其妙地失去了它们的状态,这真的很烦人。谢天谢地,由于他们有用的代码,我设法弄明白了。

Basically, when using a list navigation, ``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()` to be called twice!

基本上,当使用列表导航时,``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()` 会被调用两次!

See my onNavigationItemSelected()implementation below.

请参阅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;
    }
}

I borrowed inspiration for this solution from here.

我从这里借用了这个解决方案的灵感。

回答by Barak

It looks to me like it's because you are instantiating your TabListener every time... so the system is recreating your fragment from the savedInstanceState and then you are doing it again in your onCreate.

在我看来,这是因为您每次都在实例化您的 TabListener ……所以系统正在从savedInstanceState 重新创建您的片段,然后您在 onCreate 中再次执行此操作。

You should wrap that in a if(savedInstanceState == null)so it only fires if there is no savedInstanceState.

您应该将if(savedInstanceState == null)它包装在 a 中,这样它只会在没有savedInstanceState 时触发。