FragmentStatePagerAdapter的notifyDataSetChanged优化方案
首先上图:
接下来我们先来分析下布局
很容易可以看出是radiogroup+指示器+viewpager+fragment来展示。一开始我按照正常的方式来做,会发现有几个问题。第一:这里有十几个fragment,一进来就会执行十几次网络请求。虽说网络请求都是异步执行,但是这个一是浪费流量,二是总感觉有点不对劲慌慌的,虽然我也做了退出取消网络的方法。第二,每次我点击昨天这个按钮时,notifyDataSetChanged方法好像没起作用。
针对问题一:
我采用fragment的懒加载属性+viewpager的缓存属性,可以得到当fragment页面可见时才执行网络请求。而viewpager的缓存属性,可以防止fragment重复加载。只需要两个标记位即可。需要注意的是setUserVisibleHint方法并不可控,也就意味着setUserVisibleHint方法有可能在view初始化之前执行,那这个时候有那么一丝可能会造成,网络请求成功但是view是null的情况。所以这个地方加入了一个isInit是否view初始化完成的判断。
//fragment实现懒加载的关键代码
public abstract class BaseLazyFragment extends BaseFragment
{
private static final String TAG = "BaseLazyFragment";
protected boolean isInit = false;
protected boolean isGetData = false;
@Override
public void doBusiness(Context context)
{
if (getUserVisibleHint())
{
visibleToPerform();
}
isInit = true;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser)
{
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint() && isInit && !isGetData)
{
visibleToPerform();
}
}
public abstract void visibleToPerform();
}
针对问题二:
notifyDataSetChanged方法中进入源码可以看到会走viewpager的dataSetChanged()方法。
下面看关键代码有关键两步:
1.会调用adapter中的getItemPosition方法;
2.会根据getItemPosition方法返回的int值来进行判断是否重建itemView;
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;
int newCurrItem = mCurItem;
boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
//1.首先会调用getItemPosition方法
final int newPos = mAdapter.getItemPosition(ii.object);
//2.会判断getItemPosition方法的返回值,只有等于PagerAdapter.POSITION_NONE时
//才会将其销毁并重建
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
if (mCurItem == ii.position) {
// Keep the current item in the valid range
newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
needPopulate = true;
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
所以我一开始的初步解决方案是重写adapter中的getItemPosition方法如下所示:
@Override
public int getItemPosition(Object object)
{
return POSITION_NONE;
}
问题貌似已经解决了。但是,等等,为什么我每次点击昨天,今天按钮切换的时候,会有将近1000ms的卡顿!作为一个追求完美的开发者来说,真的有点不能忍。然后马上我就意识到,上面那个方法其实是最差劲的方法,是最无奈的一种方法。因为他把所有的fragment都销毁了,然后进行重建,十几个啊,所以造成了UI界面的卡顿现象。所以必须想出一种优化方法,我想了下,最后我的优化方式如下:
@Override
public int getItemPosition(Object object)
{
//针对 pageAdapter notifychangeData的优化 无卡顿
Log.e(TAG, "getItemPosition:" + mPosition);
RegionOnlineRequestEntity entity = mDatas.get(0);
if (object instanceof AreaOnlineChildFragment)
{
((AreaOnlineChildFragment) object).setRequestEntity(entity);
if (((AreaOnlineChildFragment) object).getUserVisibleHint())
{
((AreaOnlineChildFragment) object).visibleToPerform();
}
}
return super.getItemPosition(object);
}
这里面有可以在setRequestEntity做一些请求实体类的数据改变操作和挺关键是isGetData设置为false。然后再判断当前fragment是否可见,再进行刷新操作。这样的话,每次刷新就只会刷新一个页面,在我两年多年前买的千元Android机器上都没有卡顿效果哦!
以上。
下篇博客:动手系列一:动手写一个状态布局