本文共 11050 字,大约阅读时间需要 36 分钟。
目录
出现问题的前提:
1)在ViewPager加载Fragment的时候,由于Fragment有预加载,所以在请求服务器的方法不能像平常一样,放到生命周期方法中,因为这样会导致在加载一个页面可见的时候,另一个预加载的页面也在执行生命周期,同时请求两个接口,造成请求资源的浪费。
2)目前在请求服务器的方法中需要Context对象的实例,而Context对象只有在Fragment执行到onAttach()时才会获得。
所以就有了一种方式来实现有预加载的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版本之后,发现在第一个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对象。
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条件不在满足了吗?对源码的分析下原因。
在24.0.0的源码基础上进行debug。
在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()该条件不成立
设置第一个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对象的原因。
经过上述两步之后,第一个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的生命周期方法
看一下里面几个主要的方法。
当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(),同时可以看到该方法是一个同步调用。
在该run()中符合第一个if条件,进入到calculateFragments()中。
public void run() { //。。。。省略代码 //此时符合该if条件,进入到if if (SUPPORTS_TRANSITIONS && mManager.mCurState >= Fragment.CREATED) { firstOutFragments = new SparseArray(); lastInFragments = new SparseArray (); calculateFragments(firstOutFragments, lastInFragments); state = beginTransition(firstOutFragments, lastInFragments, false); } //。。。省略代码,后面在介绍 }
最终调用到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); } }
此时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。
由第一个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对象。
转载地址:http://jpejz.baihongyu.com/