Android ViewPager + fragment 实现懒加载及原理解析

Android ViewPager与Fragment懒加载深度解析
本文详细探讨了如何在Android中使用ViewPager与Fragment实现懒加载,通过分析ViewPager的关键方法如onMeasure、populate等,揭示了FragmentPagerAdapter与FragmentStateAdapter在内存管理上的差异,并阐述了基于FragmentPagerAdapter的流程。

Android ViewPager + fragment 实现懒加载及原理解析

1 代码实现

fragment 实现

public abstract class LazyFragment extends LogFragment {

    private View viewRoot;

    private boolean isShow;

    private boolean isLoaded;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        viewRoot = inflater.inflate(getLayoutId(), container, false);
        initView(viewRoot);
        if (!isShow && getUserVisibleHint()) {
            dispatchVisibleHint(true);
        }
        return viewRoot;
    }

    protected abstract int getLayoutId();

    protected abstract void initView(View viewRoot);

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (viewRoot != null) {
            if (!isShow && isVisibleToUser) {
                //当前从不可见变为可见
                dispatchVisibleHint(true);
            } else if (isShow && !isVisibleToUser) {
                //当前从可见变为不可见
                dispatchVisibleHint(false);
            }
        }
    }

    private void dispatchVisibleHint(boolean isVisible) {
        isShow = isVisible;
        if (isVisible) {
            if (!isLoaded) {
                Log.e(TAG, "开始加载" + getClass().getName());
                isLoaded = true;
                loadData();
            }
        } else {
            Log.e(TAG, "停止加载" + getClass().getName());
            stopLoadData();
        }
    }

    protected void stopLoadData() {
    }

    protected void loadData() {
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        viewRoot = null;
        isShow = false;
        isLoaded = false;
    }
}

使用

public class SecondActivity extends AppCompatActivity {

    private ViewPager viewPager;

    private TabLayout tabLayout;

    private ArrayList<LazyFragment> fragments;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        initView();
    }

    private void initView() {
        fragments = new ArrayList<>();
        viewPager = findViewById(R.id.activity_second_viewPager);
        tabLayout = findViewById(R.id.activity_tabLayout);
//        for (int i = 0; i < 10; i++) {
//            fragments.add(fragment1);
//        }

        fragments.add(new Fragment1());
        fragments.add(new Fragment2());
        fragments.add(new Fragment3());
        fragments.add(new Fragment4());
        fragments.add(new Fragment6());
        //他会真正销毁我们的fragment,只保存状态
//        FragmentStatePagerAdapter
        //mActive  这个数组 会造成oom

        //他不会真正销毁
        FragmentPagerAdapter fragmentPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {

            @NonNull
            @Override
            public Fragment getItem(int position) {
//                Fragment1 fragment1 = new Fragment1();
//                Bundle bundle = new Bundle();
//                bundle.putString("fragment", "" + position);
//                fragment1.setArguments(bundle);
//                fragments.add(fragment1);
//                return fragment1;
                return fragments.get(position);
            }

            @Override
            public int getCount() {
                return fragments.size();
            }
        };
        viewPager.setAdapter(fragmentPagerAdapter);
        tabLayout.setupWithViewPager(viewPager);
        for (int i = 0; i < tabLayout.getTabCount(); i++) {
            tabLayout.getTabAt(i).setText("Tab " + i);
        }
        viewPager.setOffscreenPageLimit(fragments.size());
    }
}

2 原理解析

先来看 ViewPager 的源码 onMeasure 方法

2.1 ViewPager.onMeasure

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
                getDefaultSize(0, heightMeasureSpec));

        final int measuredWidth = getMeasuredWidth();
        final int maxGutterSize = measuredWidth / 10;
        mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);

        // Children are just made to fill our space.
        int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
        int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
        
        int size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp != null && lp.isDecor) {
                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
                    int widthMode = MeasureSpec.AT_MOST;
                    int heightMode = MeasureSpec.AT_MOST;
                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
                    boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;

                    //... 测量代码 省略代码
                    child.measure(widthSpec, heightSpec);

                    if (consumeVertical) {
                        childHeightSize -= child.getMeasuredHeight();
                    } else if (consumeHorizontal) {
                        childWidthSize -= child.getMeasuredWidth();
                    }
                }
            }
        }

        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);

        // Make sure we have created all fragments that we need to have shown.
        mInLayout = true;
        populate(); // 重点方法,负责创建我们需要显示的 fragments
        mInLayout = false;

        // Page views next.
        size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                if (DEBUG) {
                    Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec);
                }

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp == null || !lp.isDecor) {
                    final int widthSpec = MeasureSpec.makeMeasureSpec(
                            (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
                    child.measure(widthSpec, mChildHeightMeasureSpec);
                }
            }
        }
    }

2.2 ViewPager.populate()

void populate(int newCurrentItem) {
        ItemInfo oldCurInfo = null;
        if (mCurItem != newCurrentItem) {
            oldCurInfo = infoForPosition(mCurItem);
            mCurItem = newCurrentItem;
        }

        // 省略代码。。。

			 // 1.当显示的页面将开始进行更改时调用
        mAdapter.startUpdate(this);

			 // 预加载相关
        final int pageLimit = mOffscreenPageLimit;
        final int startPos = Math.max(0, mCurItem - pageLimit);
        final int N = mAdapter.getCount();
        final int endPos = Math.min(N - 1, mCurItem + pageLimit);

        // 省略代码。。。

        // Locate the currently focused item or add it if needed.
        int curIndex = -1;
        ItemInfo curItem = null;
        for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
            final ItemInfo ii = mItems.get(curIndex);
            if (ii.position >= mCurItem) {
                if (ii.position == mCurItem) curItem = ii;
                break;
            }
        }

        if (curItem == null && N > 0) {
        		 // 2.实例化 fragment 的代码入口
        		 // instantiteItem -> getItem ->  setUserVisableHint(false)
            curItem = addNewItem(mCurItem, curIndex);
        }

        // Fill 3x the available width or up to the number of offscreen
        // pages requested to either side, whichever is larger.
        // If we have no current item we have no work to do.
        if (curItem != null) {
            float extraWidthLeft = 0.f;
            int itemIndex = curIndex - 1;
            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
            final int clientWidth = getClientWidth();
            final float leftWidthNeeded = clientWidth <= 0 ? 0 :
                    2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
            for (int pos = mCurItem - 1; pos >= 0; pos--) {
                if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
                    if (ii == null) {
                        break;
                    }
                    if (pos == ii.position && !ii.scrolling) {
                        mItems.remove(itemIndex);
                        // 3.销毁不需要显示的 fragment
                        mAdapter.destroyItem(this, pos, ii.object);
                        if (DEBUG) {
                            Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                    + " view: " + ((View) ii.object));
                        }
                        itemIndex--;
                        curIndex--;
                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                    }
                } else if (ii != null && pos == ii.position) {
                    extraWidthLeft += ii.widthFactor;
                    itemIndex--;
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                } else {
                    ii = addNewItem(pos, itemIndex + 1);
                    extraWidthLeft += ii.widthFactor;
                    curIndex++;
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                }
            }

            float extraWidthRight = curItem.widthFactor;
            itemIndex = curIndex + 1;
            if (extraWidthRight < 2.f) {
                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                final float rightWidthNeeded = clientWidth <= 0 ? 0 :
                        (float) getPaddingRight() / (float) clientWidth + 2.f;
                for (int pos = mCurItem + 1; pos < N; pos++) {
                    if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
                        if (ii == null) {
                            break;
                        }
                        if (pos == ii.position && !ii.scrolling) {
                            mItems.remove(itemIndex);
                            // 销毁不需要显示的 fragment
                            mAdapter.destroyItem(this, pos, ii.object);
                            if (DEBUG) {
                                Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                        + " view: " + ((View) ii.object));
                            }
                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                        }
                    } else if (ii != null && pos == ii.position) {
                        extraWidthRight += ii.widthFactor;
                        itemIndex++;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    } else {
                        ii = addNewItem(pos, itemIndex);
                        itemIndex++;
                        extraWidthRight += ii.widthFactor;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    }
                }
            }

            calculatePageOffsets(curItem, curIndex, oldCurInfo);
					// 4.设置当前显示的 item, 会调用 setUserVisableHint(true)
            mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
        }
			 // 5.真正调用 commit, 走 fragment 的生命周期
        mAdapter.finishUpdate(this);

        // 省略代码。。。
    }

FragmentPagerAdapter 和 FragmentStateAdapter 的区别在于,FragmentPagerAdapter 不会对不在显示的fragment 进行销毁,只进行 detach 操作,存在内存泄漏的风险。而 FragmentStateAdapter 则会重新销毁(remove)、创建,具体调用实现可以看两个类对以下方法的实现。

我们来看基于 FragmentPagerAdapter 的流程:

2.3 mAdapter.startUpdate( )

2.4 mAdapter.addNewItem(mCurItem, curIndex)

ItemInfo addNewItem(int position, int index) {
        ItemInfo ii = new ItemInfo();
        ii.position = position;
        // 提供实例化 fragment 的回调方法
        ii.object = mAdapter.instantiateItem(this, position);
        ii.widthFactor = mAdapter.getPageWidth(position);
        if (index < 0 || index >= mItems.size()) {
            mItems.add(ii);
        } else {
            mItems.add(index, ii);
        }
        return ii;
    }
// androidx/fragment/app/FragmentPagerAdapter.java
public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            // 调用 mCurTransaction的attach ,后面会走生命周期
            mCurTransaction.attach(fragment);
        } else {
        		// 获取 fragment,提供给用户实现
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
            } else {
            		// 懒加载重点
                fragment.setUserVisibleHint(false);
            }
        }

        return fragment;
    }

2.5 mAdapter.destroyItem()

// androidx/fragment/app/FragmentPagerAdapter.java
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        // 调用 detach,此时还未 commit
        mCurTransaction.detach(fragment);
        if (fragment == mCurrentPrimaryItem) {
            mCurrentPrimaryItem = null;
        }
    }

2.6 mAdapter.setPrimaryItem()

// androidx/fragment/app/FragmentPagerAdapter.java
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                    if (mCurTransaction == null) {
                        mCurTransaction = mFragmentManager.beginTransaction();
                    }
                    mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
                } else {
                    mCurrentPrimaryItem.setUserVisibleHint(false);
                }
            }
            fragment.setMenuVisibility(true);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                }
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
            } else {
            		// 懒加载重点
                fragment.setUserVisibleHint(true);
            }
					// 设置当前主 item
            mCurrentPrimaryItem = fragment;
        }
    }

2.7 mAdapter.finshStartUpdate()

// androidx/fragment/app/FragmentPagerAdapter.java
public void finishUpdate(@NonNull ViewGroup container) {
        if (mCurTransaction != null) {
        		// 与 {@link commitNow} 类似,但允许在保存活动状态后执行提交.
        		// 此时立即执行生命周期回调,此前会先调用 setUserVisableHint(false/true)
            mCurTransaction.commitNowAllowingStateLoss();
            mCurTransaction = null;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值