美文网首页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