当一个Activity执行了onStop()
、Android 3.0之前执行onPause()
方法时,将会被系统标记为Killable
,在系统处于extreme low memory
的情况下,处于Killable
状态的Activity将极有可能会被系统从内存释放(If an activity is paused or stopped,the system can drop the activity from memory by either asking it to finish, or simply killing its process) —— Activity API
默认情况下Fragment的restore
过程 ➣当系统认为一个Activity处于Killable状态时会调用Activity#onSaveInstanceState(Bundle outState)
方法进行数据的保存,Android3.0之前该方法的执行时机为:onResume → onSaveInstanceState →onPause
,即调用onPause()之前;Android3.0之后:onPause →onSaveInstanceState→ onStop
,即调用onStop()之前。 ➣FragmentActivity的onSaveInstanceState(Bundle outState)
中会记录所有Fragment状态信息,源码如下:
1 2 3 4 5 6 7 8 9 @Override protected void onSaveInstanceState (Bundle outState) { super .onSaveInstanceState(outState); Parcelable p = mFragments.saveAllState(); if (p != null ) { outState.putParcelable(FRAGMENTS_TAG, p); } ...... }
➣当用户重新回到被Killed的FragmentActivity时,Activity将会被重新创建,并进行之前onSaveInstanceState(Bundle outState)
保存数据的restore
过程,在FragmentActivity#onCreate(Bundle savedInstanceState)
方法中会根据之前保存的Fragment状态信息进行Fragment的恢复(重新创建之前Activity中存在的所有Fragment),源码如下:
1 2 3 4 5 6 7 8 9 protected void onCreate (@Nullable Bundle savedInstanceState) { ...... if (savedInstanceState != null ) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, nc != null ? nc.fragments : null ); ...... } mFragments.dispatchCreate(); }
➣restore
过程恢复的“旧”的Fragment将会随着Activity生命周期方法(onStart
、onResume
)的执行而执行相应的Fragment生命周期方法(onCreateView
、onActivityCreated
、onResume
等),从而完成Fragment的完整创建。如果我们在Activity中未做针对性处理,有时重新回到一个Activity的时候会出现Fragment重叠
、自定义View功能失效甚至直接Crash
等情况。
如何简单有效的处理restore
带来的问题 正常情况下,restore过程带来的问题,根源是由于Activity在恢复之前“旧”的Fragment的同时,我们又重新创建新的Fragment导致的,也就是说此时Activity存在两份职责功能完全重复的Fragment,针对这个问题其实我们只需要注释掉Activity中保存Fragment状态的代码,让Activity在被Killed之前不去保存Fragment信息,那样在FragmentActivity#onCreate(Bundle savedInstanceState)
方法中便无法进行相应的restore
处理,也就是说“旧”的Fragment都将不复存在,界面只会显示我们新创建的Fragment,注释的具体代码如下:
1 2 3 4 @Override protected void onSaveInstanceState (Bundle outState) { }
如何复用restore
产生的Fragment 注释掉代码的方式固然简单粗暴,但也牺牲了程序执行效率,以及可能带来较差的用户体验(用户可能看到短暂的黑屏现象)。Fragment的重新创建肯定没有复用来的高效,而且注释代码的方式也阻止了其它视图状态的保存。
1. add/replace方式显示的Fragment 对于普通的add/replace方式显示的Fragment,在Activity中可以在初始化之前根据tag
使用FragmentManager#findFragmentByTag
判断是否已经存在,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 Fragment aFragment = getSupportFragmentManager().findFragmentByTag("标识Fragment的Tag" ); if (aFragment == null ) { aFragment=new FragmentA(); Bundle args = new Bundle(); args.putString("参数名" , "参数值" ); aFragment.setArguments(args); getSupportFragmentManager().beginTransaction(). add(R.id.container, aFragment,"标识Fragment的Tag" ). commitAllowingStateLoss(); }
在使用ViewPager嵌套Fragment的情况下,情况会和普通add/replace方式添加的Fragment有所不同,如果我们使用的是FragmentPagerAdapter
,其在instantiateItem
方法中会先通过tag查找Fragment,如果Fragment不存在,在add时会自动添加tag,源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public Object instantiateItem (ViewGroup container, int position) { ...... String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null ) { mCurTransaction.attach(fragment); } else { fragment = getItem(position); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } ...... return fragment; }
这种情况下,我们需要如何复用呢?分如下两种情况:
➣Activity与Fragment不需要进行交互 在Activity与Fragment不需要进行交互的情况下,我们不需要保持ViewPager中所显示Fragment的引用,只需要在Adapter#getItem(int position)
根据position
直接返回创建的Fragment实例即可,具体原因在上述FragmentPagerAdapter#instantiateItem()
方法中可以很清楚看到:restore过程中产生的fragment,ViewPager会直接利用,只有在当前初始化位置的Fragment==null
时才会通过getItem(position)
获取,因此可以在Adapter中使用如下方法初始化Fragment(FragmentPagerAdapter 的example中使用的是类似的方式):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private class MyPageAdapter extends FragmentPagerAdapter { ...... @Override public Fragment getItem (int position) { switch (position) { case 0 : return Fragment0.newInstance(args); case 1 : return Fragment1.newInstance(args); case 2 : return Fragment2.newInstance(args); default : return Fragment0.newInstance(args); } } ...... }
➣Activity与Fragment需要进行交互 如果我们在Activity中必须 保持对ViewPager中显示的Fragment的直接引用,如TitleBar中有个刷新按钮(仅从开发实际情况中选取示例,其实同样可以从FragmentManager中获取到,原则上是能不保持引用最好不保持 ),我们需要实现点击按钮来刷新ViewPager当前的界面,就可以全局声明一个Fragment数组,来保持对ViewPager中用到的每个Fragment的引用,以便控制刷新。这种情况下在Activity中,我们需要对restore产生的Fragment进行查找和引用,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 public class MainActivity extends AppCompatActivity { private final String KEY_TAGS = "viewpager_fragment_tags" ; private MyPageAdapter mPageAdapter; private final int PAGE_COUNT = 3 ; private Fragment[] fragments = new Fragment[PAGE_COUNT]; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager); if (savedInstanceState == null ) { for (int i = 0 ; i < PAGE_COUNT; i++) { fragments[i] = getFragment(i); } } else { String[] tags = savedInstanceState.getStringArray(KEY_TAGS); for (int i = 0 ; i < PAGE_COUNT; i++) { fragments[i] = getFragmentWithTags(i, tags); } } mPageAdapter = new MyPageAdapter(getSupportFragmentManager(), fragments); viewPager.setAdapter(mPageAdapter); } private Fragment getFragmentWithTags (int position, String[] tags) { if (tags == null ) { return getFragment(position); } FragmentManager fragmentManager = getSupportFragmentManager(); if (tags[position] != null ) { Fragment targetFragment = fragmentManager.findFragmentByTag(tags[position]); return targetFragment == null ? getFragment(position) : targetFragment; } return getFragment(position); } private Fragment getFragment (int position) { switch (position) { case 0 : return new Fragment0(); case 1 : return new Fragment1(); case 2 : return new Fragment2(); default : return new Fragment0(); } } @Override protected void onSaveInstanceState (Bundle outState) { super .onSaveInstanceState(outState); outState.putStringArray(KEY_TAGS, mPageAdapter.getInstantiateTags()); } @Override protected void onResume () { super .onResume(); } private class MyPageAdapter extends FragmentPagerAdapter { private String[] mFragmentTags; private Fragment[] mFragments = null ; public MyPageAdapter (FragmentManager mFragmentManager, @NonNull Fragment[] fragments) { super (mFragmentManager); this .mFragments = fragments; mFragmentTags = new String[mFragments.length]; } @Override public int getCount () { return mFragments.length; } @Override public Fragment getItem (int position) { return mFragments[position]; } @Override public Object instantiateItem (ViewGroup container, int position) { Fragment fragment = (Fragment) super .instantiateItem(container, position); mFragmentTags[position] = fragment.getTag(); return fragment; } public String[] getInstantiateTags() { return mFragmentTags; } } }
如果使用的是FragmentStatePagerAdapter
,其与FragmentPagerAdapter
也不同,FragmentStatePagerAdapter
本身进行了状态的保存(#restoreState(Parcelable state, ClassLoader loader)
)和恢复(#saveState()
)处理,如果我们不需要保持ViewPager中所显示Fragment的引用,只需像FragmentPagerAdapter
一样在getItem(int position)
中直接return相应的Fragment对象即可;若 必须要保持所显示Fragment的引用,可以通过反射的方式在初始化时反射为Fragment#mTag
赋值,在初始化前同样根据findFragmentByTag
判断是否已经存在。当然,特殊必要的情况下,我们可以不使用底层原有的保存机制,在研究透彻的基础上,自己编码进行Fragment列表及相关数据的保存
4. addToBackStack 在使用FragmentTransaction#addToBackStack(String name)
将Fragment加入回退栈的情况下,由于在FragmentActivity#onSaveInstanceState(Bundle outState)
方法中为所有Fragment都保存了状态信息(Saves the state for all Fragments),正常restore过程中也会将之前的Fragment的Stack状态恢复,如果没有特殊需要,正常情况下系统会帮我们恢复到之前显示的状态,不需要做特殊处理。 (Demo源码 )
需要注意的是,此篇文章主要探讨的是在FragmentActivity被系统强制销毁的情况下Fragment的恢复和复用问题,如果是一个复杂Activity被销毁,我们想回到销毁之前一模一样的状态,还需要处理Activity中View的内容、位置、状态等一些复杂信息的保存和恢复。