Android - 保存/恢复片段状态

51

我有一个Activity,其中包含多个Fragment。每个Fragment中都有多个视图(EditText、ListView、Map等)。

如何保存当前显示的Fragment的实例?我需要在Activity执行onPause() --> onResume()周期时正常工作。而且当我从另一个Fragment返回时(从后退栈中退出),也需要正常工作。

我从主Activity调用第一个Fragment,然后从该Fragment调用下一个。

我的Activity代码:

public class Activity_Main extends FragmentActivity{

public static Fragment_1 fragment_1;
public static Fragment_2 fragment_2;
public static Fragment_3 fragment_3;
public static FragmentManager fragmentManager;

@Override
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

     fragment_1 = new Fragment_1();

     fragment_2 = new Fragment_2();

     fragment_3 = new Fragment_3();

     fragmentManager = getSupportFragmentManager();
     FragmentTransaction transaction_1 = fragmentManager.beginTransaction();
     transaction_1.replace(R.id.content_frame, fragment_1);
     transaction_1.commit();
}}

以下是我其中一个片段的代码:

public class Fragment_1 extends Fragment {

      private EditText title;
      private Button go_next;


      @Override
      public View onCreateView(final LayoutInflater inflater,
        ViewGroup container, Bundle savedInstanceState) {

            View rootView = inflater.inflate(R.layout.fragment_1,
            container, false);

            title = (EditText) rootView.findViewById(R.id.title);

            go_next = (Button) rootView.findViewById(R.id.go_next);

            image.setOnClickListener(new View.OnClickListener() {

         @Override
         public void onClick(View v) {

                 FragmentTransaction transaction_2 = Activity_Main.fragmentManager
                .beginTransaction();

                 transaction_2.replace(R.id.content_frame,
                  Activity_Main.fragment_2);
                 transaction_2.addToBackStack(null);
                 transaction_2.commit();  

            });
        }}

我已经搜索了很多信息,但没有什么是清晰明确的。请问有人可以给出一个明确的解决方案和例子吗?


尝试使用此链接:https://dev59.com/uOo6XIcBkEYKwwoYNBln - user3355820
8个回答

87

当一个片段被移动到后台堆栈时,它不会被销毁,所有实例变量仍然存在。因此,这是保存数据的地方。在 onActivityCreated 中,您要检查以下条件:

  1. 捆绑包是否为null?如果是,则数据已保存在其中(可能是方向更改)。
  2. 实例变量中是否有保存的数据?如果是,则从中恢复状态(或者可能无需执行任何操作,因为一切都正常)。
  3. 否则,您的片段第一次显示,请重新创建所有内容。

编辑:以下是一个示例

public class ExampleFragment extends Fragment {
    private List<String> myData;

    @Override
    public void onSaveInstanceState(final Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable("list", (Serializable) myData);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (savedInstanceState != null) {
            //probably orientation change
            myData = (List<String>) savedInstanceState.getSerializable("list");
        } else {
            if (myData != null) {
                //returning from backstack, data is fine, do nothing
            } else {
                //newly created, compute data
                myData = computeData();
            }
        }
    }
}

请您能否举个例子? - Stanete
1
你想要什么样的例子?我描述的三个步骤的代码? - Kirill Rakhman
onCreateView方法是否在每次显示片段时都被调用?正如您所看到的,我现在在那里声明我的视图。我需要改变什么吗? - Stanete
你可以把onCreateView方法保持不变。但是最好在onViewCreated()中创建视图并分配监听器等。 - Kirill Rakhman
4
这篇帖子是解决方案吗?如果是,请考虑标记它。 - Boas Enkler
显示剩余5条评论

19
Android的Fragment有一些优点和一些缺点。 Fragment最大的缺点是,当你想使用一个fragment时,你只需要创建一次。 当你使用它时,每次都会调用fragment的onCreateView方法。如果你想保持fragment中组件的状态,你必须保存fragment的状态,并在下一次显示时加载它的状态。 这使得fragment的视图有点慢和奇怪。
我找到了一个解决方案,并且我已经使用了这个解决方案:“一切都很好。每个人都可以尝试”。
当第一次运行onCreateView时,将视图创建为全局变量。当第二次调用此fragment时,onCreateView会再次被调用,你可以返回这个全局视图。fragment组件的状态将被保留。
View view;

@Override
public View onCreateView(LayoutInflater inflater,
        @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    setActionBar(null);
    if (view != null) {
        if ((ViewGroup)view.getParent() != null)
            ((ViewGroup)view.getParent()).removeView(view);
        return view; 
    }
    view = inflater.inflate(R.layout.mylayout, container, false);
}

1
((ViewGroup)view.getParent()).removeView(view); 会报空指针异常,但是如果我只使用return view;就可以正常工作! - Pavel Biryukov
@PavelBiryukov 你需要控制 (ViewGroup)view.getParent() 的值。 - nebyan
1
Me too ((ViewGroup)view.getParent()).removeView(view); 给出了空指针异常。 - Mahmood Ali
嗨@TusharSaha,当片段重新加载时,视图将再次被创建。可能会有延迟才能看到视图。而且视图状态可能会丢失。在这段代码中,片段不会再次创建视图并保留视图状态。如果片段将被销毁,则可以删除视图。 - nebyan
片段尚未被销毁,仅在后台堆栈中,因此当您弹出它时,它将从onCreateView开始,但保持充气视图作为变量是一种不好的做法,我想对性能产生负面影响... GC可能会再次运行,而实际上没有收集任何东西。 - Tushar Saha
显示剩余2条评论

8
试试这个:
@Override
protected void onPause() {
    super.onPause();
    if (getSupportFragmentManager().findFragmentByTag("MyFragment") != null)
        getSupportFragmentManager().findFragmentByTag("MyFragment").setRetainInstance(true);
}

@Override
protected void onResume() {
    super.onResume();
    if (getSupportFragmentManager().findFragmentByTag("MyFragment") != null)
        getSupportFragmentManager().findFragmentByTag("MyFragment").getRetainInstance();
}

希望这能帮到你。

同时,你可以将其写入清单文件中的activity标签:

  android:configChanges="orientation|screenSize"

Good luck !!!


4
空指针异常。 - Pravinsingh Waghela
9
android:configChanges="orientation|screenSize"会覆盖正常的Android行为,导致许多其他问题。它很可能会带来意外的错误。我建议不要使用它。 - Timo Bähr

5
为了保存Fragment的状态,您需要实现onSaveInstanceState()
与活动类似,您可以使用Bundle保存片段的状态,以防止活动进程被杀并在重新创建活动时需要恢复片段状态。您可以在片段的onSaveInstanceState()回调期间保存状态,并在onCreate()onCreateView()onActivityCreated()期间恢复它。有关保存状态的更多信息,请参见Activities文档。 http://developer.android.com/guide/components/fragments.html#Lifecycle

onSaveInstanceState 是一个棘手的问题 - 即使从 Fragment 导航离开,有时甚至在销毁时也不会总是被调用 - 在这里检查答案 https://dev59.com/lXrZa4cB1Zd3GeqP7tpk#20892548 - Burkely91

3
如此说来:为什么要使用Fragment#setRetainInstance(boolean)?
你也可以像这样使用片段方法setRetainInstance(true)
public class MyFragment extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // keep the fragment and all its data across screen rotation
        setRetainInstance(true);

    }
}

1
你可以通过fragmentManager获取当前的Fragment。如果在fragmentManager中没有任何Fragment,你可以创建Fragment_1
public class MainActivity extends FragmentActivity {


    public static Fragment_1 fragment_1;
    public static Fragment_2 fragment_2;
    public static Fragment_3 fragment_3;
    public static FragmentManager fragmentManager;


    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        setContentView(R.layout.main);

        fragment_1 = (Fragment_1) fragmentManager.findFragmentByTag("fragment1");

        fragment_2  =(Fragment_2) fragmentManager.findFragmentByTag("fragment2");

        fragment_3 = (Fragment_3) fragmentManager.findFragmentByTag("fragment3");


        if(fragment_1==null && fragment_2==null && fragment_3==null){           
            fragment_1 = new Fragment_1();          
            fragmentManager.beginTransaction().replace(R.id.content_frame, fragment_1, "fragment1").commit();
        }


    }


}

另外,您可以将setRetainInstance设置为true,它会忽略片段中的onDestroy()方法,当您的应用程序进入后台并且操作系统杀死应用程序以分配更多内存时,您需要在onSaveInstanceState捆绑中保存所有所需数据。

public class Fragment_1 extends Fragment {


    private EditText title;
    private Button go_next;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true); //Will ignore onDestroy Method (Nested Fragments no need this if parent have it)
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        onRestoreInstanceStae(savedInstanceState);
        return super.onCreateView(inflater, container, savedInstanceState);
    }


    //Here you can restore saved data in onSaveInstanceState Bundle
    private void onRestoreInstanceState(Bundle savedInstanceState){
        if(savedInstanceState!=null){
            String SomeText = savedInstanceState.getString("title");            
        }
    }

    //Here you Save your data
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("title", "Some Text");
    }

}

0

我不确定这个问题是否仍然困扰着你,因为已经过去几个月了。但是我很想分享一下我是如何解决这个问题的。 以下是源代码:

int FLAG = 0;
private View rootView;
private LinearLayout parentView;

/**
 * The fragment argument representing the section number for this fragment.
 */
private static final String ARG_SECTION_NUMBER = "section_number";

/**
 * Returns a new instance of this fragment for the given section number.
 */
public static Fragment2 newInstance(Bundle bundle) {
    Fragment2 fragment = new Fragment2();
    Bundle args = bundle;
    fragment.setArguments(args);
    return fragment;
}

public Fragment2() {

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    Log.e("onCreateView","onCreateView");
    if(FLAG!=12321){
        rootView = inflater.inflate(R.layout.fragment_create_new_album, container, false);
        changeFLAG(12321);
    }       
    parentView=new LinearLayout(getActivity());
    parentView.addView(rootView);

    return parentView;
}

/* (non-Javadoc)
 * @see android.support.v4.app.Fragment#onDestroy()
 */
@Override
public void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    Log.e("onDestroy","onDestroy");
}

/* (non-Javadoc)
 * @see android.support.v4.app.Fragment#onStart()
 */
@Override
public void onStart() {
    // TODO Auto-generated method stub
    super.onStart();
    Log.e("onstart","onstart");
}

/* (non-Javadoc)
 * @see android.support.v4.app.Fragment#onStop()
 */
@Override
public void onStop() {
    // TODO Auto-generated method stub
    super.onStop();
    if(false){
        Bundle savedInstance=getArguments();
        LinearLayout viewParent;

        viewParent= (LinearLayout) rootView.getParent();
        viewParent.removeView(rootView);

    }
    parentView.removeView(rootView);

    Log.e("onStop","onstop");
}
@Override
public void onPause() {
    super.onPause();
    Log.e("onpause","onpause");
}

@Override
public void onResume() {
    super.onResume();
    Log.e("onResume","onResume");
}

这是MainActivity的代码:

/**
 * Fragment managing the behaviors, interactions and presentation of the
 * navigation drawer.
 */
private NavigationDrawerFragment mNavigationDrawerFragment;

/**
 * Used to store the last screen title. For use in
 * {@link #restoreActionBar()}.
 */

public static boolean fragment2InstanceExists=false;
public static Fragment2 fragment2=null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    setContentView(R.layout.activity_main);

    mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager()
            .findFragmentById(R.id.navigation_drawer);
    mTitle = getTitle();

    // Set up the drawer.
    mNavigationDrawerFragment.setUp(R.id.navigation_drawer,
            (DrawerLayout) findViewById(R.id.drawer_layout));
}

@Override
public void onNavigationDrawerItemSelected(int position) {
    // update the main content by replacing fragments
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
    switch(position){
    case 0:
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.replace(R.id.container, Fragment1.newInstance(position+1)).commit();
        break;
    case 1:

        Bundle bundle=new Bundle();
        bundle.putInt("source_of_create",CommonMethods.CREATE_FROM_ACTIVITY);

        if(!fragment2InstanceExists){
            fragment2=Fragment2.newInstance(bundle);
            fragment2InstanceExists=true;
        }
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.replace(R.id.container, fragment2).commit();

        break;
    case 2:
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.replace(R.id.container, FolderExplorerFragment.newInstance(position+1)).commit();
        break;
    default: 
        break;
    }
}
parentView 是关键点。 通常,在 onCreateView 中,我们只需使用 return rootView。但现在,我将 rootView 添加到 parentView 中,然后返回 parentView。为了防止出现“指定的子项已经有一个父项。您必须调用 removeView()...”错误,我们需要调用 parentView.removeView(rootView),否则我提供的方法就无效了。 我还想分享一下我是如何发现这个问题的。首先,我设置了一个布尔值来指示实例是否存在。当实例存在时,rootView 将不会再次被填充。但是,logcat 给出了子项已经有一个父项的提示,所以我决定使用另一个父项作为中间父视图。这就是它的工作原理。
希望对你有所帮助。

0

如果你正在使用bottombar而不是viewpager,并且希望设置自定义片段替换逻辑以检索先前保存的状态,则可以使用以下代码进行操作

 String current_frag_tag = null;
 String prev_frag_tag = null;



    @Override
    public void onTabSelected(TabLayout.Tab tab) {
   

        switch (tab.getPosition()) {
            case 0:

                replaceFragment(new Fragment1(), "Fragment1");
                break;

            case 1:
                replaceFragment(new Fragment2(), "Fragment2");
                break;

            case 2:
                replaceFragment(new Fragment3(), "Fragment3");
                break;

            case 3:
               replaceFragment(new Fragment4(), "Fragment4");
                break;

            default:
                replaceFragment(new Fragment1(), "Fragment1");
                break;

        }

    public void replaceFragment(Fragment fragment, String tag) {
        if (current_frag_tag != null) {
            prev_frag_tag = current_frag_tag;
        }

        current_frag_tag = tag;


        FragmentManager manager = null;
        try {
            manager = requireActivity().getSupportFragmentManager();
            FragmentTransaction ft = manager.beginTransaction();

            if (manager.findFragmentByTag(current_frag_tag) == null) { // No fragment in backStack with same tag..
                ft.add(R.id.viewpagerLayout, fragment, current_frag_tag);

                if (prev_frag_tag != null) {
                    try {
                        ft.hide(Objects.requireNonNull(manager.findFragmentByTag(prev_frag_tag)));
                    } catch (NullPointerException e) {
                        e.printStackTrace();
                    }
                }
//            ft.show(manager.findFragmentByTag(current_frag_tag));
                ft.addToBackStack(current_frag_tag);
                ft.commit();

            } else {

                try {
                    ft.hide(Objects.requireNonNull(manager.findFragmentByTag(prev_frag_tag)))
                            .show(Objects.requireNonNull(manager.findFragmentByTag(current_frag_tag))).commit();
                } catch (NullPointerException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }




    }

在子片段中,您可以使用以下方法访问片段是否可见 注意:您必须在子片段中实现以下方法
@Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);

        try {
            if(hidden){
                adapter.getFragment(mainVideoBinding.viewPagerVideoMain.getCurrentItem()).onPause();
            }else{
                adapter.getFragment(mainVideoBinding.viewPagerVideoMain.getCurrentItem()).onResume();
            }
        }catch (Exception e){
       }

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接