好久没有记录bug了的,因为最近一直在解bug... 之所以写这个文章,确实是出于无奈,毕竟同一个问题,项目上遇到了两次。这里主要是提一下 ViewPager + Fragment。

ViewPager + Fragment 有 '两种' Adapter

  1. 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);    }
  1. 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) 的时候会走 fragmentonCreateView方法重新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 mSavedState来管理页面的状态,当走到 destroyItem 的时候会保留即将销毁的这个fragment的状态:

  mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));

总结

当我们在使用两个adapter切记要管理好fragment的状态。。。。

更多相关文章

  1. Android(安卓)开源项目汇总
  2. Android中Context具体解释 ---- 你所不知道的Context
  3. Android(安卓)解决自定义控件布局中match_parent属性无效
  4. onSaveInstanceState和onRestoreInstanceState
  5. Android中使用httpclient访问服务器,需要session功能
  6. RK3288_Android7.1接eDP屏休眠之后led状态灯没有亮红色
  7. GPS定位及获取卫星参数实例整理
  8. Android(安卓)自定义FloatView实现悬浮视图
  9. 2017-06-10-Activity启动模式的区别

随机推荐

  1. Android(安卓)支持多屏幕机制
  2. Android(安卓)DEX方法超过64K和gradle编
  3. Android(安卓)SystemUI任务栏修改
  4. Configuration on demand is not support
  5. Android(安卓)WebView ScrollBar设置
  6. MySQL: 基于 Android(安卓)远程连接
  7. android获取系统铃声并播放
  8. Android(安卓)照相机的实例应用
  9. Android(安卓)ImageView显示网络图片
  10. Android中模拟器如何访问本地mysql数据库