目录
1)在ViewPager中初始化Item,加载Fragment时,第一次调用setUserVisibleHint()
2)设置ViewPager的第一个Fragment可见时,即在初始化时第二次调用到setUserVisibleHint()
2. 第二次调用到FragmentManager的moveToState()
问题追溯
出现问题的前提:
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对象。