为了账号安全,请及时绑定邮箱和手机立即绑定

FragmentPagerAdapter和FragmentStatePagerAdapter源码中的三宝【上】

标签:
Android
前言

FragmentPagerAdapter和FragmentStatePagerAdapter是我们开发中经常遇到的两个类,尤其是和ViewPager的配合。几乎我们每个Android开发者都被Fragment和ViewPager,PopupWindow,适配等等一堆神坑折磨着,尤其是Fragment神坑无数,这些都是天天在用的组件,Google为什么留给我们这么多坑。也正因如此,为了不掉进坑里,就需要我们不断去填坑。

下面是通过阅读FragmentPagerAdapter和FragmentStatePagerAdapter能够学到的知识点:

  1. ViewPager刷新问题
  2. 适配器模式
  3. 观察者模式
区别一: 状态保存

我们在使用ViewPager的时候,经常使用下面几种方式:

ViewPager viewPager = findViewById(R.id.viewPager);
// 方式一
viewPager.setAdapter(new PagerAdapter() {
        private String mTitles[] ;
        private List<View> mViewList;

        @Override
        public int getCount() {
            return mViewList.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            View view = mViewList.get(position);
            container.addView(view);
            return view;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return mTitles[position];
        }
});

// 方式二
viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }
});

// 方式三
viewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
    @Override
    public Fragment getItem(int position) {
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        return fragments.size();
    }
});

用法大家都比较熟悉了,其中FragmentPagerAdapter 和 FragmentStatePagerAdapter有什么区别呢?

根据两个类的名称就可以知道FragmentStatePagerAdapter似乎是保存状态的,我们分别去这两个类找下它们的区别,发现它们都重写了父类PageAdapter的方法:

public abstract class PagerAdapter {
    // 省略 
    public static final int POSITION_UNCHANGED = -1;
    public static final int POSITION_NONE = -2;

    public Parcelable saveState() {
        return null;
    }

    public void restoreState(Parcelable state, ClassLoader loader) {

    }
}

分别查看它们的实现:

FragmentPagerAdapter的实现

@Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
    }

FragmentStatePagerAdapter的实现

 public Parcelable saveState() {
        Bundle state = null;
        if (mSavedState.size() > 0) {
            state = new Bundle();
            Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
            mSavedState.toArray(fss);
            state.putParcelableArray("states", fss);
        }
        for (int i=0; i<mFragments.size(); i++) {
            Fragment f = mFragments.get(i);
            if (f != null && f.isAdded()) {
                if (state == null) {
                    state = new Bundle();
                }
                String key = "f" + i;
                mFragmentManager.putFragment(state, key, f);
            }
        }
        return state;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        if (state != null) {
            Bundle bundle = (Bundle)state;
            bundle.setClassLoader(loader);
            Parcelable[] fss = bundle.getParcelableArray("states");
            mSavedState.clear();
            mFragments.clear();
            if (fss != null) {
                for (int i=0; i<fss.length; i++) {
                    mSavedState.add((Fragment.SavedState)fss[i]);
                }
            }
            Iterable<String> keys = bundle.keySet();
            for (String key: keys) {
                if (key.startsWith("f")) {
                    int index = Integer.parseInt(key.substring(1));
                    Fragment f = mFragmentManager.getFragment(bundle, key);
                    if (f != null) {
                        while (mFragments.size() <= index) {
                            mFragments.add(null);
                        }
                        f.setMenuVisibility(false);
                        mFragments.set(index, f);
                    } else {
                        Log.w(TAG, "Bad fragment at key " + key);
                    }
                }
            }
        }
    }

可以很容易看出只有FragmentStatePagerAdapter对Fragment的状态进行了保存,而FragmentPagerAdapter则是空实现。

虽然两个Adapter均有保存状态的代码,但是它们具体是在哪里被调用的呢?根据我们学过的Activity和Fragment的保存状态的方式,我们知道状态的恢复和保存一般在这些组件或者View里,的确,它们是在ViewPager中。

public class ViewPager extends ViewGroup {
  @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.position = mCurItem;
        if (mAdapter != null) {
            ss.adapterState = mAdapter.saveState();
        }
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        if (mAdapter != null) {
            mAdapter.restoreState(ss.adapterState, ss.loader);
            setCurrentItemInternal(ss.position, false, true);
        } else {
            mRestoredCurItem = ss.position;
            mRestoredAdapterState = ss.adapterState;
            mRestoredClassLoader = ss.loader;
        }
    }
}

因为ViewPager持有Adapter实例,所以ViewPager的onSaveInstanceState和onRestoreInstanceState方法都是间接调用Adapter来执行状态的恢复和保存的,我们看到ViewPager中间接调用了mAdapter.saveState()mAdapter.restoreState


区别二: 实例销毁 vs 视图销毁

除了上面的区别外,FragmentStatePagerAdapter和FragmentPagerAdapter唯一的区别就是对Fragment对象的处理了。

我们平常使用ViewPager + PageAdater时候需要重写很多方法,如开头的那几个案例,而ViewPager + FragmentPagerAdapter(FragmentStatePagerAdapter) 仅仅实现getItem和getCount两个方法就够了,核心方法instantiateItem和destroyItem内部已经做好了实现。

先看FragmentStatePagerAdapter类

 private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

 public Object instantiateItem(ViewGroup container, int position) {
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        // 实例化fragment(交给我们实现的getItem方法)
        Fragment fragment = getItem(position);

        if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }
        // 如果缓存 <= ViewPager传入的position,说明当前位置还未存入缓存.
        while (mFragments.size() <= position) {
            // 先占个坑
            mFragments.add(null);
        }
        fragment.setUserVisibleHint(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();
        }
        // 从缓存中移除
        mFragments.set(position, null);
        // 从FragmentManager中移除
        mCurTransaction.remove(fragment);
    }

再来看下FragmentPagerAdapter的两个实现方法:

@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) {
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        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();
    }
    mCurTransaction.detach((Fragment)object);
}

FragmentStatePagerAdapter 内部还做了个小缓存,这个不是重点,我们主要关注

FragmentStatePagerAdapter

mCurTransaction.add(container.getId(), fragment);
mCurTransaction.remove(fragment);

和FragmentPagerAdapter

mCurTransaction.attach(fragment);
mCurTransaction.add(container.getId(), fragment,makeFragmentName(container.getId(), itemId));
mCurTransaction.detach((Fragment)object);

很明显,FragmentStatePagerAdapter 对fragment进行完全的添加和删除操作,而FragmentPagerAdapter 则是对视图进行attach和detach。

总结:
FragmentStatePagerAdapter 适合大量页面,不断重建和销毁
FragmentPagerAdapter 适合少量页面,常驻内存。

适配器模式

因为Android这个知识点有两个设计模式的案例实在太经典了,所以想顺便拿来讲一下,理解了这些,开发过程中常见两个坑的问题:

  1. 懒加载
  2. 数据不更新
    经过查看源码就非常容易解决了!
public class ViewPager {

    private PagerAdapter mAdapter;
    public void setAdapter(PagerAdapter adapter) {
        adapter.xx();
        adapter.xxx();
        this.mAdapte = adapter;

        // ....
        requestLayout();
    }

    public void dataSetChanged() {
         final int adapterCount = mAdapter.getCount();
         // ....
         mAdapter.destroyItem(this, ii.position, ii.object);
         // ....
         // ....
    }
}

可以看到ViewPager持有的是PagerAdapter,ViewPager中间接调用了很多PagerAdapter的方法,使用组合方式来代替继承方式解耦。
怎么看着那么像模板方法模式呢,设计模式中很多模式确实太像了,比如代理模式装饰器模式

组合优于继承,总之,能用组合实现的不要用继承。
前不久我看一个开源项目的代码,大量的继承和模板方法模式,看的我真的快怀疑自己智商了。

伪观察者模式

再来看下ViewPager的代码:

public class ViewPager {
    // 观察者
    private PagerObserver mObserver;
    private PagerAdapter mAdapter;
    public void setAdapter(PagerAdapter adapter) {
        adapter.xx();
        adapter.xxx();
        this.mAdapte = adapter;
        if (mAdapter != null) {
            if (mObserver == null) {
                // 实例化观察者对象
                mObserver = new PagerObserver();
            }
            // 传递一个观察者mObserver对象供adapter调用
            mAdapter.setViewPagerObserver(mObserver);
        }
        // ....
        requestLayout();
    }

    public void dataSetChanged() {
         final int adapterCount = mAdapter.getCount();
         // ....
         mAdapter.destroyItem(this, ii.position, ii.object);
         // ....
         // ....
    }

    /**
     * 观察者对象 
     */
    private class PagerObserver extends DataSetObserver {
        PagerObserver() {
        }

        @Override
        public void onChanged() {
            dataSetChanged();
        }
        @Override
        public void onInvalidated() {
            dataSetChanged();
        }
    }
}

在setAdapter中mAdapter.setViewPagerObserver(mObserver);
这里传递了一个内部类对象名称叫 PagerObserver,大家要注意了,这个地方虽然起名叫做观察者,我认为是不合理的,确实Android提供给我们一个注册观察者的接口来监听(后面详细讲),不过我们常常用notifyDataChange() 来通知ViewPager数据更新这里的默认实现 并没有真正用 观察者模式,可能是Google偷懒了吧。
mAdapter.setViewPagerObserver(mObserver)传递的这个对象 更像回调。回调接口的本质不就是传递一个对象吗?? C语言的实现则是传递指针。JavaScript传递function。

看下我们经常调用的notifyDataSetChanged方法:

public abstract class PagerAdapter {
    // 被观察者,暂时不用管
    private final DataSetObservable mObservable = new DataSetObservable();
    // 冒充者,虽然也叫观察者对象,但实际算是个回调对象
    private DataSetObserver mViewPagerObserver;

    void setViewPagerObserver(DataSetObserver observer) {
        synchronized (this) {
            mViewPagerObserver = observer;
        }
    }

    public void notifyDataSetChanged() {
        synchronized (this) {
            if (mViewPagerObserver != null) {
                mViewPagerObserver.onChanged();
            }
        }
        mObservable.notifyChanged();
    }
}

注释写的很明白,PagerAdapter里面怎么可能同时充当观察者和被观察者嘛,notifyDataSetChanged没有用观察者模式实现。

但是我们注意到了,notifyDataSetChanged方法的最后调用了
mObservable.notifyChanged();
这里才是真正的观察者模式,被观察者准备调用自己的方法通知所有的观察者 数据改变了。可惜的是当前 目前还木有人注册,孤芳自赏!

这里暂时做个标记,我们最后在看Android的观察者模式设计。

继续跟踪代码,notifyDataSetChanged调用了mViewPagerObserver这个伪娘的onChanged方法(ViewPager中),
onChanged()调用了ViewPager的dataSetChanged方法:

  void dataSetChanged() {
        // This method only gets called if our observer is attached, so mAdapter is non-null.

        final int adapterCount = mAdapter.getCount();
        mExpectedAdapterCount = adapterCount;
        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
                && mItems.size() < adapterCount;

        // newCurrItem 用于跟踪标记当前ViewPager的所在页
        int newCurrItem = mCurItem;

        boolean isUpdating = false;
        // 遍历ViewPager中所有的items(每个ItemInfo中包含着fragment实例,position等信息)
        for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
            // getItemPosition方法是我们根据需要重写的方法,有三种值: POSITION_UNCHANGED和POSITION_NONE和pos(int类型)
            final int newPos = mAdapter.getItemPosition(ii.object);

            // (1). 如果getItemPosition()返回值是POSITION_UNCHANGED(默认实现),不做处理
            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
                continue;
            }

            // (2). 如果getItemPosition()返回值是POSITION_NONE,移除ViewPager的mItems中当前正在遍历着的ItemInfo
            if (newPos == PagerAdapter.POSITION_NONE) {
                mItems.remove(i);
                i--;

                if (!isUpdating) {
                    // 方法内没什么实际意义
                    mAdapter.startUpdate(this);
                    isUpdating = true;
                }
                // 同时调用adapter的销毁方法销毁当前遍历着的ItemInfo
                mAdapter.destroyItem(this, ii.position, ii.object);
                needPopulate = true;

                continue;
            }
            // (3). 如果getItemPosition()返回值是其他的值(如newPos = 3),则相当于把[首次初始化的ViewPager中ItemInfo的position]重新赋值为指定的值.换个位置,这个特性一般我们很少用到.
            if (ii.position != newPos) {
                if (ii.position == mCurItem) {
                    // 如果当前for循环中遍历的ItemInfo.position正好等于ViewPager中的当前页下标,跟踪标记
                    newCurrItem = newPos;
                }

                ii.position = newPos;
                needPopulate = true;
            }
        }

        if (isUpdating) {
            mAdapter.finishUpdate(this);
        }

        Collections.sort(mItems, COMPARATOR);

        // 根据前面的分析, (2) 和 (3)都会导致重新请求布局
        if (needPopulate) {
            // Reset our known page widths; populate will recompute them.
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (!lp.isDecor) {
                    lp.widthFactor = 0.f;
                }
            }
            // 设置当前页,并重新布局或者是滚动到此页
            setCurrentItemInternal(newCurrItem, false, true);
            requestLayout();
        }
    }

这个方法逻辑稍有点多,分析都写在注释里了。

根据下面这段代码

boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
                && mItems.size() < adapterCount;

我们知道给ViewPager新添加View或Fragment没有任何问题.它会自动处理,但是更新 就有问题了,我们可能希望把某个页面替换掉,比如A->B.

但是根据这段代码的逻辑,不重写getItemPosition方法(默认POSITION_UNCHANGED)的话是不会有任何变化的。

通过重写getItemPosition()方法

final int newPos = mAdapter.getItemPosition(ii.object);
// ... 
mAdapter.destroyItem(this, ii.position, ii.object);

我们可以看到ViewPager中只要有返回POSITION_NONE的项,那么就会销毁该项并刷新。

但是不建议大家直接在adapter中这么干(虽然我是这么干的,懒人):
反例如下:

@Override
public int getCount() {
    return this.mFragmentList.size();
}

public int getItemPosition(Object object) {
    return POSITION_NONE;
}

@Override
public Fragment getItem(int position) {
    return mFragmentList.get(position);
}

这样会导致调用notifyDataChange时候ViewPager中每个Fragment都会被 mAdapter.destroyItem。我们只是想更新某个Item就够了,这一下子全部都
destroyItem一遍,性能肯定造成浪费。

大家可以根据自己的逻辑修改进行实现,其中object就是Fragment对象或view对象,比如设置tag之类的,只令某一项返回POSITION_NONE。

public int getItemPosition(Object object) {
    return POSITION_NONE;
}

下篇续:源码中的两大设计模式

点击查看更多内容
13人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消