PagerApapter、FragmentPageAdapter、FragmentStatePageAdapter源码解析

来源:互联网 发布:洗衣机选购 知乎 编辑:程序博客网 时间:2024/06/06 05:10

PagerApapter、FragmentPageAdapter、FragmentStatePageAdapter是我们使用ViewPager的时候,常用的三个适配器。关于三者的区别网上一大堆资料【感觉这个总结的不错】,今天从源码的角度看看,这些区别的背后到底是怎样的存在。

1、ViewPager是如何与Adapter建立联系的

ViewPager通过setAdapter方法与适配器建立联系,并在这个方法中向对应的适配器注册Observer,这是典型的观察者模式,当数据发生变化的时候,适配器通过回调Observer的相关方法,通知ViewPager数据已经变化了,需要刷新页面喽。

ViewPager的setAdapter()部分源码:

       if (mAdapter != null) {            if (mObserver == null) {                mObserver = new PagerObserver();            }            mAdapter.setViewPagerObserver(mObserver);
可以看出,将PagerApapter设置给了ViewPager,同时ViewPager向PagerApapter注册了一个Observer。这个ObServer在PagerApapter中怎么使用的呢?
 public void notifyDataSetChanged() {        synchronized(this) {            if(this.mViewPagerObserver != null) {                this.mViewPagerObserver.onChanged();            }        }        this.mObservable.notifyChanged();    }

可以看到,当我们主动调用notifyDataSetChanged()的时候,会回调ObServer的onChanged方法,通知ViewPager数据发生了改变。

2、FragmentPageAdapter、FragmentStatePageAdapter区别

两者主要的区别在于对已经添加进来的Fragment的处理。FragmentPageAdapter会将添加进来的Fragment都保存在内存中,因此FragmentPageAdapter适合少量的Fragment情况。FragmentStatePageAdapter仅保存当前页面的Fragment,当页面不可见的时候,会remove掉,因此FragmentStatePageAdapter适合量比较大的Fragment情况。

在查看源码之前,需要搞清楚两个常量的含义:

public static final int POSITION_UNCHANGED = -1;//不做任何处理public static final int POSITION_NONE = -2;//会调用Adapter的destoryItem方法
这两个常量,尤其是第二个,我们经常会在getItemPosition中使用,如果返回POSITION_UNCHANGED,即使notifyDataSetChanged了,也不会有任何效果 ,有源码为证:

ViewPager的dataSetChanged()方法源码(该方法在PageObserver.onChanged()中调用):

   if (newPos == PagerAdapter.POSITION_UNCHANGED) {                continue;            }            if (newPos == PagerAdapter.POSITION_NONE) {               ......                mAdapter.destroyItem(this, ii.position, ii.object);

当我们主动调用notifyDataSetChanged的时候,ViewPager就会执行这段代码,如果发现是POSITION_UNCHANGED, 则什么都不做。只有是POSITION_NONE,才会执行Adapter的destoryItem方法。  

FragmentPageAdapter、FragmentStatePageAdapter的区别主要体现在instantiateItem()和destroyItem()方法上。

a)FragmentPageAdapter

instantiateItem():

Fragment fragment = mFragmentManager.findFragmentByTag(name);        if (fragment != null) {            mCurTransaction.attach(fragment);        } else {            fragment = getItem(position);            mCurTransaction.add(container.getId(), fragment,                    makeFragmentName(container.getId(), itemId));        }
FragmentPageAdapter会将添加进来的Fragment都保存在内存中,是怎么体现出来的呢?先看第一句代码:mFragmentManager.findFragmentByTag(name);这是查找操作,这说明之前肯定有保存了,如果查找到了Fragment存在,则直接attach使用之前已经存在的Fragment,不再重新生成新的Fragment了。否则,才会去调用getItem方法生成新的Fragment了。

为了验证【之前肯定有保存了】,我们看一下destoryItem()的源码

 public void destroyItem(ViewGroup container, int position, Object object) {        if (mCurTransaction == null) {            mCurTransaction = mFragmentManager.beginTransaction();        }        mCurTransaction.detach((Fragment)object);    }
有没有发现,FragmentPageAdapter在执行销毁操作的时候,只是简单的detach,并没有remove掉,这验证了上面的说法--FragmentPageAdapter会将添加进来的Fragment都保存在内存中。

b)FragmentStatePageAdapter

instantiateItem():

if (mCurTransaction == null) {            mCurTransaction = mFragmentManager.beginTransaction();        }        Fragment fragment = getItem(position);........mCurTransaction.add(container.getId(), fragment);
FragmentStatePageAdapter每次都是直接调用getItem生成新的Fragment的。

destoryItem():

       Fragment fragment = (Fragment) object;        if (mCurTransaction == null) {            mCurTransaction = mFragmentManager.beginTransaction();        }        ......        mCurTransaction.remove(fragment)
在销毁的处理上,也是很干脆的直接remove掉,并没有在内存中保存。

3、刷新问题

PagerApapter是FragmentPageAdapter、FragmentStatePageAdapter的父类。在PagerApapter中有一个getItemPosition方法,该方法的返回值在ViewPager的dataSetChanged()方法中会用到。如果是POSITION_UNCHANGED,则什么都不做,如果是POSITION_NONE,则强制刷新。默认情况下,getItemPosition()返回POSITION_UNCHANGED。

public int getItemPosition(Object object) {        return POSITION_UNCHANGED;    }
因此就会产生刷新问题,虽然数据源发生了变化,我们也调用了notifyDataSetChanged但是没有任何效果。有的同学说了,直接重写getItemPosition返回POSITION_NONE,强制刷新,问题不就解决了。没错,对于PagerApapter确实可以解决问题,但是FragmentPageAdapter还是存在原来的问题。为什么呢?ViewPager在执行dataSetChanged方法时,会调用对应的Adapter的destoryItem,但是别忘了,FragmentPageAdapter会将Fragment保存在内存中的,所以即使你返回了POSITION_NONE,你使用的还是之前的Fragment,还是无任何效果。解决方式在这篇文章的底部。

原创粉丝点击