Android Fragment 创建了两次方向更改

Posted

技术标签:

【中文标题】Android Fragment 创建了两次方向更改【英文标题】:Android Fragment created twice orientation change 【发布时间】:2015-05-24 12:53:29 【问题描述】:

我的片段被创建了两次,即使活动只是将片段添加到内容中一次。当我旋转屏幕时会发生这种情况。此外,每次调用片段的 onCreateView 时,它都会丢失所有的变量状态。

public class MainActivity extends ActionBarActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null)  // Checking for recreation
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new AppPanelFragment())
                    .commit();
        
    


onCreate 在活动中检查 null savedInstanceState 并且只有 null 才会添加片段,所以我看不出为什么片段应该被创建两次?在 if 条件中放置一个断点告诉我它只会被调用一次,因此活动不应该多次添加片段。然而,每次方向改变时仍会调用片段的 onCreateView。

        public class AppPanelFragment extends Fragment 

            private TextView appNameText;

            @Override
            public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                     Bundle savedInstanceState) 
// This method called several times
                View rootView = inflater.inflate(R.layout.fragment_app_panel, container, false);

// 2nd call on this method, appNameText is null, why?
appNameText = (TextView) rootView.findViewById(R.id.app_name);
appNameText.text = "My new App";

    return view;

    

我设法使用 setRetainInstance(true) 保持变量状态,但这是真正的解决方案吗?我希望片段不会仅在方向更改时创建。

【问题讨论】:

【参考方案1】:

android中,当手机的方向改变时,activity会被销毁并重新创建。现在,我相信要解决您的问题,您可以使用片段管理器检查片段是否已存在于后台堆栈中,如果不存在则创建它。

public void onCreated(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) 
            mFragmentManager = getSupportFragmentManager();
            AppPanelFragment fragment  = (AppPanelFragment)mFragmentManager.findFragmentById(R.id.fagment_id);
            if(fragment == null) 
                //do your fragment creation
             
        
    

附:我没有对此进行测试,但是一旦您在 findFragmentById 方法中提供了正确的片段 ID,它应该可以工作。

【讨论】:

【参考方案2】:

Fragment 的生命周期与Activity 非常相似。默认情况下,是的,它们将在配置更改期间重新创建,就像 Activity 所做的那样。这是预期的行为。即使使用 setRetainInstance(true)(如果它包含 UI,我会说要非常小心地使用它)你的 View 将被销毁并重新创建,但在这种情况下你的 Fragment 实例不会被销毁——只是View.

【讨论】:

感谢您的回复。所以也许我的实际问题是我没有正确建模我的片段。我在这个片段中有一些数据,我想在方向改变时保留这些数据。听起来我将无法做到这一点。那么我应该将数据从片段类中抽象出来吗?这里会以哪种方式发生 - 我确实有可以附加模型的 PresentationModel 如果您的数据可以放入捆绑包中,请使用 onSaveInstanceState() 将数据放入该捆绑包中,如果 savedInstanceState 捆绑包不是空。 有道理,是的,数据可以很容易地保存在捆绑包中。这样就不需要 setRetainInstance 了吗?【参考方案3】:

我知道现在回答有点晚了,但是使用 The Code Pimp 回答你可以做接下来的事情:

如果片段存在于 backstack 中,我们会弹出并删除它以将其添加回来(如果在没有删除它的情况下将其添加回来,则抛出异常,说明它已经存在)。

片段变量是类成员变量。

这个方法会在Activity的onCreate方法中调用:

    if (savedInstanceState == null) 
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        if (fragmentManager.findFragmentById(getFragmentActivityLayoutContainerId()) == null) 
            fragment = getNewFragmentInstance();
         else 
            fragment = fragmentManager.findFragmentById(getFragmentActivityLayoutContainerId());
            fragmentTransaction.remove(fragment);
            fragmentManager.popBackStack();
            fragmentTransaction.commit();
            fragmentTransaction = fragmentManager.beginTransaction();
        
        fragmentTransaction.add(getFragmentActivityLayoutContainerId(), fragment);
        fragmentTransaction.commit();
    

下一个代码将在片段本身中被调用。

这是您可以在片段中实现以了解其工作原理的代码的一个小示例。 dummyTV 是片段中心的一个简单文本视图,它根据方向接收文本(为此我们需要一个计数器)。

private TextView dummyTV;
private static int counter = 0;

@Override
protected int getFragmentLayoutId() 
    return R.layout.fragment_alerts_view;


@Override
protected void saveReferences(View view) 
    dummyTV = (TextView) view.findViewById(R.id.fragment_alerts_view_dummy_tv);


@Override
public void onViewCreated(View view, Bundle savedInstanceState) 

    if (savedInstanceState != null) 
        dummyTV.setText(savedInstanceState.getString("dummy_string"));
     else 
        dummyTV.setText("flip me!");
    

    dummyTV.append(" | " + String.valueOf(counter));


@Override
public void onSaveInstanceState(Bundle outState) 
    outState.putString("dummy_string", counter++ % 2 == 0 ? "landscape" : "portrait");

【讨论】:

【参考方案4】:

如前所述,在方向更改时,活动将被销毁并重新创建。此外,系统会重新创建 Fragments(any)。

为确保您的应用程序恢复到之前的状态,onSaveInstanceState() 在 Activity 被销毁之前被调用。

因此,您可以在活动的 onSaveInstanceState() 方法中存储一些信息,然后在方向更改时将应用程序恢复到相同的状态。

注意:您无需在方向更改时创建片段,因为片段会重新创建。

来自http://www.mynewsfeed.x10.mx/articles/index.php?id=15的示例:

public class MainActivity extends Activity 
@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);

    if ( savedInstanceState == null )
          //Initialize fragments
          Fragment example_fragment = new ExampleFragment();
          FragmentManager manager = getFragmentManager();
          FragmentTransaction transaction = manager.beginTransaction();
          transaction.add(R.id.container, example_fragment, "Example");
     else
       //control comes to this block only on orientation change.

       int postion = savedInstanceState.getInt("position"); //You can retrieve any piece of data you've saved through onSaveInstanceState()

      //finding fragments on orientation change 
      Fragment example_fragment = manager.findFragmentByTag("Example");

     //update the fragment so that the application retains its state
     example_fragment.setPosition(position); //This method is just an example

    


@Override
public void onSaveInstanceState(Bundle outState) 
    super.onSaveInstanceState(outState);
    outState.putInt("position", position); //add any information you'd like to save and restore on orientation change.
    


【讨论】:

以上是关于Android Fragment 创建了两次方向更改的主要内容,如果未能解决你的问题,请参考以下文章

android 转屏触发了两次onConfigurationChanged()方法,很诡异,不知道有人遇到过吗?

活动创建了两次

通过使用 FragmentScenario 停止和恢复来测试 androidx.fragment 生命周期,onCreateView() 调用了两次,但此错误已在 1.3.1 中修复

Android Fragment 和 Activity 在方向更改时的行为

DatePicker.OnDateChangedListener 调用了两次

Firebase Android onAuthStateChanged 调用了两次