viewpager使用_明星学员作品:手把手讲解ViewPager懒加载

1c097c46d397b49f9d6d796494d2e7bf.png

作者介绍

周周:享学课堂Android VIP班学员,享学课堂终生学员,目前就职xxx(保密,嘿嘿~),6年的工作经验。  

Alvin老师这样评价周周同学:“周周几乎从不缺席移动互联网的VIP课,课堂表现积极踊跃,善于思考提问。课后经常会找我探讨课堂的知识点,也会把工作当中的一些项目拿出来一起研究。周周是个热心肠的人,也是个特别追求细节的人,从这篇文章就可以看到,他的学习态度是多么的精益求精。

前言

手把手讲解系列文章,是我写给各位看官,也是写给我自己的。文章可能过分详细,但是这是为了帮助到尽量多的人,毕竟工作5,6年,不能老吸血,也到了回馈开源的时候. 这个系列的文章:1、用通俗易懂的讲解方式,讲解一门技术的实用价值 2、详细书写源码的追踪,源码截图,绘制类的结构图,尽量详细地解释原理的探索过程 3、提供Github 的 可运行的Demo工程,但是我所提供代码,更多是提供思路,抛砖引玉,请酌情cv 4、集合整理原理探索过程中的一些坑,或者demo的运行过程中的注意事项 5、用gif图,最直观地展示demo运行效果

如果觉得细节太细,直接跳过看结论即可。本人能力有限,如若发现描述不当之处,欢迎留言批评指正。

学到老活到老,路漫漫其修远兮。与众君共勉 !

正文大纲

先给出 github Demo: https://github.com/18598925736/ViewPagerKeng

一、 ViewPager+ Fragment"诡异"的缓存特性

二、"诡异"特性带来的后果

三、必要的基础知识

四、 ViewPager 的缓存相关源码索引

五、 ViewPager+Fragment 懒加载机制的设计思路

六、案例演示

七、总结 程序开发者的修炼之路

八、鸣谢

正文

一 、ViewPager+Fragment"诡异"的缓存特性

  1. 缓存机制,最少缓存一个 Fragment

  2. 在只要是存在于缓存中的 Fragment,无论它当前是不是可见,都会无脑跟随他们所在的 Activity,执行生命周期函数

  3. 当存在这样的结构:ViewPager+Fragment(ViewPager+Fragment)嵌套,内部的 Fragment也会遵循第 2点

二 、"诡异"特性带来的后果

无论可见或者不可见,只要是存在于 ViewPager这个容器之内的 Fragment,都会统一执行生命周期函数,这意味着什么?

意味着, Fragment的生命周期函数已经不再可信。并且 如果你在某个生命周期函数内写了 耗时操作,或者 消耗CPU,内存,网络请求的 代码,也会在 UI 根本不在视野内的情况下执行,而且执行地毫无意义。还有可能导致当前 FragmentUI 的卡顿

试想如果 ViewPager有10个 fragment,但是一次只显示了一个,你离开了当前 Activity之后回来, Activity执行 onResume,然后10个 Fragment 一起执行 onStart- onResume , 而恰好你在 onResume里面写了耗时操作,想想那 酸爽 , 关键是其他9个 不可见,系统资源完全浪费.

可是,你耗时操作的调用,不写在 生命周期函数里面,还能写在哪里??

看来 ViewPager+Fragment的默认机制,很有可能造成 app里的一个大坑,那怎么办?

只能自己设计一套懒加载机制

所谓懒加载,就是:

保证在任何情况下,只有可见的那一个 Fragment才会执行 耗时操作,网络请求和UI操作

其余不可见的,一律不执行任何操作,并且已有的操作(比如 定时器已经发出的网络请求)都要手动终止

三 、必要的基础知识

ActivityFragment的生命周期函数图,我就不贴出来了,大家可以百度。以下几点,是本人写 demo做试验所得。

1) 关于 Fragment在结合 ViewPager的情况(包括内嵌 ViewPager+Fragment)下的生命周期函数 执行流程的几个重要结论:

  1. Fragment 会听从 ViewPager的缓存机制,只要 ViewPager觉得一个 Fragment应该被初始化(无论是不是可见),然后缓存起来,那么 Fragment就会执行完整的生命周期 ,

  onCreate-onCreateView-onViewCreated-onStart-onResume

至于具体初始化几个,这个 与 ViewPagersetOffscreenPageLimit()缓存设置有关

  1. 当所在的 Activity发生跳转,其中的已经被实例化的 Fragment都会执行:onPause-onStop

  2. 当回到所在 Activity时, onStart-onResume

2) 滑动 ViewPager,所有的 Fragment都只会有一次 onCreate,然而,View会经历销毁/重建

onCreateView-onViewCreated-onStart-onResume -----> onPause-onStop-onDestroyView

3) Fragment其实还有两个与 它生命周期无关的 函数,这两个函数 可以控制 Fragment的当前可见状态值 ,但是, 他只是一个特征值而已,并不能证明它就是可见的或者不可见, 他们分别是:

setUserVisibleHint () 看过注释,大概意思是 给系统设置一个关于当前 fragmentUI是否显示给用户的暗示,这个方法可能被 fragment的生命周期的外部调用,所以不能确保它在生命周期的哪个阶段被调用

onHiddenChanged() 这个一般用于 fragmentFragmentManager hide或者 show的时候

这两个函数,不能确保在生命周期的哪个阶段调用,但是可以根据他们,来配合生命周期函数,判定当前 fragment是不是可见.

四 、ViewPager 相关源码索引

终于要进入源码了。

之前提到过, ViewPager 是自带缓存机制的,并且默认缓存数量是1个。

那么有3个问题:

  1. 为什么至少缓存一个?

  2. 缓存的是什么?

  3. 存到哪里去了?

  4. 存起来之后用来干什么了?

进入ViewPager.java (SDK 27)源代码一起来探索一下:

setOffscreenPageLimit() 作为入口(为什么是它?因为它是设置缓存数量的函数)

private static final int DEFAULT_OFFSCREEN_PAGES = 1;//默认缓存页数

public void setOffscreenPageLimit(int limit) {

       if (limit < DEFAULT_OFFSCREEN_PAGES) {//如果入参是0

           Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +

                   DEFAULT_OFFSCREEN_PAGES);

           limit = DEFAULT_OFFSCREEN_PAGES;// 强制设定为1

       }

       if (limit != mOffscreenPageLimit) {//如果缓存页数和当前值不一致

           mOffscreenPageLimit = limit;//那么更新当前值

           populate();//并且执行缓存方法

       }

   }

上述注释很明确,如果入参是 0,那么强行改为 1,也就是说 ViewPager不允许 无缓存的情况。而且,在缓存数量更新的时候,要执行缓存方法 polulate()

那么 polulate()都做了什么?

我们知道, ViewPager要搭配 PagerAdapter使用. 缓存一定和这个adapter有关,进入源码 , 跟随 adapter

void populate(int newCurrentItem) {

       ItemInfo oldCurInfo = null;

       int focusDirection = View.FOCUS_FORWARD;

       if (mCurItem != newCurrentItem) {

           focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT;

           oldCurInfo = infoForPosition(mCurItem);

           mCurItem = newCurrentItem;

       }

       if (mAdapter == null) { //adapter是空,就不进行后面的操作了

           sortChildDrawingOrder();

           return;

       }

       // Bail now if we are waiting to populate.  This is to hold off

       // on creating views from the time the user releases their finger to

       // fling to a new position until we have finished the scroll to

       // that position, avoiding glitches from happening at that point.

       if (mPopulatePending) {

           if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");

           sortChildDrawingOrder();

           return;

       }

       // Also, don't populate until we are attached to a window.  This is to

       // avoid trying to populate before we have restored our view hierarchy

       // state and conflicting with what is restored.

       if (getWindowToken() == null) {

           return;

       }

       mAdapter.startUpdate(this);//当一个改变发生在已经显示出来的页面时执行(貌似和缓存无关)

       final int pageLimit = mOffscreenPageLimit;//当前缓存数

       final int startPos = Math.max(0, mCurItem - pageLimit);//用当前页编号和缓存数的计算缓存开始的位置

       final int N = mAdapter.getCount();//获得 adapter的子内容数量

       final int endPos = Math.min(N-1, mCurItem + pageLimit);//用当前页编号和缓存数的计算缓存结束的位置

       if (N != mExpectedAdapterCount) {//如果adapter的内容数量和期望值不同,就抛出异常

           String resName;

           try {

               resName = getResources().getResourceName(getId());

           } catch (Resources.NotFoundException e) {

               resName = Integer.toHexString(getId());

           }

           throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +

                   " contents without calling PagerAdapter#notifyDataSetChanged!" +

                   " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +

                   " Pager id: " + resName +

                   " Pager class: " + getClass() +

                   " Problematic adapter: " + mAdapter.getClass());

       }

       // Locate the currently focused item or add it if needed.

       // 本地化当前聚焦的item 或者 有必要的话将它加到list中去

       int curIndex = -1;

       ItemInfo curItem = null;//你就是当前正在显示的item

       for (curIndex = 0; curIndex < mItems.size(); curIndex++) {

           //这里在遍历一个mItems,它是ArrayList mItems

           final ItemInfo ii = mItems.get(curIndex);// 获得当前位置item

           if (ii.position >= mCurItem) {//

               if (ii.position == mCurItem) curItem = ii;

               //讲道理,上面两行代码我没看懂,先判定>= ,又判定==,最后把当前缓存的这个,赋值给curItem

               break;

           }

       }

       if (curItem == null && N > 0) {// 如果curItem经历了上面的遍历,还是null,并且adapter的内容数量大于0

           curItem = addNewItem(mCurItem, curIndex);//那么就构建一个ItemInfo对象,并且赋值给curItem

       }

       // 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.

       // 这段注释的意思是,填充三倍可见宽度 或者一直到 屏幕外页面的数量需要的宽度,无论它多大.

       // 如果我们没有当前item,就不用做任何事情

       if (curItem != null) {

           float extraWidthLeft = 0.f;

           int itemIndex = curIndex - 1;//??

           ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;//itemIndex已经是0了,所以,这里就是获取当前位置的item

           final int clientWidth = getPaddedWidth();

           final float leftWidthNeeded = clientWidth <= 0 ? 0 :

                   2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;

           for (int pos = mCurItem - 1; pos >= 0; pos--) {//从当前显示的item倒过来往前遍历

               if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {

                   if (ii == null) {

                       break;

                   }

                   if (pos == ii.position && !ii.scrolling) {//如果遍历到了缓存item所在的位置,并且缓存的这个item没有处于滑动状态

                       mItems.remove(itemIndex);//就从缓存list中移除它

                       mAdapter.destroyItem(this, pos, ii.object);//并且 adapter执行destroyItem销毁item

                       if (DEBUG) {

                           Log.i(TAG, "populate() - destroyItem() with pos: " + pos +

                                   " view: " + ii.object);

                       }

                       itemIndex--;

                       curIndex--;

                       ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;

                   }

               } else if (ii != null && pos == ii.position) {//如果遍历到了 当前显示的item

                   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);//从缓存中移除掉当前item,因为正在显示中,所以无需缓存

                           mAdapter.destroyItem(this, pos, ii.object);

                           if (DEBUG) {

                               Log.i(TAG, "populate() - destroyItem() with pos: " + pos +

                                       " 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); Fix up offsets for later layout. 为了后面的布局,修复偏移量,没有涉及到缓存list的add和remove

       }

       if (DEBUG) {

           Log.i(TAG, "Current page list:");

           for (int i=0; i

               Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);

           }

       }

上面的代码中提到了 addNewItem() ,它就是往缓存 ArrayListmItems里面增加 ItemInfo的 核心方法

ItemInfo addNewItem(int position, int index) {

       ItemInfo ii = new ItemInfo();

       ii.position = position;

       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;

   }

看了这个方法,得出结论:

1.为什么至少缓存一个?

答:当我们传参为0,它会强制设定为1.

  if (limit < DEFAULT_OFFSCREEN_PAGES) {//如果入参是0              

            limit = DEFAULT_OFFSCREEN_PAGES;// 强制设定为1

   }

2.缓存的是什么?

答:缓存的是 ItemInfo 对象, 而 ItemInfoadapter.instantiateItem的一个封装,除了 mAdapter.instantiateItem 返回的这个object之外,它还封装了 position,还有 mAdapter.getPageWidth 页面宽度.

   static class ItemInfo {

           Object object;

           boolean scrolling;

           float widthFactor;

           /** Logical position of the item within the pager adapter. */

           int position;

           /** Offset between the starting edges of the item and its container. */

           float offset;

       }

3.存到哪里去了?

答:存到了一个List,叫做 ArrayListmItems

   private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();

4.存起来之后用来干什么了?

答: 追踪 mItems.get方法,发现,源码中有30处出现,一共有 12 个方法使用了它:

   setAdapter

   setCurrentItemInternal

   dataSetChanged

   populate

   calculatePageOffsets

   infoForChild

   infoForPosition

   completeScroll

   performDrag

   infoForFirstVisiblePage

   determineTargetPage

   onDraw

可以发现 缓存,贯穿了几乎 ViewPager的所有行为,包括适配器,数据变更,修正页面偏移量,计算滑动,拖拽,绘制等, 但是我想进一步探索的时候,发现这些方法,大部分没有注释,时间原因,只好作罢。

从上面的结论,我们总结一下, ViewPager缓存的,是 PageAdapter mAdapter里面 instantiateItem 创建出来的 object, 我们今天重点应该关注的是和 Fragment有关的 FragmentPagerAdapter,所以,我们看看它的 instantiateItem到底返回了一个神马东西:

public Object instantiateItem(ViewGroup container, int position) {

       //...省略非重点代码

       return fragment;

   }

看到了吧?returnfragment !

所以,得出最终结论

ViewPager+Fragment (其实还应该加上 FragmentPagerAdapter), 缓存的是 Fragment对象,该对象被封装成了 ItemInfoadd到了 ArrayList mItems 里面。当我们滑动ViewPager的时候,如果进入的是 已经缓存的 Fragment,就不会去走 Fragment的重新创建生命周期( onCreate-onCreateView-onViewCreated-onStart-onResume),直接使用缓存中的 Fragment,调用它的 onStart-onResume.

五 、ViewPager+Fragment懒加载机制的设计思路

既然 ViewPager已经这么明确了,他就是要缓存至少一个 Fragment,我们不太好去直接改源码,那怎么去 实现 懒加载呢?没办法了,曲线救国吧,改不了 ViewPager,那就 在 Fragment的生命周期上做文章。

Fragment的生命周期也不太好去动刀,但是我们可以增加另外的方法,来结合生命周期,以及 上文提及的无关生命周期的两个函数 setUserVisibleHint / onHiddenChanged.

原本的 Fragment生命周期函数不可信了,那么首先我们继承 Fragment,增加3个我们自己的方法, 以及3bool标志位

public abstract class BaseLazyLoadingFragment extends Fragment {    

   //省略无关代码...

   private boolean isViewCreated = false;//View是否已经被创建出来

   private boolean isFirstVisible = true;//当前Fragment是否是首次可见

   private boolean currentVisibleState = false;//当前真正的可见状态

   /**

    * 当第一次可见的时候(此方法,在View的一次生命周期中只执行一次)

    * 如果Fragment经历了onDestroyView,那么整个方法会再次执行

    * 重写此方法时,对Fragment全局变量进行 初始化

    * 具体的参照github demo

    */

   protected void onFragmentFirstVisible() {

       Log.d(getCustomMethodTag(), "第一次可见,进行当前Fragment初始化操作");

   }

   /**

    * 当fragment变成可见的时候(可能会多次)

    */

   protected void onFragmentResume() {

       Log.d(getCustomMethodTag(), "onFragmentResume 执行网络请求以及,UI操作");

   }

   /**

    * 当fragment变成不可见的时候(可能会多次)

    */

   protected void onFragmentPause() {

       Log.d(getCustomMethodTag(), "onFragmentPause 中断网络请求,UI操作");

   }    

}

我们设计了3个自定义的方法,那么这3个方法在什么情况下执行呢?

定义一个 visible状态分发的方法:

void dispatchVisibleState(boolean isVisible) {

       //为了兼容内嵌ViewPager的情况,分发时,还要判断父Fragment是不是可见

       if (isVisible && isParentInvisible()) {//如果当前可见,但是父容器不可见,那么也不必分发

           return;

       }

       if (isVisible == currentVisibleState) return;//如果目标值,和当前值相同,那就别费劲了

       currentVisibleState = isVisible;//更新状态值

       if (isVisible) {//如果可见

           //那就区分是第一次可见,还是非第一次可见

           if (isFirstVisible) {

               isFirstVisible = false;

               onFragmentFirstVisible();

           }

           onFragmentResume();

           dispatchChildVisibilityState(true);

       } else {

           onFragmentPause();

           dispatchChildVisibilityState(false);

       }

   }

接着Fragment原有的生命周期函数内( 主要是 OnCreateViewonResumeonPause),调用此方法

public abstract class BaseLazyLoadingFragment extends Fragment {

     //省略无关代码...

   @Override

   public void onAttach(Context context) {

       super.onAttach(context);

       Log.d(getLifeCycleTag(), "onAttach");

   }

   @Override

   public void onCreate(@Nullable Bundle savedInstanceState) {

       Log.d(getLifeCycleTag(), "onCreate");

       super.onCreate(savedInstanceState);

   }

   @Nullable

   @Override

   public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

       mRoot = inflater.inflate(getLayoutId(), container, false);

       Log.d(getLifeCycleTag(), "onCreateView");

       initView(mRoot);

       //在View创建完毕之后,isViewCreate 要变为true

       isViewCreated = true;

       if (!isHidden() && getUserVisibleHint())

           dispatchVisibleState(true);

       return mRoot;

   }

   @Override

   public void onDestroyView() {//相对应的,当View被销毁的时候,isViewCreated要变为false

       super.onDestroyView();

       Log.d(getLifeCycleTag(), "onDestroyView");

       reset();

   }

   @Override

   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {

       super.onViewCreated(view, savedInstanceState);

       Log.d(getLifeCycleTag(), "onViewCreated");

   }

   @Override

   public void onStart() {

       super.onStart();

       Log.d(getLifeCycleTag(), "onStart");

   }

   @Override

   public void onResume() {

       super.onResume();

       Log.d(getLifeCycleTag(), "onResume");

       if (!isFirstVisible) {

           if (!isHidden() && !currentVisibleState && getUserVisibleHint())

               dispatchVisibleState(true);

       }

   }

   @Override

   public void onPause() {

       super.onPause();

       Log.d(getLifeCycleTag(), "onPause");

       if (currentVisibleState && getUserVisibleHint()) {

           dispatchVisibleState(false);

       }

   }

   @Override

   public void onStop() {

       super.onStop();

       Log.d(getLifeCycleTag(), "onStop");

   }

   @Override

   public void onDestroy() {

       super.onDestroy();

       Log.d(getLifeCycleTag(), "onDestroy");

   }

   @Override

   public void onDetach() {

       super.onDetach();

       Log.d(getLifeCycleTag(), "onDetach");

   }

      //省略无关代码...

}

然而,能够表示当前 Fragment是否可见的,并不止有生命周期函数,还有2个和生命周期无关的函数( setUserVisibleHint, onHiddenChanged),在其中也要调用 dispatchVisibleState

   /**

    * 此方法和生命周期无关,由外部调用,只是作为一个可见不可见的参考

     */

   @Override

   public void setUserVisibleHint(boolean isVisibleToUser) {

       super.setUserVisibleHint(isVisibleToUser);

       Log.d(getLogTag(), "setUserVisibleHint:" + isVisibleToUser);

       //因为只有在Fragment的View已经被创建的前提下,UI处理才有意义,所以

       if (isViewCreated) {

           //为了逻辑严谨,必须当目前状态值和目标相异的时候,才去执行UI可见分发

           if (currentVisibleState && !isVisibleToUser) {

               dispatchVisibleState(false);

           } else if (!currentVisibleState && isVisibleToUser) {

               dispatchVisibleState(true);

           }

       }

   }

   /**

    * 在Fragment被hide/show的时候被调用

    * @param hidden

    */

   @Override

   public void onHiddenChanged(boolean hidden) {

       super.onHiddenChanged(hidden);

       if (hidden) {

           dispatchVisibleState(false);

       } else {

           dispatchVisibleState(true);

       }

   }

最后,考虑到 ViewPager+Fragment(ViewPager+Fragment)嵌套的问题:

设计一个 dispatchChildVisibilityState 方法,来控制 内嵌的 Fragment的可见状态 :

private void dispatchChildVisibilityState(boolean isVisible) {

       FragmentManager fragmentManager = getChildFragmentManager();

       List<Fragment> list = fragmentManager.getFragments();

       if (list != null) {

           for (Fragment fg : list) {//遍历子

               if (fg instanceof BaseLazyLoadingFragment

                       && !fg.isHidden() && fg.getUserVisibleHint()) {

                   ((BaseLazyLoadingFragment) fg).dispatchVisibleState(isVisible);

               }

           }

       }

   }

以及 isParentInvisible方法来判定内嵌 Fragment的父 Fragment是否可见:

private boolean isParentInvisible() {

       Fragment parent = getParentFragment();

       Log.d(getLogTag(), "getParentFragment:" + parent + "");

       if (parent instanceof BaseLazyLoadingFragment) {

           BaseLazyLoadingFragment lz = (BaseLazyLoadingFragment) parent;

           return !lz.currentVisibleState;

       }

       return false;// 默认可见

   }

六 、案例演示(观察日志)

请下载源码,运行 demo中的 module:lazyloadingfragments

最简单的情况,单个ViewPager内滑动时

c0b0ae7c7a3ba6b20d16eae0506aec88.gif

生命周期函数

刚启动 app:

10dccf25ad9960fc394066382ab75cd9.png

然后从0滑动到1:

40b01bf771b7b6ff922f352ac1efe1ec.png

然而,观察一下我们自定义的3个方法:

f65c78b858ad8718284386a3416458a7.png

从0滑动到1:

9216ec9b4d8cf20d747eb10db9ee8a5d.png

再多滑动几次,可以发现:

只有当前可见的那一个Fragment,才执行了 onFragmentVisibleonFragmentFirstVisible,并且 onFragmentFirstVisible只有在 onDestroyView执行之后,才会再次执行

ViewPager所在Activity 发生跳转,又跳回来

bb9e21da9319060c6c74c239a268c44e.gif

生命周期函数:

a1a46543a9f02bc7a9a2588c7e14b6ce.png

自定义的方法:

826879c4d281e0d5b3cde0d86c7221fd.png

可以确定:

Activity跳转,只有当前可见的Fragment才会有方法执行,不可见的那些Fragment没有任何多余的操作

ViewPager嵌套 + 所在Activity发生跳转,又跳回来

tips: 验证这种情况,必须:

d2541766e8be007572256b2859f3716e.png

走起:

d8bf902ca78dc03bc4213188ed8ec13e.gif

生命周期函数:

e73bac447076feb5e43f1af4544a17b9.png

d961423f527839c834e0913add42cfd2.png

这里看得很清楚,为什么说生命周期函数不可信了吧?

然而,看看我们自定义的方法:

5bac09296e17a1751a40dd36e60edae5.png

ab66a07ee20edb5a61d14b0a02327388.png

其实还有另外的操作,就是内外 ViewPager联动滑动,这里我就不贴图了,结果都是一样,凡是不可见的 Fragment,无论内外,都没有多余的操作。

可见:我们的懒加载机制 完全解决了 各种情况下的生命周期不可靠的问题

其实最后还有一种操作 就是 FragmentManagershow hide,结果也是一样,没有多余的操作,时间原因,不写在 demo里面了.

七 、总结 程序开发者的修炼之路

这几年几乎年年都在传,互联网不景气,然而就算如此,我们已经入行了,就必须往前。那么如何进步,如何进阶。编程开发的学习资料网上几乎铺天盖地到处都是,其中不乏一些 精致优秀的博客文章视频,那么拿到这些学习资源之后怎么办,没有捷径。笔者从高手那里取得这份Demo源码之后,从头到尾,反复研读,阅读源码,反复验证,然后自己写一遍,忙里偷闲,历时一周,总算总结出一篇能读的技术文章,希望能对看到此文有缘人有些许帮助,而且 总结文章,也是对我自身的一种提升。

Demo github地址已经给出,Demo的讲解,上文应该已经很详细了。再有问题,或者发现文章中的错误,欢迎联系本人。qq:545679452

各位开发者, fighting~

欢迎转载,但是请务必注明出处.

八 、鸣谢

感谢 享学课堂 avlin老师 提供的demo 以及视频

后台回复【4】加入享学课堂粉丝交流群,相亲交友,技术交流

推荐阅读:

Android技术——ASM字节码插桩

View Pager 性能优化之 无限循环

死磕设计模式之单例模式

今日课题:

5ea3e4c3ce02efdaa245f6d8cff9d9cf.png

↑点击图片直接跳转观看免费直播课程↑

20e9ed06c2ffa9451fb7e140bc1066b9.png

↑点击图片直接跳转观看免费直播课程↑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值