添加到后端堆栈时,如何维护碎片状态?

我写了一个虚拟活动,在两个片段之间切换。 当你从FragmentA到FragmentB时,FragmentA被添加到后端堆栈。 然而,当我回到FragmentA(通过按回),一个全新的FragmentA被创build,并且它所处的状态已经丢失。 我觉得我跟这个问题是一样的,但是我已经包含了一个完整的代码示例来帮助解决这个问题:

public class FooActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(android.R.id.content, new FragmentA()); transaction.commit(); } public void nextFragment() { final FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(android.R.id.content, new FragmentB()); transaction.addToBackStack(null); transaction.commit(); } public static class FragmentA extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View main = inflater.inflate(R.layout.main, container, false); main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { ((FooActivity) getActivity()).nextFragment(); } }); return main; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Save some state! } } public static class FragmentB extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.b, container, false); } } } 

添加了一些日志消息:

 07-05 14:28:59.722 D/OMG ( 1260): FooActivity.onCreate 07-05 14:28:59.742 D/OMG ( 1260): FragmentA.onCreateView 07-05 14:28:59.742 D/OMG ( 1260): FooActivity.onResume <Tap Button on FragmentA> 07-05 14:29:12.842 D/OMG ( 1260): FooActivity.nextFragment 07-05 14:29:12.852 D/OMG ( 1260): FragmentB.onCreateView <Tap 'Back'> 07-05 14:29:16.792 D/OMG ( 1260): FragmentA.onCreateView 

它永远不会调用FragmentA.onSaveInstanceState,当你回击的时候它会创build一个新的FragmentA。 但是,如果我在FragmentA上,并locking屏幕,FragmentA.onSaveInstanceState确实被调用。 太奇怪了……我错在期待一个片段添加到后端堆栈,不需要重新创build? 以下是文档所说的内容:

而如果在删除片段时确实调用了addToBackStack(),则片段将停止,并在用户返回时恢复。

如果从后端堆栈返回片段,则不会重新创build片段,而是重新使用同一个实例,并在片段生命周期中以onCreateView()开头,请参阅片段生命周期 。

所以,如果你想存储状态,你应该使用实例variables,而不是依靠onSaveInstanceState()

与苹果的UINavigationControllerUIViewController ,Google在Android软件架构上做得不好。 而关于Fragment Android文件并没有多大帮助。

从FragmentAinputFragmentB时,现有的FragmentA实例不会被销毁。 当按FragmentB中的Back并返回到FragmentA时,我们不会创build一个新的FragmentA实例。 现有的FragmentA实例的onCreateView()将被调用。

关键是我们不应该在FragmentA的onCreateView()再次膨胀视图,因为我们正在使用现有的FragmentA的实例。 我们需要保存和重用rootView。

以下代码运行良好。 它不仅保持片段状态,而且还减less了RAM和CPU负载(因为我们只在必要时扩充布局)。 我不能相信谷歌的示例代码和文档从来没有提到它,但总是夸大布局 。

版本1(不要使用版本1使用版本2)

 public class FragmentA extends Fragment { View _rootView; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (_rootView == null) { // Inflate the layout for this fragment _rootView = inflater.inflate(R.layout.fragment_a, container, false); // Find and setup subviews _listView = (ListView)_rootView.findViewById(R.id.listView); ... } else { // Do not inflate the layout again. // The returned View of onCreateView will be added into the fragment. // However it is not allowed to be added twice even if the parent is same. // So we must remove _rootView from the existing parent view group // (it will be added back). ((ViewGroup)_rootView.getParent()).removeView(_rootView); } return _rootView; } } 

—— 2005年5月3日更新:——-

正如所提到的注释,有时_rootView.getParent()onCreateView ,为空,导致崩溃。 版本2删除onDestroyView()中的_rootView,如dell116build议。 在Android 4.0.3,4.4.4,5.1.0上testing

版本2

 public class FragmentA extends Fragment { View _rootView; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (_rootView == null) { // Inflate the layout for this fragment _rootView = inflater.inflate(R.layout.fragment_a, container, false); // Find and setup subviews _listView = (ListView)_rootView.findViewById(R.id.listView); ... } else { // Do not inflate the layout again. // The returned View of onCreateView will be added into the fragment. // However it is not allowed to be added twice even if the parent is same. // So we must remove _rootView from the existing parent view group // in onDestroyView() (it will be added back). } return _rootView; } @Override public void onDestroyView() { if (_rootView.getParent() != null) { ((ViewGroup)_rootView.getParent()).removeView(_rootView); } super.onDestroyView(); } } 

警告!!!

这是一个HACK! 虽然我在我的应用程序中使用它,但您需要仔细testing和阅读注释。

我想有一个替代方法来实现你正在寻找的东西。 我不是说它是一个完整的解决scheme,但它在我的情况下是为了达到目的。

我所做的是不是replace我刚添加目标片段的片段。 所以基本上你会使用add()方法而不是replace()

我还做了什么。 我隐藏我的当前片段,并将其添加到背层。

因此它在当前片段上重叠新的片段而不会破坏它的视图(检查它的onDestroyView()方法没有被调用.Plus将它添加到backstate给了我恢复片段的优点。

这里是代码:

 Fragment fragment=new DestinationFragment(); FragmentManager fragmentManager = getFragmentManager(); android.app.FragmentTransaction ft=fragmentManager.beginTransaction(); ft.add(R.id.content_frame, fragment); ft.hide(SourceFragment.this); ft.addToBackStack(SourceFragment.class.getName()); ft.commit(); 

如果视图被销毁或者没有被创build,AFAIK系统只调用onCreateView() 。 但是在这里,我们通过不把它从内存中移除来保存视图,所以它不会创build新的视图。

而当你从Destination Fragment回来时,它会popup最后一个FragmentTransaction移除顶部的片段,这将使最上面的(SourceFragment)视图出现在屏幕上。

评论:正如我所说,它不是一个完整的解决scheme,因为它不会消除源片段的视图,因此比平常占用更多的内存。但仍然服务于此目的。此外,我们正在使用一个完全不同的机制隐藏视图,而不是replace这是非传统的。

所以它不是真的如何维持国家,而是你如何维持这个观点。

我在包含地图的Fragment中遇到了这个问题,该地图有太多的设置细节来保存/重新加载。 我的解决办法是基本保持这个片段活跃(类似于@kaushal所提到的)。

假设你有现在的片段A并且想要显示片段B.总结结果:

  • replace() – 删除Fragment A并用Fragment Breplace它。Fragment A将会被重新创build一次
  • 添加() – (创build和)添加片段B,它重叠片段A,它仍然在后台活动
  • remove() – 可以用来删除Fragment B并返回A.当稍后调用时,将会重新创buildFragment B.

因此,如果你想保留这两个碎片“保存”,只需使用hide()/ show()切换它们。

优点 :保持多个碎片运行的简单方法
缺点 :你使用更多的内存来保持所有的内存运行。 可能会遇到问题,例如显示许多大的位图

onSaveInstanceState()仅在configuration更改时才会调用。

由于从一个片段更改为另一个没有configuration更改,所以没有调用onSaveInstanceState()在那里。 什么状态不被保存? 你能指定吗?

如果您在EditText中input一些文字,它将被自动保存。 任何没有ID的UI项目都是不能保存视图状态的项目。

在这里,因为onSaveInstanceState中的片段不会在您将片段添加到backstack时调用。 在恢复后,堆栈中的片段生命周期开始onCreateView并结束onDestroyView onSaveInstanceStateonDestroyViewonDestroy之间调用。 我的解决scheme是在onCreate创build实例variables和init。 示例代码:

 private boolean isDataLoading = true; private ArrayList<String> listData; public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); isDataLoading = false; // init list at once when create fragment listData = new ArrayList(); } 

并检查onActivityCreated

 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if(isDataLoading){ fetchData(); }else{ //get saved instance variable listData() } } private void fetchData(){ // do fetch data into listData } 
 getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { @Override public void onBackStackChanged() { if (getSupportFragmentManager().getBackStackEntryCount() == 0) { //setToolbarTitle("Main Activity"); } else { Log.e("fragment_replace11111", "replace"); } } }); YourActivity.java @Override public void onBackPressed() { Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content); if (fragment instanceof YourFragmentName) { fragmentReplace(new HomeFragment(),"Home Fragment"); txt_toolbar_title.setText("Your Fragment"); } else{ super.onBackPressed(); } } public void fragmentReplace(Fragment fragment, String fragment_name) { try { fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name); fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right); fragmentTransaction.addToBackStack(fragment_name); fragmentTransaction.commitAllowingStateLoss(); } catch (Exception e) { e.printStackTrace(); } } 

我的问题是相似的,但我没有保持片段活着,我克服了我。 假设你有一个活动,有两个片段 – F1和F2。 F1是最初启动,并让说包含一些用户信息,然后在某些情况下F2popup要求用户填写额外的属性 – 他们的电话号码。 接下来,您需要将该电话号码回弹到F1并完成注册,但您意识到之前的所有用户信息都已丢失,并且您没有以前的数据。 该片段是从头开始重新创build的,即使您将此信息保存在onSaveInstanceState该包在onActivityCreated也会返回null。

解决scheme:将所需信息作为实例variables保存在调用活动中。 然后将该实例variables传递给你的片段。

 @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Bundle args = getArguments(); // this will be null the first time F1 is created. // it will be populated once you replace fragment and provide bundle data if (args != null) { if (args.get("your_info") != null) { // do what you want with restored information } } } 

因此,继续我的示例:在显示F2之前,我使用callback将实例variables中的用户数据保存起来。 然后我开始F2,用户填写电话号码并按保存。 我在活动中使用另一个callback,收集这些信息并replace我的碎片F1,这次它我可以使用的捆绑数据。

 @Override public void onPhoneAdded(String phone) { //replace fragment F1 f1 = new F1 (); Bundle args = new Bundle(); yourInfo.setPhone(phone); args.putSerializable("you_info", yourInfo); f1.setArguments(args); getFragmentManager().beginTransaction() .replace(R.id.fragmentContainer, f1).addToBackStack(null).commit(); } } 

有关callback的更多信息可以在这里find: https : //developer.android.com/training/basics/fragments/communicating.html

第一 :只是使用add方法而不是FragmentTransaction类的replace方法,那么你必须添加secondFragment通过addToBackStack方法堆栈

第二 :在后面点击你必须调用popBackStackImmediate()

 Fragment sourceFragment = new SourceFragment (); final Fragment secondFragment = new SecondFragment(); final FragmentTransaction ft = getChildFragmentManager().beginTransaction(); ft.add(R.id.child_fragment_container, secondFragment ); ft.hide(sourceFragment ); ft.addToBackStack(NewsShow.class.getName()); ft.commit(); ((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult() { @Override public void backFragmentNewsResult() { getChildFragmentManager().popBackStackImmediate(); } };