美文网首页Android开发Android开发经验谈
01 ViewPager源码解析与性能优化

01 ViewPager源码解析与性能优化

作者: 凤邪摩羯 | 来源:发表于2020-10-24 10:11 被阅读0次

1 ViewPager布局之因

image.png
  • 原因:
    根据源码的onMeasure()方法,由于他先测量的自己,其MeasureSpec值和自身没有关系,只和父控件的限制有关.
//ViewPager源码
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // For simple implementation, our internal size is always 0.
        // We depend on the container to specify the layout size of
        // our view.  We can't really know what it is since we will be
        // adding and removing different arbitrary views and do not
        // want the layout to change as this happens.
        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
                getDefaultSize(0, heightMeasureSpec));
        ...
}
  • 解决方案
    重写ViewPager 的onMeasure()方法,测量子view的height,将其中的最大值设置为ViewPager的heightMeasureSpec后,再调用super.onMeasure(widthMeasureSpec, heightMeasureSpec);传入参数
public class MyViewPager extends ViewPager {
    private static final String TAG = "MyViewPager";
    public MyViewPager(@NonNull Context context) {
        super(context);
    }

    public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int height = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED));
            int h = child.getMeasuredHeight();
            if (h > height) {
                height = h;
            }
            Log.d(TAG, "onMeasure: "  + h);
        }
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

2 缓存机制源码层解析

image.png
  • 为什么设置的缓存页面数不能低于1
//源码
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();
        }
    }

DEFAULT_OFFSCREEN_PAGES的值=1,如果设置的值小于1,则源码中设置为1.

  • 2 populate()方法进行布局和缓存
    populate()生命周期和adapter函数紧密绑定.意味着adapter所有流程都由populate()控制->每个item的管理都由populate()->缓存由populate()控制


    image.png

3 ViewPager常规懒加载

image.png
image.png
  • 生命周期


    image.png
    image.png
  • Framement三种状态分发


    image.png
image.png

4 ViewPager深层嵌套懒加载

  • 问题复查


    image.png
image.png
  • 解决方案


    image.png

5 懒加载的BaseFragment基类示例

public abstract class LazyFragment3 extends Fragment {
    private static final String TAG = "LazyFragment3";
    //fragment 生命周期:
    // onAttach -> onCreate -> onCreatedView -> onActivityCreated -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDestroy -> onDetach
    //对于 ViewPager + Fragment 的实现我们需要关注的几个生命周期有:
    //onCreatedView + onActivityCreated + onResume + onPause + onDestroyView

    protected View rootView = null;
    boolean isViewCreated = false;
    boolean currentVisibleState = false;
    boolean mIsFirstVisible = true;
    FragmentDelegater mFragmentDelegater;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        if (rootView == null) {
            rootView = inflater.inflate(getLayoutRes(), container, false);
        }
        initView(rootView);
        isViewCreated = true;
        logD("onCreateView: ");
        //初始化的时候,判断当前fragment可见状态
        //todo, isHidden()什么时候调用?原理
        if (!isHidden() && getUserVisibleHint()) {
            dispatchUserVisibleHint(true);
        }
        return rootView;
    }

    protected abstract int getLayoutRes();

    protected abstract void initView(View view);



    //修改fragment的可见性
    //setUserVisibleHint 被调用有两种情况:1) 在切换tab的时候,会先于所有fragment的其他生命周期,先调用这个函数,可以看log
    //2)对于之前已经调用过setUserVisibleHint 方法的fragment后,让fragment从可见到不可见之间状态的变化
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        logD("setUserVisibleHint: " + isVisibleToUser);
        //  对于情况1)不予处理,用 isViewCreated 进行判断,如果isViewCreated false,说明它没有被创建
        if (isViewCreated) {
            //对于情况2)要分情况考虑,如果是不可见->可见是下面的情况 2.1),如果是可见->不可见是下面的情况2.2)
            //对于2.1)我们需要如何判断呢?首先必须是可见的(isVisibleToUser 为true)而且只有当可见状态进行改变的时候才需要切换,否则会出现反复调用的情况
            //从而导致事件分发带来的多次更新
            if (isVisibleToUser && !currentVisibleState ) {
                dispatchUserVisibleHint(true);
            } else if (!isVisibleToUser && currentVisibleState) {
                dispatchUserVisibleHint(false);
            }
        }
    }

    /**
     * 统一处理用户可见信息分发
     * @param isVisible
     */
    private void dispatchUserVisibleHint(boolean isVisible) {
        logD( "dispatchUserVisibleHint: " + isVisible);
        //事实上作为父 Fragment 的 BottomTabFragment2 并没有分发可见事件,
        // 他通过 getUserVisibleHint() 得到的结果为 false,首先我想到的
        // 是能在负责分发事件的方法中判断一下当前父 fragment 是否可见,
        // 如果父 fragment 不可见我们就不进行可见事件的分发
        if (isVisible && isParentInvisible()) {
            return;
        }
        //为了代码严谨
        if (currentVisibleState == isVisible) {
            return;
        }
        currentVisibleState = isVisible;
        if (isVisible) {
            if (mIsFirstVisible) {
                mIsFirstVisible = false;
                onFragmentFirstVisible();
            }
            onFragmentResume();
            //在双重ViewPager嵌套的情况下,第一次滑到Frgment 嵌套ViewPager(fragment)的场景的时候
            //此时只会加载外层Fragment的数据,而不会加载内嵌viewPager中的fragment的数据,因此,我们
            //需要在此增加一个当外层Fragment可见的时候,分发可见事件给自己内嵌的所有Fragment显示
//            dispatchChildVisibleState(true);
        } else {
            onFragmentPause();
//            dispatchChildVisibleState(false);
        }
    }

    private boolean isParentInvisible() {
        Fragment parentFragment = getParentFragment();
        if (parentFragment instanceof LazyFragment3) {
            LazyFragment3 fragment = (LazyFragment3)parentFragment;
            return !fragment.isSupportVisible();
        }
        return false;
    }

    private boolean isSupportVisible() {
        return currentVisibleState;
    }

    private void dispatchChildVisibleState(boolean visible) {
        FragmentManager fragmentManager = getChildFragmentManager();
        List<Fragment> fragments = fragmentManager.getFragments();
        if (fragments != null) {
            for (Fragment fragment: fragments) {
                if (fragment instanceof LazyFragment3 &&
                        !fragment.isHidden() &&
                        fragment.getUserVisibleHint()) {
                    ((LazyFragment3)fragment).dispatchUserVisibleHint(visible);
                }
            }
        }
    }

    /**
     *
     * 用FragmentTransaction来控制fragment的hide和show时,
     * 那么这个方法就会被调用。每当你对某个Fragment使用hide
     * 或者是show的时候,那么这个Fragment就会自动调用这个方法。
     * https://blog.csdn.net/u013278099/article/details/72869175
     * @param hidden
     */
    @Override
    public void onHiddenChanged(boolean hidden) {
        logD("onHiddenChanged: " + hidden);
        super.onHiddenChanged(hidden);
        if (hidden) {
            dispatchUserVisibleHint(false);
        } else {
            dispatchUserVisibleHint(true);
        }
    }

    protected abstract void onFragmentFirstVisible();

    protected void onFragmentResume() {
        logD("onFragmentResume " + " 真正的resume,开始相关操作耗时");
    }

    protected void onFragmentPause() {
        logD("onFragmentPause" + " 真正的Pause,结束相关操作耗时");
    }

    public void setFragmentDelegater(FragmentDelegater fragmentDelegater) {
        mFragmentDelegater = fragmentDelegater;
    }

    @Override
    public void onResume() {
        super.onResume();
        logD( "onResume: ");
        //在滑动或者跳转的过程中,第一次创建fragment的时候均会调用onResume方法,类似于在tab1 滑到tab2,此时tab3会缓存,这个时候会调用tab3 fragment的
        //onResume,所以,此时是不需要去调用 dispatchUserVisibleHint(true)的,因而出现了下面的if
        if (!mIsFirstVisible) {
            //由于Activit1 中如果有多个fragment,然后从Activity1 跳转到Activity2,此时会有多个fragment会在activity1缓存,此时,如果再从activity2跳转回
            //activity1,这个时候会将所有的缓存的fragment进行onResume生命周期的重复,这个时候我们无需对所有缓存的fragnment 调用dispatchUserVisibleHint(true)
            //我们只需要对可见的fragment进行加载,因此就有下面的if
            if (!isHidden() && !currentVisibleState && getUserVisibleHint()) {
                dispatchUserVisibleHint(true);
            }
        }
    }

    /**
     * 只有当当前页面由可见状态转变到不可见状态时才需要调用 dispatchUserVisibleHint
     * currentVisibleState && getUserVisibleHint() 能够限定是当前可见的 Fragment
     * 当前 Fragment 包含子 Fragment 的时候 dispatchUserVisibleHint 内部本身就会通知子 Fragment 不可见
     *  子 fragment 走到这里的时候自身又会调用一遍
     */
    @Override
    public void onPause() {
        super.onPause();
        logD( "onPause: ");
        if (currentVisibleState && getUserVisibleHint()) {
            dispatchUserVisibleHint(false);
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        logD("onStop");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        logD("onDestroyView");
        isViewCreated = false;
        mIsFirstVisible = false;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    private void logD(String infor) {
        if (mFragmentDelegater != null) {
            mFragmentDelegater.dumpLifeCycle(infor);
        }
    }
}

6 参考

https://www.jianshu.com/p/b0830f9b44bb
https://www.jianshu.com/p/ea5de4925b36
https://www.jianshu.com/p/204efa98a18d

相关文章

网友评论

    本文标题:01 ViewPager源码解析与性能优化

    本文链接:https://www.haomeiwen.com/subject/grreuktx.html