Android(安卓)ViewPager Adapter 内存管理的坑
好久没有记录bug了的,因为最近一直在解bug... 之所以写这个文章,确实是出于无奈,毕竟同一个问题,项目上遇到了两次。这里主要是提一下 ViewPager + Fragment。
ViewPager + Fragment 有 '两种' Adapter
- FragmentPagerAdapter
https://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter
Implementation of[PagerAdapter](https://developer.android.com/reference/android/support/v4/view/PagerAdapter.html)
that represents each page as a[Fragment](https://developer.android.com/reference/android/support/v4/app/Fragment.html)
that is persistently kept in the fragment manager as long as the user can return to the page.
示例:
public static class MyAdapter extends FragmentPagerAdapter { public MyAdapter(FragmentManager fm) { super(fm); } @Override public int getCount() { return NUM_ITEMS; } @Override public Fragment getItem(int position) { return ArrayListFragment.newInstance(position); } }
实现上的核心代码:
@Override public Object instantiateItem(ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object + " v=" + ((Fragment)object).getView()); mCurTransaction.detach((Fragment)object); }
- FragmentStatePagerAdapter
https://developer.android.com/reference/android/support/v4/app/FragmentStatePagerAdapter
Implementation of[PagerAdapter](https://developer.android.com/reference/android/support/v4/view/PagerAdapter.html)
that uses a[Fragment](https://developer.android.com/reference/android/support/v4/app/Fragment.html)
to manage each page. This class also handles saving and restoring of fragment's state.
示例:
public static class MyAdapter extends FragmentStatePagerAdapter { public MyAdapter(FragmentManager fm) { super(fm); } @Override public int getCount() { return NUM_ITEMS; } @Override public Fragment getItem(int position) { return ArrayListFragment.newInstance(position); } }
实现上的核心代码:
@Override public Object instantiateItem(ViewGroup container, int position) { // If we already have this item instantiated, there is nothing // to do. This can happen when we are restoring the entire pager // from its saved state, where the fragment manager has already // taken care of restoring the fragments we previously had instantiated. if (mFragments.size() > position) { Fragment f = mFragments.get(position); if (f != null) { return f; } } if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } Fragment fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); if (mSavedState.size() > position) { Fragment.SavedState fss = mSavedState.get(position); if (fss != null) { fragment.setInitialSavedState(fss); } } while (mFragments.size() <= position) { mFragments.add(null); } fragment.setMenuVisibility(false); mFragments.set(position, fragment); mCurTransaction.add(container.getId(), fragment); return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment)object; if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment)object).getView()); while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); mFragments.set(position, null); mCurTransaction.remove(fragment); }
相同点: 二者在Adapter初始化上看上去没什么差别,都是基于 PagerAdapter
实现,只需要重写 getItem
返回每一页对应的 fragment
即可。
重点说一下不同点: 就像官方描述那样,我就不做翻译了的,从内部实现也确实印证了这一点.
前者 FragmentPagerAdapter
通过 mFragmentManager.findFragmentByTag(name)
从视图上把之前的视图找出来,然后执行 attach
, 找不到就回调 getItem
问实现方要 fragment
, 然而这个attach
有一个很隐藏的坑,我们都知道viewpager可以设置offsetLimit
, 也就是说会销毁limit之外的page,然而在使用 FragmentPagerAdapter
你会发现,当你再次滑动回来的时候,因为之前 destroyItem
仅仅走的是 detach
方法,所以通过 findFragmentByTag(name)
依旧能找到上次使用的 fragment
,但此时由于 ViewPager
本身实现层面上已经把它踢出了的,所以当走 mCurTransaction.attach(fragment)
的时候会走 fragment
的 onCreateView
方法重新loadView, 但是由于这个'fragment'实例还是上次初始化的那个实例,意味着其实数据(成员变量都在), 此时如果我们不重置一下数据的话就会出现一些逻辑上的bug. 所以网上就有人说可以这么搞:
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { //if (rootView == null) { rootView = inflater?.inflate(getRootViewLayoutResId(), container, false) /*} else run { val p = rootView!!.getParent() as? ViewGroup if (p != null) { p.removeAllViewsInLayout() } }*/
简单的说就是缓存一下上一次使用的view, 下次重新加载的时候就把缓存的rootView
重新添加到屏幕上,然而这种方案会不会让我们的内存占比比较大呢?
后者 FragmentStatePagerAdapter
是用一个数组ArrayList
去管理fragment
,当走到destroyItem
的时候fragment
会从这个ArrayList
中踢出掉:
mFragments.set(position, null);
所以每次确实是重新创建一个fragment
实例。但这句话也不完全正确,因为还有一个关键信息是State
. 我们可以看到 FragmentStatePagerAdapter
中有一个ArrayList
来管理页面的状态,当走到 destroyItem
的时候会保留即将销毁的这个fragment
的状态:
mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
总结
当我们在使用两个adapter切记要管理好fragment
的状态。。。。
更多相关文章
- Android(安卓)开源项目汇总
- Android中Context具体解释 ---- 你所不知道的Context
- Android(安卓)解决自定义控件布局中match_parent属性无效
- onSaveInstanceState和onRestoreInstanceState
- Android中使用httpclient访问服务器,需要session功能
- RK3288_Android7.1接eDP屏休眠之后led状态灯没有亮红色
- GPS定位及获取卫星参数实例整理
- Android(安卓)自定义FloatView实现悬浮视图
- 2017-06-10-Activity启动模式的区别