首页 文章

片段onCreateView和onActivityCreated调用了两次

提问于
浏览
86

我正在使用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调用了两次?第一次使用Bundle保存状态,第二次使用null savedInstanceState?

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

5 回答

  • 12

    我也在讨论这个问题一段时间了,因为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) {
        }
    }
    

    正如你所看到的,它非常类似于Android示例,除了不在构造函数中分离,并使用 replace instead of add .

    在经过多次头部划分和反复试验之后,我发现在构造函数中找到片段似乎会使双重onCreateView问题神奇地消失(我认为当通过ActionBar.setSelectedNavigationItem()路径调用时,onTabSelected最终会为null . 保存/恢复状态) .

  • 5

    好的,这是我发现的 .

    我不明白的是,当配置更改发生(电话旋转)时附加到活动的所有片段都会重新创建并添加回活动 . (这很有意义)

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

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

    我有一个简单的Activity只带有一个片段(有时会被替换)的问题 . 然后我意识到我只在片段中使用onSaveInstanceState(并且在onCreateView中检查savedInstanceState),而不是在活动中 .

    在设备转向时,包含片段的活动将重新启动并调用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 .

  • 15

    这里的两个upvoted答案显示了导航模式 NAVIGATION_MODE_TABS 的活动的解决方案,但我与 NAVIGATION_MODE_LIST 有同样的问题 . 当屏幕方向改变时,它导致我的碎片莫名其妙地失去状态,这真的很烦人 . 值得庆幸的是,由于他们有用的代码,我设法弄清楚了 .

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

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

    我从here借用了这个解决方案的灵感 .

  • 43

    它看起来像是因为你每次都在实例化你的TabListener ...所以系统正在从savedInstanceState重新创建你的片段然后你在onCreate中再次这样做 .

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

相关问题