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 在方向更改时的行为