【小学生Android】二、我们来谈谈ViewPager的刷新机制存在的问题及其解决方案

**

参考文章:

**
https://www.jianshu.com/p/266861496508
http://blog.sina.com.cn/u/2017385987
https://blog.csdn.net/z13759561330/article/details/40737381
https://blog.csdn.net/happylishang/article/details/78961984

**

目录

**

一.问题复现

1.从ViewPager的3种适配器说起
这里我们先来盘一下ViewPager的三种适配器

PagerAdapter:
 重写:getCount()、isViewFromObject()、instantiateItem()、destroyItem()
 对应的item为View;

FragmentPagerAdapter:
 重写:getCount()、getItem()、
 对应的item为Fragment,适用于Fragment较少的情况;
 会缓存所有Fragment,为每个fragment添加Tag,需要的时候根据Tag查找;
 首次显示时候add,非首次根据Tag查找然后attach;
 移除的时候detach,注意这里并未remove;

FragmentStateAdapter:
 重写:getCount()、getItem()
 对应item为Fragment,适用于Fragment较多的情况;
 只缓存最近显示的Fragment ,不会为fragment添加Tag ,需要的时候如果没有缓存就重新创建;
 显示时候如果没有缓存就add,有缓存直接显示
 移除的时候remove

2.问题来了,刷新dapter的数据源 Viewpager并不能及时正确地刷新UI页面
 操作步骤:
  首先,对dapter进行增删改的操作
  然后调用adapter.notifyDateSetChanged()观察ViewPager的页面
 亲测结果如下:

PagerAdapterFragmentPagerAdapterFragmentStateAdapter
更新Item生效但不及时不生效生效但不及时
增加Item生效但不及时不生效crash
删除Item页面错乱不生效crash
清空Item残留当前页面OK残留当前页面

二. 问题分析

1.知其然——问题很严重
更新生效但不及时: C级bug
更新新无效: C级+bug
页面错乱: B级bug
crash: A级bug
试想一下,如果我们自己的app出现这种bug,是何等的事故哟!但是这种事情就实实在在地发生了,而且还是发生在拥有10亿级用户的Android操作系统的官方api中!

2.知其所以然——Why!
让我们来分析下这个问题的可能原因:
原因一、我们自己写的adapter有问题。
原因二、这是bug。

排除法 搞起
case 原因二: 这个bug能发布出来只有一种情况——Google发布Android的api连冒烟测试都没通过。可能性几乎为0 排除
case 原因一:是我们的代码写得不够规范。可能性较大
那么我们的代码那里写的有问题呢?为什么按Google的api只重写几个PagerAdapter必要的抽象方法,在数据源更新的时候,调用notifyDateSetChanged()不能达到像ListView和RecyclerView那样的效果呢?
这一切都要从PagerAdapter的刷新机制说起。
下图是刷新单个Item的流程图:

Created with Raphaël 2.2.0 开始 NotifyDateSetChanged 调用getItemPosition 是否=POSITION_NONE destroyItem instantiateItem 结束 yes no

调用流程:

  1. 调用 PagerAdapter 的 notifyDataSetChanged() 方法
  2. 触发 getItemPosition() 方法,该方法会为每个 Item 返回状态码POSITION_NONEPOSITION_UNCHANGED
  3. 如果是 POSITION_NONE,那么该 Item 会被 destroyItem() 方法 remove 掉,然后instantiateItem()重新加载 显示刷新后的页面
  4. 如果是 POSITION_UNCHANGED,就不会重新加载,默认是 POSITION_UNCHANGED,无刷新效果

那么问题来了,这个getItemPosition()是干什么的,我跟ta 认识,吗?
我们回过头来再来回顾我们写的PagerAdapter都重写了哪些方法
PagerAdapter:
 重写:getCount()、isViewFromObject()、instantiateItem()、destroyItem()

FragmentPagerAdapter:
 重写:getCount()、getItem()、

FragmentStateAdapter:
 重写:getCount()、getItem()

并没有找到这个getItemPosition()啊,别急既然这方法是PagerAdapter的,我们去源码里找下。


public int getItemPosition(@NonNull Object object) {
        return PagerAdapter.POSITION_UNCHANGED;//默认返回POSITION_UNCHANGED
    }

...

哈哈,“真相只有一个!”就是因为父类PagerAdapter的getItemPosition()直接返回POSITION_UNCHANGED,才导致这一连串的刷新问题。
那么,我们是不是只要在子类中重写该方法返回POSITION_NONE就万事大吉了呢?So,easy?妈妈再也不用担心我的Viewpager不刷新?

老办法,我们试一下子!

PagerAdapterFragmentPagerAdapterFragmentStateAdapter
更新ItemOK不生效OK
增加ItemOK不生效OK
删除ItemOK不生效OK
清空ItemOKOKOK

三. 解决方案

1.PagerAdapter 重写该方法返回POSITION_NONE 可以解决刷新问题
2.FragmentPagerAdapter 效果并不理想 这跟它内部对Fragment的缓存有关
3.FragmentStateAdapter 重写该方法返回POSITION_NONE 可以解决刷新问题

@Override
    public int getItemPosition(@NonNull Object object) {
        return POSITION_NONE;//返回POSITION_NONE 强制刷新
    }

关于1和3 的【可以解决】这里有几点说明一下:
First…我们这里所谓的刷新数据源包括:增加,删除、更新、清空
Second…这里的刷新虽然能达到效果,但是所有Item都会刷新,不能实现类似RecyclerView只更新单个tem的效果;
Third…如果只是更新数据源 不涉及动态增加、删除、清空操作,那么 可以这样优化:
 通过为每个Item设置Tag,然后重建的时候根据Tag决定是否更新
注意 这种Tag方式只能保证更新操作不会出问题

关于2FragmentPagerAdapter的【效果并不理想
尝试的解决方案是这样的,先讲下思路:
 在Adapter内维护一个所有fragment的集合fragments,注意这个集合需要在adapter内通过new创建,不能由外部指定,加载数据需要先clear 再addAll();
  每次外部数据源list有刷新操作的时候:
  然后通过fragmentManager将所有fragment移除
  先将内部维护的fragemnt清空,
  接着内部集合fragments 再addAll()
  最后调用notifyDataSetChanged()
完整代码如下:

package com.darcy.hellotest.ui.viewpager.adapter;

import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

public class FragmentsAdapter extends FragmentPagerAdapter {

    private List<Fragment> fragments;//内维护一个所有fragment的集合fragments

    private FragmentManager fragmentManager;

    public FragmentsAdapter(FragmentManager fm, List<Fragment> list) {
        super(fm);
        this.fragmentManager = fm;
        fragments = new ArrayList<>();//集合需要在adapter内通过new创建
        setFragments(list);
    }

    /**
     * 刷新 通过修改adapter数据源实现
     * 每次更新刷新所有fragment
     *
     * @param fragments 新数据源
     */
    public void setFragments(List<Fragment> fragments) {
        if (this.fragments != null) {
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            for (Fragment f : this.fragments) {//通过fragmentManager将所有fragment移除
                transaction.remove(f);
            }
            transaction.commit();
            fragmentManager.executePendingTransactions();
        }
        assert this.fragments != null;
        this.fragments.clear();//清空
        this.fragments.addAll(fragments);//addAll操作
        notifyDataSetChanged();//调用notifyDataSetChanged()
    }

    @Override
    public Fragment getItem(int i) {
        return fragments.get(i);
    }

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

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        return super.instantiateItem(container, position);
    }

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

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

}

Activity中的刷新调用代码如下:

    //真更新 通过修改适配器数据源实现
    private void refresh2() {
        fragments.set(0, PagerFragment.newInstance("更新测试"));
        mPagerAdapter.setFragments(fragments);
    }

    private void add() {
        fragments.add(0, PagerFragment.newInstance("添加Item测试"));
        mPagerAdapter.setFragments(fragments); 
    }

    private void delete() {
        fragments.remove(0);
        mPagerAdapter.setFragments(fragments);
    }

    private void clean() {
        fragments.clear();
        mPagerAdapter.setFragments(fragments);
    }

这个方法的实验结果如下:

FragmentPagerAdapter
更新Item有效 但是会导致下一个页面首次加载时空白
增加Item有效 但是会导致下一个页面首次加载时空白
删除Item有效 但是会导致下一个页面首次加载时空白
清空ItemOK

很显然 目前这个方案还不完善,哪位大佬知道,还望不吝赐教,这里先行谢过了(教我,让我做你的舔狗~~~)
所以我现在的做法是向FragmentStatePagerAdapter靠拢,既然FragmentPagerAdapter效果不理想,那就先拿FragmentStatePagerAdapter救急。

四. 总结

1.以上我们复现了ViewPager的刷新机制存在的问题——不能及时正确地更新页面
2.接着我们分析了这种情况出现的原因——PagerAdapter的getItemPosition()直接返回POSITION_UNCHANGED 导致页面不刷新
3.然后我们提出了解决方案:在子类中重写该方法返回POSITION_NONE
  这个方案针对PagerAdapter和FragmentStatePagerAdapter效果理想
  对FragmentPagerAdapter由于其内部的缓存机制 效果并不理想
4.最后我们还尝试针对FragmentPagerAdapter的缓存机制进行优化——强制清除缓存,但是效果也有瑕疵。期待大佬出来指点江山~~

此致:
我是漩涡小学生

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值