Support v4 24.0.0的在初始化中setUserVisibleHint中获取Context为null

 

目录

问题追溯

实现Fragment的延迟请求接口的解决方案

升级到24.0.0版本的变化

Debug查看setUserVisibleHint()

1)在ViewPager中初始化Item,加载Fragment时,第一次调用setUserVisibleHint()

2)设置ViewPager的第一个Fragment可见时,即在初始化时第二次调用到setUserVisibleHint()

3)开始加载Fragment的生命周期

1. 第一次调用moveToState()

2. 第二次调用到FragmentManager的moveToState() 

4)另外断点发现下面两点内容

解决问题方案


问题追溯

出现问题的前提:

1)在ViewPager加载Fragment的时候,由于Fragment有预加载,所以在请求服务器的方法不能像平常一样,放到生命周期方法中,因为这样会导致在加载一个页面可见的时候,另一个预加载的页面也在执行生命周期,同时请求两个接口,造成请求资源的浪费。

2)目前在请求服务器的方法中需要Context对象的实例,而Context对象只有在Fragment执行到onAttach()时才会获得。

实现Fragment的延迟请求接口的解决方案

所以就有了一种方式来实现有预加载的Fragment的延迟请求服务器资源。

解决方案通常为:在setUserVisibleHint()方法中通过getUserVisibleHint()的返回值为true,即Fragment可见的时候,调用接口;在返回值为false,即Fragment不可见的时候,做其他的一些逻辑处理。

  @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        //用来防止viewpager预加载
        if (getUserVisibleHint()) {
            onVisible();
        } else {
            onInvisible();
        }
    }

注意这个方法并不是Fragment的生命周期方法,会在所有的生命周期方法之前进行调用,并且在每次切换ViewPager中的Fragment的时候,必然走该方法,Fragment的isVisibleToUser由true变为false,或者从false变为true。

目前我们在项目中也是在onVisible()中调用请求服务器的接口,所以需要在setUserVisibleHint()中获取到Context对象。

注意setUserVisibleHint()会在所有的生命周期方法之前调用。

升级到24.0.0版本的变化

在24.0.0之前的版本是没有问题的,但是在升级到24.0版本之后,发现在第一个Fragment在初次显示的时候,出现了Context为null的问题。进而跟进代码,发现setUserVisibleHint()的代码发生变化。

  • 1)24.0.0之前的代码如下:

    public void setUserVisibleHint(boolean isVisibleToUser) {
        if (!mUserVisibleHint && isVisibleToUser && mState < STARTED) {
            mFragmentManager.performPendingDeferredStart(this);
        }
        mUserVisibleHint = isVisibleToUser;
        mDeferStart = !isVisibleToUser;
    }

在代码执行到setUserVisibleHint()的方法时,满足if条件,从而执行到 mFragmentManager.performPendingDeferredStart(this);在该方法中会去调用到onAttach(),将Context传入到Fragment中。所以在24.0.0之前的版本中可以在调用完super.setUserVisibleHint()获取到Context对象。

  • 2)24.0.0的代码如下:
    public void setUserVisibleHint(boolean isVisibleToUser) {
        if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
                && mFragmentManager != null && isAdded()) {
            mFragmentManager.performPendingDeferredStart(this);
        }
        mUserVisibleHint = isVisibleToUser;
        mDeferStart = mState < STARTED && !isVisibleToUser;
    }
    /**
     * Return true if the fragment is currently added to its activity.
     */
    final public boolean isAdded() {
        return mHost != null && mAdded;
    }

可以很明显的看到24.0.0增加了判断条件:mFragmentManager != null && isAdded(),才会执行mFragmentManager.performPendingDeferredStart(this);的方法。其中在performPendingDeferredStart()中,有根据Fragment的INITIALIZING对fragment的mHost进行赋值,并加载Fragment的onAttach()从而获取当前Activity。

难道是现在这个if条件不在满足了吗?对源码的分析下原因。

Debug查看setUserVisibleHint()

在24.0.0的源码基础上进行debug。

1)在ViewPager中初始化Item,加载Fragment时,第一次调用setUserVisibleHint()

在ViewPager的addNewItem()方法第一个Fragment加进来,调用流程如下:

方法内容如下:

 @Override
    public Object instantiateItem(ViewGroup container, int position) {
      //...省略代码
        //从Fragment的Adapter的设置的集合中取出当前位置的Fragment
        Fragment fragment = getItem(position);
       //...省略代码
        fragment.setUserVisibleHint(false);
       //...省略代码
        return fragment;
    }

通过debug发现,在初始化渲染第一个Fragment的时候,执行setUserVisibleHint()的时候, mFragmentManager和mHost为null,mFragmentManager != null && isAdded()该条件不成立

2)设置ViewPager的第一个Fragment可见时,即在初始化时第二次调用到setUserVisibleHint()

设置第一个Fragment可见,方法调用流程:

方法内容:

   @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);
            }
        //设置当前的fragment可见
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

 mFragmentManager不为null ,mHost仍为为null,mFragmentManager != null && isAdded()该条件不成立。此时setUserVisibleHint()的isVisibleToUser由false变为true。

所以不会走到mFragmentManager.performPendingDeferredStart()这个方法,所以不执行Fragment的onAttach(),所以无法取得Context。这就是为什么在项目中调用setUserVisibleHint()的时候,获取不到Context对象的原因。

3)开始加载Fragment的生命周期

经过上述两步之后,第一个Fragment已经由getUserVisibleHint()已经有false变为true,FragmentManager的mCurState已经有开始的INITIALIZING变成RESUMED状态(其中变化过程:FragmentActivity在加载Fragment的时候,在FragmentActivity生命周期onCreate()/onStart()/onResume()中分dispatchCreate()/dispatchStart()/dispatchResume()将FragmentManager的mCurState的状态变成RESUMED状态,因为本实例验证的是在Fragment中加载ViewPager,所以ViewPager加载的Fragment是子Fragment)。然后开始加载第一个Fragment的生命周期方法

看一下里面几个主要的方法。

1. 第一次调用moveToState()

当ViewPager完成measure之后,会调用到FragmentStatePagerAdapter的finishUpdate()的方法,通过下面几个方法第一次调用到moveToState()

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitNowAllowingStateLoss();
            mCurTransaction = null;
        }
    }
  • (1)调用到BackStackRecord的commitNowAllowingStateLoss()

    @Override
    public void commitNowAllowingStateLoss() {
        disallowAddToBackStack();
        mManager.execSingleAction(this, true);
    }  

将自身传入到FragmentManagerImpl中。

    public void execSingleAction(Runnable action, boolean allowStateLoss) {
  //...省略代码
        mExecutingActions = true;
        action.run();
        mExecutingActions = false;

        doPendingDeferredStart();
    }

其实就是执行了 BackStackRecord的run(),同时可以看到该方法是一个同步调用。

  • (2)BackStackRecord的run()

在该run()中符合第一个if条件,进入到calculateFragments()中。 

 public void run() {
      //。。。。省略代码
        //此时符合该if条件,进入到if
        if (SUPPORTS_TRANSITIONS && mManager.mCurState >= Fragment.CREATED) {
            firstOutFragments = new SparseArray<Fragment>();
            lastInFragments = new SparseArray<Fragment>();

            calculateFragments(firstOutFragments, lastInFragments);

            state = beginTransition(firstOutFragments, lastInFragments, false);
        }

      //。。。省略代码,后面在介绍
    }
  • (3)FragmentManager的moveToState()

最终调用到FragmentManager的moveToState(),刚开始Fragment的mState为INITIALIZING,

    void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
        //.......省略代码
        if (f.mState < newState) {
            //.......省略代码
            switch (f.mState) {
                case Fragment.INITIALIZING:
                //.......省略代码
(1).对Fragment的变量进行赋值
                    f.mHost = mHost;
                    f.mParentFragment = mParent;
                //根据是否是子Fragment获取对应的FragmentHost
                    f.mFragmentManager = mParent != null
                            ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
                    f.mCalled = false;
(2)调用onAttach()
                    f.onAttach(mHost.getContext());
                    //.......省略代码
                  if (f.mParentFragment == null) {
                        mHost.onAttachFragment(f);
                    } else {
                        f.mParentFragment.onAttachFragment(f);
                    }
                   //.......省略代码
                   if (!f.mRetaining) {
(3)在该方法中调用了onCreate(savedInstanceState);同时将状态改为mState = CREATED;
                        f.performCreate(f.mSavedFragmentState);
                    }
                  //.......省略代码
            }
                 //.......省略代码
        }
   }

进行对第一个Fragment的mHost、mFragmentManager等进行赋值,并调用到Fragment的onAttach()的方法,将mHost的Context对象传到Fragment中,在继续调用到Fragment的onAttachFragment(),继续向下执行调用到Fragment的onCreate()

    public void onCreate(@Nullable Bundle savedInstanceState) {
        mCalled = true;
        restoreChildFragmentState(savedInstanceState);
        if (mChildFragmentManager != null
                && !mChildFragmentManager.isStateAtLeast(Fragment.CREATED)) {
            mChildFragmentManager.dispatchCreate();
        }
    }

在Fragmnet的onCreate()中,调用到FragmentManager的dispatchCreate(),将Fragment的状态变为Fragment.CREATED,并且执行moveToState()的方法,没有对应的匹配,方法返回到 calculateFragments()中。此时calculateFragments()里面的while条件不在满足条件,直接返回到BackStackRecord的run(),此时while条件不满足

 public void run() {
      //。。。。省略代码
       
 //刚才删掉的下面的代码逻辑
        int transitionStyle = state != null ? 0 : mTransitionStyle;
        int transition = state != null ? 0 : mTransition;
        Op op = mHead;
        while (op != null) {
            int enterAnim = state != null ? 0 : op.enterAnim;
            int exitAnim = state != null ? 0 : op.exitAnim;
            switch (op.cmd) {
                case OP_ADD: {
                    Fragment f = op.fragment;
                    f.mNextAnim = enterAnim;
//在这里会将Fragment中的mAdded置为true
                    mManager.addFragment(f, false);
                } break;
               //。。。。省略代码    
            }
            op = op.next;
        }
//再一次进入到moveToState()
        mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);

        if (mAddToBackStack) {
            mManager.addBackStackState(this);
        }
    }

2. 第二次调用到FragmentManager的moveToState() 

此时Fragment的状态为从CREATED进行匹配。

    void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
    //......省略代码               
            case Fragment.CREATED:
                if (newState > Fragment.CREATED) {
                    if (!f.mFromLayout) {
    //......省略代码
                        f.mContainer = container;
//调用Fragment的onCreateView()
                        f.mView = f.performCreateView(f.getLayoutInflater(
                                    f.mSavedFragmentState), container, f.mSavedFragmentState);
                        f.onViewCreated(f.mView, f.mSavedFragmentState);
    //......省略代码
                    }
//调用Fragment的onActivityCreated
                   f.performActivityCreated(f.mSavedFragmentState);
            }
    //......省略代码
    }

然后 调用的Fragment的onCreateView()Fragment的onActivityCreated()

由于所有的Case并没有break,所以会一直往下执行,匹配到Fragment的onStart()Fragment的onResume()

    void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
    //......省略代码
                case Fragment.STOPPED:
                    if (newState > Fragment.STOPPED) {
                        if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                        f.performStart();
                    }
                case Fragment.STARTED:
                    if (newState > Fragment.STARTED) {
                        if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
                        f.performResume();
                        f.mSavedFragmentState = null;
                        f.mSavedViewState = null;
                    }
    //......省略代码
    }

这样第一个Fragment就显示出来了。

经过上述流程,我们在看下isAdd()

    final public boolean isAdded() {
        return mHost != null && mAdded;
    }

也可以看出此时Fragment的mHost已经不为null,而mAdded也在BackStackRecord的run()中的while循环中的 case OP_ADD中将该值置为true。

4)另外断点发现下面两点内容

由第一个Fragment切换到第二个Fragment的时候,因为第二个Fragment有预加载,所以第二个Fragment 的setUserVisibleHint()的有isVisibleToUser由false变为true时,mFragmentManager和mHost为null,该mFragmentManager.performPendingDeferredStart()仍然不会执行。

再有第二个Fragment切换到第一个Fragment的时候,其mState已经不在满足mState < STARTED,所以在setUserVisibleHint()中的这个if条件没有成立的时候。

解决问题方案

通过debug发现,当升级了Support v4包之后,之前的逻辑保持不变,仍然在onVisible()去请求接口,只不过这里做一个Context判空的处理。在ViewPager进行预加载Fragment到时候,第一个可见的Fragment的onCreate()中获取Context,调用服务器的接口。这样就避免了第一个Fragment在初始化的时候在setUserVisibleHint()无法获取到Context对象,而在onAttach()中进行请求接口。而在不同的Fragment进行切换的时候,已经在setUserVisibleHint()中可以获取到了Context对象。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值