Android 片段onCreateView和onActivityCreated调用了两次
我正在开发一款使用安卓4.0 ICS和碎片的应用程序 考虑ICS 4.0.3(API级别15)API演示示例应用程序中的修改示例:Android 片段onCreateView和onActivityCreated调用了两次,android,android-fragments,android-4.0-ice-cream-sandwich,Android,Android Fragments,Android 4.0 Ice Cream Sandwich,我正在开发一款使用安卓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) {
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;
}
}
我的问题是,为什么onCreateView和onActivityCreated调用了两次?第一次使用保存状态的捆绑包,第二次使用空的savedInstanceState
这会导致在旋转时保留片段的状态出现问题。在我看来,这是因为您每次都在实例化TablListener。。。因此,系统正在从savedInstanceState重新创建片段,然后在onCreate中再次执行该操作
如果(savedInstanceState==null),您应该将其包装在
中
,这样它只有在没有savedInstanceState的情况下才会触发。好的,下面是我的发现
我不明白的是,当配置发生更改(手机旋转)时,附加到活动的所有片段都会重新创建并添加回活动。(这是有道理的)
TablListener构造函数中发生的事情是,如果找到选项卡并将其附加到活动,则该选项卡被分离。见下文:
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();
}
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"));
}
稍后在“创建”活动中,从保存的实例状态中选择了先前选择的选项卡。见下文:
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();
}
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"));
}
选择该选项卡后,它将在OnAbsSelected回调中重新附着
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捆绑包第一次会保存数据,但第二次不会
解决方案是不要分离TablListener构造函数中的片段,只需将其保持连接即可。(您仍然需要在FragmentManager中通过它的标记找到它)另外,在OnAbsSelected方法中,我检查片段是否已分离,然后再附加它。大概是这样的:
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);
}
}
}
我对此也挠头了一段时间,由于Dave的解释有点难以理解,我将发布我的(显然是工作的)代码:
私有类TabListener实现ActionBar.TabListener{
私人碎片;
私人活动能力;
私有最终字符串mTag;
私人期末班;
公共TabListener(活动、字符串标记、类clz){
活动性=活动性;
mTag=标签;
mClass=clz;
MFFragment=mActivity.getFragmentManager().findFragmentByTag(mTag);
}
已选择的公共事务(选项卡,碎片事务ft){
if(mFragment==null){
MFFragment=Fragment.instantiate(mActivity,mClass.getName());
ft.replace(android.R.id.content、mFragment、mTag);
}否则{
if(mFragment.isDetached()){
ft.attach(mFragment);
}
}
}
已选择公共空页(选项卡,碎片事务ft){
if(mFragment!=null){
ft.分离(mFragment);
}
}
已重新选择公共无效页签(页签,碎片事务){
}
}
正如您所看到的,它与Android示例非常相似,只是没有在构造函数中分离,而是使用replace而不是add
经过多次Headscratch和反复尝试,我发现在构造函数中找到片段似乎可以神奇地解决double onCreateView问题(我假设在保存/恢复状态时,通过ActionBar.setSelectedNavigationItem()路径调用OnAbsSelected时,它最终为null).这里的两个投票结果显示了导航模式下活动的解决方案
导航模式选项卡
,但我对导航模式列表
也有同样的问题。当屏幕方向改变时,它使我的片段莫名其妙地失去了状态,这真是令人讨厌。谢天谢地,由于他们有用的代码,我设法找到了答案
基本上,在使用列表导航时,创建/重新创建活动时,无论您是否喜欢,都会自动调用onNavigationItemSelected()
。为了防止片段的onCreateView()
被调用两次,对onNavigationItemSelected()
的初始自动调用应该检查片段是否已经存在于活动中。如果是,立即返回,因为无事可做;如果不是,那么只需构建片段并像通常那样将其添加到活动中。执行此检查可防止不必要地再次创建您的片段,这会导致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;
}
}
这个解决方案的灵感来源于。我也遇到过同样的问题,一个简单的活动只包含一个片段(有时会被替换)。然后我意识到我只在片段中使用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 我认为那是不对的。当我在if块中包装addTab代码时,片段被附加到活动,但没有选项卡。似乎每次都必须在onCreate方法中添加选项卡。我将继续研究这个问题,并在我更好地理解的情况下发布更多信息