前言
关于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
看下调用的情况
很直观的可以看到在FragmentPagerAdapter中有3次调用,进到调用的地方看下源码
主要在两个方法中设置的fragment的是否显示instantiateItem
和setPrimaryItem
以下是源码
@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
的调用
可以看到就在ViewPager中Adapter使用了一次是在populate
方法内,看方法名,我们可以知道这个ViewPager在填充fragment的方法。
至此我们就可以知道setUserVisibleHint
的具体逻辑了。
首先先向FragmentPagerAdapter中加入多个fragment对象
ViewPager通过
setOffscreenPageLimit
中的值来确认需要填充的fragment数量,即调用populate
方法populate
方法先确认需要添加的个数让Adapter调用instantiateItem
方法加入fragment最后让Adapter调用
setPrimaryItem
方法来设置fragment的setUserVisibleHint
属性
这样就有了我们一开始看到的log
开始需要预加载1个fragment就调用了2次instantiateItem
,然后再设置setPrimaryItem
来设置显示与否。
之后滑到第二页是调用了1次instantiateItem
,然后设置setPrimaryItem
这个时候有第一页和第二页,所以设置第一页隐藏,第二页显示。
总结
通过逻辑我们可以知道为什么可以在setUserVisibleHint
方法中来控制是否加载fragment网络数据,即百度到的fragment懒加载。
本篇就算是一段记录吧!