大家看标题,可能会有点儿懵,什么是ViewPagers,因为在很久之前,我们使用的都是ViewPager,但是现在更多的是在用ViewPager2,因此用ViewPagers(ViewPager、ViewPager2)来代替两者,主要介绍两者的区别。
ViewPagers嵌套Fragment架构,在我们常用的App中随处可见,抖音的首页、各大电商app首页(淘宝、京东、拼多多)等,通过左右滑动切换Tab;但因为ViewPager的预加载机制存在,我们先看下ViewPager的源码:
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
当我们设置offscreenPageLimit(离屏加载)的数值时,我们可以看到limit的值是有限制,不能小于DEFAULT_OFFSCREEN_PAGES
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
那么就意味着ViewPager默认支持预加载,我们看下面这张图
如需完整版Android性能优化文件 请点击免费领取
如果红色区域默认为首页,根据ViewPager默认预加载的阈值,那么左右两边的页面同样也会被加载,如果有网络请求,也就是说,我们没有打开左边的页面,它已经默认进行了网络请求,这种体验是非常差的,因为会在暗地里消耗流量。
理想情况下,我们需要的是打开某个页面的时候才去加载,这里就需要通过懒加载的方式优化。
1 ViewPager懒加载优化
1.1 ViewPager的缓存机制
很多时候,我们在使用Fragment的时候,发现打开过的页面再回来,页面没有重建刷新,很多人觉得是Fragment是有缓存的,其实并不是Fragment有缓存,而是ViewPager具备缓存能力;
如果有小伙伴使用过单Activity + 多Fragment架构的时候就会发现,打开过的页面再次返回的时候,Fragment会被重建,所以两种架构都有利弊,关键看我们怎么选择,下面我们看下ViewPager的缓存机制。
public void setAdapter(@Nullable PagerAdapter adapter) {
if (mAdapter != null) {
①
mAdapter.setViewPagerObserver(null);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
mAdapter.finishUpdate(this);
mItems.clear();
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
②
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
③
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
mPopulatePending = false;
final boolean wasFirstLayout = mFirstLayout;
mFirstLayout = true;
mExpectedAdapterCount = mAdapter.getCount();
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else if (!wasFirstLayout) {
④
populate();
} else {
⑤
requestLayout();
}
}
// Dispatch the change to any listeners
if (mAdapterChangeListeners != null && !mAdapterChangeListeners.isEmpty()) {
for (int i = 0, count = mAdapterChangeListeners.size(); i < count; i++) {
mAdapterChangeListeners.get(i).onAdapterChanged(this, oldAdapter, adapter);
}
}
}
核心方法就是setAdapter,像RecyclerView一样,因为会有缓存,所以当页面滑动的时候,如果缓存中存在页面,那么就会从缓存中取,如果没有,就需要去创建新的页面,所以我们先来关注一下PagerAdapter
public abstract class PagerAdapter {
private final DataSetObservable mObservable = new DataSetObservable();
private DataSetObserver mViewPagerObserver;
public static final int POSITION_UNCHANGED = -1;
public static final int POSITION_NONE = -2;
public abstract int getCount();
//开始更新
public void startUpdate(@NonNull ViewGroup container) {
startUpdate((View) container);
}
//初始化页面
@NonNull
public Object instantiateItem(@NonNull ViewGroup container, int position) {
return instantiateItem((View) container, position);
}
//销毁页面
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
destroyItem((View) container, position, object);
}
//结束刷新
public void finishUpdate(@NonNull ViewGroup container) {
finishUpdate((View) container);
}
}
PagerAdapter是一个抽象类,那么这些方法肯定是具体实现类实现,如果我们在使用ViewPager嵌套Fragment的时候,使用的是FragmentPageAdapter
接着回到setAdapter方法中
①:有一个全局变量 mAdapter,如果是第一个加载进来,那么mAdapter是空的,走到②
②:这里就是将我们传入的adapter给mAdapter赋值
③:这个时候mAdapter不为空,这里需要关注几个参数:
wasFirstLayout = true
mRestoredCurItem = -1
所以这里直接走到⑤,调用requestLayout方法,会执行到onMeasure,在这个方法中,会执行populate方法(这个大家自己去爬楼)
populate干了什么呢?代码太多了就不贴出来了,直接上图
如果是默认缓存(mOffscreenPageLimit = 1),那么在mItems就会缓存3个Fragment
private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
当页面滑动时,page2成为了当前页,那么ViewPager的populate做了什么操作呢?