[AS3.0.1]关于ViewPager、FragmentPagerAdapter、Fragment源码研究

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/g777520/article/details/81096001

前言

关于fragment的生命周期大家几乎都懂了。不过还是贴下图吧!这张图挺不错的比较直观。
生命周期

fragment的恢复是从onDestroyView到onCreateView

然后我们使用ViewPager的时候都知道,他会自动加载下一个fragment,以达到流畅的滑动,但是也会因为数据过多或者不断的刷新页面导致应用卡顿等问题。

所以想要让fragment不提前加载,查了下百度,大部分方案有以下两个
1.将v4包内的ViewPager拷贝出来将private static final int DEFAULT_OFFSCREEN_PAGES = 1;改为0就可以实现单独加载一个fragment的效果。

2.通过fragment中的public void setUserVisibleHint(boolean isVisibleToUser)方法来进行判断是否展示了fragment从而选择是否加载数据。


原理探究

第二种方法让我决定看下ViewPager、FragmentPagerAdapter、Fragment三个之间的流程

log测试

首先我们创建一个TestFragment用来打印所以的生命周期log,看一下具体的日志

TestFragment.java

public class TestFragment extends Fragment {

    private View view;
    private String name = "";

    public static TestFragment newInstance(String name) {
        TestFragment fragment = new TestFragment();
        Bundle bundle = new Bundle();
        bundle.putString("name", name);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        try {
            name = getArguments().getString("name", "");
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.e("-s-", "name = " + name);
        if (getUserVisibleHint()) {
            Log.e("-s-sssssss", name + "fragment is show.");
        } else {
            Log.e("-s-sssssss", name + "fragment is hide.");
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.e("-s-", name + "fragment is onAttach.");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e("-s-", name + "fragment is onCreate.");
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (view == null) {
            Log.e("-s-", name + "fragment is onCreateView.");
            view = inflater.inflate(R.layout.fragment_test, null);
            TextView tv = view.findViewById(R.id.tv_test_fm);
            tv.setText(name);
        } else {
            Log.e("-s-", name + "fragment is onCreateView ===> oldView");
        }
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null) {
            parent.removeView(view);
        }
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.e("-s-", name + "fragment is onActivityCreated.");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.e("-s-", name + "fragment is onStart.");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.e("-s-", name + "fragment is onResume.");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.e("-s-", name + "fragment is onPause.");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.e("-s-", name + "fragment is onStop.");
    }

    @Override
    public void onDestroyView() {
        Log.e("-s-", name + "fragment is onDestroyView.");
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        Log.e("-s-", name + "fragment is onDestroy.");
        super.onDestroy();
    }

    @Override
    public void onDetach() {
        Log.e("-s-", name + "fragment is onDetach.");
        super.onDetach();
    }
}

Activity正常调用FragmentPagerAdapter和ViewPager
打开app初始如下log

E/-s-: name = first
E/-s-sssssss: firstfragment is hide.
E/-s-: name = second
E/-s-sssssss: secondfragment is hide.
E/-s-: name = first
E/-s-sssssss: firstfragment is show.
E/-s-: firstfragment is onAttach.
E/-s-: firstfragment is onCreate.
E/-s-: secondfragment is onAttach.
E/-s-: secondfragment is onCreate.
E/-s-: firstfragment is onCreateView.
E/-s-: firstfragment is onActivityCreated.
E/-s-: firstfragment is onStart.
E/-s-: firstfragment is onResume.
E/-s-: secondfragment is onCreateView.
E/-s-: secondfragment is onActivityCreated.
E/-s-: secondfragment is onStart.
E/-s-: secondfragment is onResume.

发现开始最先被调用的方法其实是setUserVisibleHint而且确实创建了两个fragment,并且都执行到了onResume

之后我们滑动ViewPager将页面滑动到第二页,新log如下

E/-s-: name = third
E/-s-sssssss: thirdfragment is hide.
E/-s-: name = first
E/-s-sssssss: firstfragment is hide.
E/-s-: name = second
E/-s-sssssss: secondfragment is show.
E/-s-: thirdfragment is onAttach.
E/-s-: thirdfragment is onCreate.
E/-s-: thirdfragment is onCreateView.
E/-s-: thirdfragment is onActivityCreated.
E/-s-: thirdfragment is onStart.
E/-s-: thirdfragment is onResume.

因为ViewPager的预加载将第三页也加载出来了,并且我们看到第一页被隐藏掉了。

在继续滑到第三页,log如下

E/-s-: name = four
E/-s-sssssss: fourfragment is hide.
E/-s-: name = second
E/-s-sssssss: secondfragment is hide.
E/-s-: name = third
E/-s-sssssss: thirdfragment is show.
E/-s-: fourfragment is onAttach.
E/-s-: fourfragment is onCreate.
E/-s-: firstfragment is onPause.
E/-s-: firstfragment is onStop.
E/-s-: firstfragment is onDestroyView.
E/-s-: fourfragment is onCreateView.
E/-s-: fourfragment is onActivityCreated.
E/-s-: fourfragment is onStart.
E/-s-: fourfragment is onResume.

由于默认的ViewPager设置的setOffscreenPageLimit是1所以目前页面加载到是第三页,保存的fragment有第二页(被隐藏)第三页(正在显示)第四页(预加载,被隐藏),而第一页就被关闭掉了。

最后再将第三页滑动回第二页,log如下

E/-s-: name = first
E/-s-sssssss: firstfragment is hide.
E/-s-: name = third
E/-s-sssssss: thirdfragment is hide.
E/-s-: name = second
E/-s-sssssss: secondfragment is show.
E/-s-: firstfragment is onCreateView ===> oldView
E/-s-: firstfragment is onActivityCreated.
E/-s-: fourfragment is onPause.
E/-s-: fourfragment is onStop.
E/-s-: fourfragment is onDestroyView.
E/-s-: firstfragment is onStart.
E/-s-: firstfragment is onResume.

至此我得到如下结论

1. ViewPager必定会预加载左右两边的fragment

2. 当出现过的fragment多于设置的setOffscreenPageLimit数量就会将除了预加载和前一页的fragment释放掉

源码排查

关于上面的log之后我们就可以看下源码方法的调用情况

首先先点击setUserVisibleHint看下调用的情况
setUserVisibleHint

很直观的可以看到在FragmentPagerAdapter中有3次调用,进到调用的地方看下源码
主要在两个方法中设置的fragment的是否显示instantiateItemsetPrimaryItem
以下是源码

    @SuppressWarnings("ReferenceEquality")
    @Override
    public Object instantiateItem(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) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {
            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);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }

    @SuppressWarnings("ReferenceEquality")
    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

第一个instantiateItem很明显我们知道是Adapter添加fragment的代码
而后面一个setPrimaryItem设置属性的方法可以看出来是用于判断,当前页面的fragment是否是ViewPager滑动到的fragment,我们在看下setPrimaryItem的调用
setPrimaryItem

可以看到就在ViewPager中Adapter使用了一次是在populate方法内,看方法名,我们可以知道这个ViewPager在填充fragment的方法。

至此我们就可以知道setUserVisibleHint的具体逻辑了。

  1. 首先先向FragmentPagerAdapter中加入多个fragment对象

  2. ViewPager通过setOffscreenPageLimit中的值来确认需要填充的fragment数量,即调用populate方法

  3. populate方法先确认需要添加的个数让Adapter调用instantiateItem方法加入fragment

  4. 最后让Adapter调用setPrimaryItem方法来设置fragment的setUserVisibleHint属性


这样就有了我们一开始看到的log

开始需要预加载1个fragment就调用了2次instantiateItem,然后再设置setPrimaryItem来设置显示与否。

之后滑到第二页是调用了1次instantiateItem,然后设置setPrimaryItem这个时候有第一页和第二页,所以设置第一页隐藏,第二页显示。


总结

通过逻辑我们可以知道为什么可以在setUserVisibleHint方法中来控制是否加载fragment网络数据,即百度到的fragment懒加载。
本篇就算是一段记录吧!


资料

Fragment的setUserVisibleHint详解

猜你喜欢

转载自blog.csdn.net/g777520/article/details/81096001