简体   繁体   English

片段onCreateView和onActivityCreated调用了两次

[英]Fragment onCreateView and onActivityCreated called twice

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? 我的问题是,为什么onCreateView和onActivityCreated调用了两次? The first time with a Bundle with the saved state and the second time with a null savedInstanceState? 第一次使用Bundle保存状态,第二次使用null savedInstanceState?

This is causing problems with retaining the state of the fragment on rotation. 这导致在旋转时保持片段状态的问题。

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示例,除了不在构造函数中分离,并使用替换而不是添加

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)保存/恢复状态)。

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. TabListener构造函数中发生的事情是,如果找到并附加到活动,则选项卡已分离。 See below: 见下文:

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. 稍后在活动onCreate中,从保存的实例状态中选择了先前选择的选项卡。 See below: 见下文:

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. 附加的片段是对onCreateView和onActivityCreated方法的第二次调用。 (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. (第一个是当系统重新创建活动和所有附加的碎片时)onSavedInstanceState Bundle第一次保存数据而不是第二次保存数据。

The solution is to not detach the fragment in the TabListener constructor, just leave it attached. 解决方案是不要在TabListener构造函数中分离片段,只需将其保留为附加。 (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. (你仍然需要通过它的标签在FragmentManager中找到它)另外,在onTabSelected方法中,我检查片段是否在我附加之前被分离。 Something like this: 像这样的东西:

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

I have had the same problem with a simple Activity carrying only one fragment (which would get replaced sometimes). 我有一个简单的Activity只带有一个片段(有时会被替换)的问题。 I then realized I use onSaveInstanceState only in the fragment (and onCreateView to check for savedInstanceState), not in the activity. 然后我意识到我只在片段中使用onSaveInstanceState(并且在onCreateView中检查savedInstanceState),而不是在活动中。

On device turn the activity containing the fragments gets restarted and onCreated is called. 在设备转向时,包含片段的活动将重新启动并调用onCreated。 There I did attach the required fragment (which is correct on the first start). 在那里,我确实附加了所需的片段(在第一次启动时是正确的)。

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。

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 . 这里有两个upvoted答案显示了导航模式NAVIGATION_MODE_TABS的活动解决方案,但我遇到了与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. 值得庆幸的是,由于他们有用的代码,我设法弄清楚了。

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's 基本上,当使用列表导航is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's `onNavigationItemSelected() is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's onCreateView() from being called twice, this initial automatic call to onNavigationItemSelected() 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 causes is automatically called when your activity is created/re-created, whether you like it or not. To prevent your Fragment's onCreateView() from being called twice, this initial automatic call to onNavigationItemSelected()的from being called twice, this initial automatic call to 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 causes 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 causes 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 causes onCreateView()` to be called twice! 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 causes onCreateView()`被调用两次的原因!

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 . 我从这里借用了这个解决方案的灵感。

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)这样只有在没有savedInstanceState的情况下它才会触发。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用片段两次调用onCreateView,onActivityCreated和onStart两次 - onCreateView, onActivityCreated and onStart called twice, using fragments 碎片onCreateView和onActivityCreated未在旋转时调用 - Fragment onCreateView and onActivityCreated not being called on rotation OnCreateView Fragment调用了两次 - OnCreateView Fragment called twice 为什么调用片段onCreateView,onCreate,onActivityCreated - Why does the fragment's onCreateView, onCreate, onActivityCreated are called Android片段OnCreateView调用了两次 - Android fragment OnCreateView called twice 片段Recyclerview onCreateView,onViewCreated或onActivityCreated? - Fragment Recyclerview onCreateView, onViewCreated or onActivityCreated? Fragment 中的按钮 setOnClickListener onCreateView() 或 onActivityCreated() - Button setOnClickListener onCreateView() or onActivityCreated() in Fragment 为什么片段onCreateView被调用两次? - Why fragment onCreateView being called twice? Android:再次删除()Fragment-&gt; add()new同一类的Fragment-&gt;不调用onCreateView和onActivityCreated吗? - Android: remove() Fragment--> add() new Fragment of same class again ->onCreateView and onActivityCreated not called? ListFragment和onActivityCreated调用了两次 - ListFragment and onActivityCreated called twice
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM