为什么需要懒加载?
我们在做安卓项目的时候,经常会有一个使用场景:ViewPage与多个Fragment组合使用。
然而,viewpager有着预加载机制:默认一次加载当前页面前后两个页面,即使设置setOffLimit(0)也没有效果。 虽然预加载优化了app的体验效果,但是这样把我们看不到的页面的数据也加载了,大大降低了性能,浪费初始化资源。
这时候,我们就需要懒加载。
什么是懒加载?
Fragment可见的时候,才加载当前页面。 没有打开的页面,就不会预加载。
说白了,懒加载就是可见的时候才去请求数据。
懒加载主要的方法
1. setUserVisibleHint()方法
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
}
设置Fragment可见或者不可见时会调用此方法。isVisibleToUser表示Fragment是否可见,在该方法里面也可以通过调用getUserVisibleHint()获得Fragment的状态是可见还是不可见的,如果可见则进行懒加载操作。
此方法会在onCreateView()之前执行,所以如果在setUserVisibleHint()要实现懒加载的话,就必须要确保View以及其他变量都已经初始化结束,避免空指针。
2. getUserVisibleHint()方法
setUserVisibleHint(boolean isVisibleToUser)方法是比onCreate更早调用的,但是我们一般在加载数据时,都会在数据加载完成时进行UI更新,所以这就有了一个问题,假如拉取数据是秒回,但是我们还没有进行UI绑定,或者是Adapter初始化等,那么我们就无法更新UI了,所以Fragment给我们提供了另一个方getUserVisibleHint(),它就是用来判断当前Fragment是否可见,所以我们就可以在一系列变量初始化完成后再判断是否可见,若可见再进行数据拉取。
Fragment懒加载方式1
LazyLoadFragment
/**
* Cerated by xiaoyehai
* Create date : 2021/1/20 16:40
* description : Fragment懒加载
* <p>
* 网络请求的方法真的只有当fragment可见时才请求,而且,加载过一次不会重复加载。
*/
public abstract class LazyLoadFragment extends Fragment {
protected Context mContext;
protected View mRootView;
/**
* 是否初始化过布局
*/
private boolean isViewInitiated;
/**
* 当前Fragment是否可见
*/
private boolean isVisibleToUser;
/**
* 是否加载过数据,确保fragment来回切换时不会重复加载数据
*/
protected boolean isDataInitiated;
/**
* Fragment创建的时候回调
*
* @param savedInstanceState
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getContext();
}
/**
* 初始化fragment的布局
*
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mRootView = inflater.inflate(getLayoutID(), container, false);
return mRootView;
}
/**
* 不是表示fragment所依赖的activity创建完成,fragment所依赖的activity的onCreate方法执行结束
*
* @param savedInstanceState
*/
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
isViewInitiated = true;
init();
//第一个默认显示的Fragment加载数据 在这里需要加载一次
initLazyData();
}
/**
* 设置Fragment可见或者不可见时会调用此方法。isVisibleToUser表示Fragment是否可见,在该方法里面也可以通过调用getUserVisibleHint()
* 获得Fragment的状态是可见还是不可见的,如果可见则进行懒加载操作。
* <p>
* 此方法比onCreate()更早调用的,所以如果在setUserVisibleHint()要实现懒加载的话,
* 就必须要确保View以及其他变量都已经初始化结束,避免空指针。
* <p>
* setUserVisibleHint(boolean isVisibleToUser)方法是比onCreate更早调用的,
* 但是我们一般在加载数据时,都会在数据加载完成时进行UI更新,所以这就有了一个问题,假如拉取数据是秒回,
* 但是我们还没有进行UI绑定,或者是Adapter初始化等,那么我们就无法更新UI了,所以Fragment给我们提供了
* 另一个方法getUserVisibleHint(),它就是用来判断当前Fragment是否可见
*
* @param isVisibleToUser
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//获得Fragment的状态是可见还是不可见的
/* if (getUserVisibleHint()) {
isVisibleToUser = true;
initLazyData();
}*/
this.isVisibleToUser = isVisibleToUser;
if (isVisibleToUser) {
initLazyData();
}
}
private void initLazyData() {
if (isVisibleToUser && isViewInitiated && !isDataInitiated) {
initData();
isDataInitiated = true;
}
}
/**
* 判断懒加载条件
*
* @param forceUpdate 强制更新,没啥用
*/
public void initLazyData(boolean forceUpdate) {
if (isVisibleToUser && isViewInitiated && (!isDataInitiated || forceUpdate)) {
initData();
isDataInitiated = true;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
isViewInitiated = false;
isDataInitiated = false;
}
protected abstract int getLayoutID();
protected abstract void init();
protected abstract void initData();
}
注意事项
-
添加isViewInitiated参数。在系统调用onActivityCreated时设置为true,这时onCreateView方法已调用完毕(一般我们在这方法里执行findviewbyid等方法),确保initLazyData()方法不会报空指针异常。
-
getUserVisibleHint()会返回是否可见状态,这是fragment实现懒加载的关键,只有fragment可见才会调用加载数据。
-
添加isDataInitiated参数。确保ViewPager来回切换时LazyLoadFragment的initData方法不会被重复调用,initLazyData在该Fragment的整个生命周期只调用一次,第一次调用initLazyData()方法后马上执行 isDataInitiated = true。
我在Fragment中打印了Fragment生命周期的几个比较重要的方法,从log上看setUserVisibleHint()的调用早于onCreateView,所以如果在setUserVisibleHint()要实现懒加载的话,就必须要确保View以及其他变量都已经初始化结束,避免空指针。
MainActivity
public class MainActivity extends AppCompatActivity {
private TabLayout mTabLayout;
private ViewPager mViewPager;
private String[] titles = {"女装", "美食", "美妆", "居家用品", "男装", "鞋品"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
mViewPager = (ViewPager) findViewById(R.id.viewPager);
MyPagerAdapter myPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), titles);
mViewPager.setAdapter(myPagerAdapter);
mViewPager.setOffscreenPageLimit(titles.length);
mTabLayout.setupWithViewPager(mViewPager);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabIndicatorColor="#f00"
app:tabMode="scrollable"
app:tabSelectedTextColor="#f00"
app:tabTextColor="@color/black" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tabLayout">
</androidx.viewpager.widget.ViewPager>
</LinearLayout>
MyPagerAdapter
/**
* Cerated by xiaoyehai
* Create date : 2019/12/15 17:40
* description :
*/
public class MyPagerAdapter extends FragmentPagerAdapter {
private String[] titles;
private String[] tabIds = {"1", "17", "31", "47", "59", "72"};
public MyPagerAdapter(@NonNull FragmentManager fm, String[] titles) {
super(fm);
this.titles = titles;
}
@NonNull
@Override
public Fragment getItem(int position) {
return TabFragment.newInstance(tabIds[position]);
}
@Override
public int getCount() {
return titles.length;
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
}
TabFragment
public class TabFragment extends LazyLoadFragment {
private RecyclerView mRecyclerView;
private LinearLayout mLlProgress;
private String mTabId;
private String url;
private List<ShopInfo.PDataBean> mDatas = new ArrayList<>();
private TabAdaper mTabAdaper;
public static TabFragment newInstance(String tabId) {
TabFragment tabFragment = new TabFragment();
Bundle bundle = new Bundle();
bundle.putString("tabId", tabId);
tabFragment.setArguments(bundle);
return tabFragment;
}
@Override
protected int getLayoutID() {
return R.layout.fragment_tab;
}
@Override
protected void init() {
mRecyclerView = mRootView.findViewById(R.id.recyclerView);
mLlProgress = mRootView.findViewById(R.id.ll_progress);
if (getArguments() != null) {
mTabId = getArguments().getString("tabId");
url = "http://mgapp.sitezt.cn/api/info/itemsearch/searchall?search.Keyword=&search.Page=1" +
"&search.PageSize=20&search.Sort=0&search.SortOrder=1&search.Category=" + mTabId + "&search.SecondCategory=-1" +
"&search.QueryTime=-1";
}
Log.e("xyh", "init: " + mTabId);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
mTabAdaper = new TabAdaper(getActivity(), mDatas, R.layout.item_shop);
mRecyclerView.setAdapter(mTabAdaper);
}
@Override
protected void initData() {
Log.e("xyh", "initData: " + mTabId);
loadData();
}
private void loadData() {
mLlProgress.setVisibility(View.VISIBLE);
OkHttpManager.getInstance().asyncJsonStringByURL(url, new OkHttpManager.StringCallback() {
@Override
public void onResponse(String result) {
mLlProgress.setVisibility(View.GONE);
//Log.e("xyh", "onResponse: " + result);
ShopInfo shopInfo = new Gson().fromJson(result, ShopInfo.class);
mDatas.addAll(shopInfo.getPData());
mTabAdaper.notifyDataSetChanged();
}
@Override
public void onFailure(IOException e) {
mLlProgress.setVisibility(View.GONE);
//Log.e("xyh", "onFailure: " + e.getMessage());
}
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/ll_progress"
android:layout_width="130dp"
android:layout_height="130dp"
android:gravity="center"
android:visibility="gone"
android:layout_marginBottom="25dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:background="#99000000"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment懒加载方式2
加上FragmentTransaction来控制fragment的hide和show。
在这里插入代码片
Fragment懒加载方式3
LazyLoadFragment2
/**
* Frgament懒加载
*/
public abstract class LazyLoadFragment2 extends Fragment {
//当前Fragment是否首次可见,默认是首次可见
private boolean mIsFirstVisible = true;
//当前Fragment是否首次不可见
private boolean mIsFirstInvisible = true;
//当前Fragment的View是否已经创建
private boolean isViewCreated = false;
//当前Fragment的可见状态,一种当前可见,一种当前不可见
private boolean currentVisibleState = false;
protected View mRootView;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// 对于默认 tab 和 间隔 checked tab 需要等到 isViewCreated = true 后才可以通过此通知用户可见
// 这种情况下第一次可见不是在这里通知 因为 isViewCreated = false 成立,等从别的界面回到这里后会使用 onFragmentResume 通知可见
// 对于非默认 tab mIsFirstVisible = true 会一直保持到选择则这个 tab 的时候,因为在 onActivityCreated 会返回 false
if (isViewCreated) {
if (isVisibleToUser && !currentVisibleState) {
dispatchUserVisibleHint(true);
} else if (!isVisibleToUser && currentVisibleState) {
dispatchUserVisibleHint(false);
}
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mRootView = inflater.inflate(getLayoutID(), container, false);
return mRootView;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
isViewCreated = true;
init();
//isHidden()是Fragment是否处于隐藏状态和isVisible()有区别
// !isHidden() 默认为true,在调用 hide show 的时候可以使用
//getUserVisibleHint(),Fragement是否可见
if (!isHidden() && getUserVisibleHint()) {
//如果Fragment没有隐藏且可见
//执行分发的方法,三种结果对应自Fragment的三个回调,对应的操作,Fragment首次加载,可见,不可见
dispatchUserVisibleHint(true);
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
dispatchUserVisibleHint(false);
} else {
dispatchUserVisibleHint(true);
}
}
@Override
public void onResume() {
super.onResume();
if (!mIsFirstVisible) {
//表示点击home键又返回操作,设置可见状态为ture
if (!isHidden() && !currentVisibleState && getUserVisibleHint()) {
dispatchUserVisibleHint(true);
}
}
}
@Override
public void onPause() {
super.onPause();
// 当前 Fragment 包含子 Fragment 的时候 dispatchUserVisibleHint 内部本身就会通知子 Fragment 不可见
// 子 fragment 走到这里的时候自身又会调用一遍 ?
if (currentVisibleState && getUserVisibleHint()) {
dispatchUserVisibleHint(false);
}
}
/**
* 统一处理 显示隐藏
*
* @param visible
*/
private void dispatchUserVisibleHint(boolean visible) {
//当前 Fragment 是 child 时候 作为缓存 Fragment 的子 fragment getUserVisibleHint = true
//但当父 fragment 不可见所以 currentVisibleState = false 直接 return 掉
// 这里限制则可以限制多层嵌套的时候子 Fragment 的分发
if (!isViewCreated)
return;
if (visible && isParentInvisible())
return;
//此处是对子 Fragment 不可见的限制,因为 子 Fragment 先于父 Fragment回调本方法 currentVisibleState 置位 false
// 当父 dispatchChildVisibleState 的时候第二次回调本方法 visible = false 所以此处 visible 将直接返回
if (currentVisibleState == visible) {
return;
}
currentVisibleState = visible;
if (visible) {
if (mIsFirstVisible) {
mIsFirstVisible = false;
onFirstUserVisible();
} else {
onUserVisible();
}
dispatchChildVisibleState(true);
} else {
dispatchChildVisibleState(false);
if (mIsFirstInvisible) {
mIsFirstInvisible = false;
onFirstUserInvisible();
} else {
onUserInvisible();
}
}
}
/**
* 用于分发可见时间的时候父获取 fragment 是否隐藏
*
* @return true fragment 不可见, false 父 fragment 可见
*/
private boolean isParentInvisible() {
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof LazyLoadFragment2) {
LazyLoadFragment2 fragment = (LazyLoadFragment2) parentFragment;
return !fragment.isSupportVisible();
} else {
return false;
}
}
private boolean isSupportVisible() {
return currentVisibleState;
}
/**
* 当前 Fragment 是 child 时候 作为缓存 Fragment 的子 fragment 的唯一或者嵌套 VP 的第一 fragment 时 getUserVisibleHint = true
* 但是由于父 Fragment 还进入可见状态所以自身也是不可见的, 这个方法可以存在是因为庆幸的是 父 fragment 的生命周期回调总是先于子 Fragment
* 所以在父 fragment 设置完成当前不可见状态后,需要通知子 Fragment 我不可见,你也不可见,
* <p>
* 因为 dispatchUserVisibleHint 中判断了 isParentInvisible 所以当 子 fragment 走到了 onActivityCreated 的时候直接 return 掉了
* <p>
* 当真正的外部 Fragment 可见的时候,走 setVisibleHint (VP 中)或者 onActivityCreated (hide show) 的时候
* 从对应的生命周期入口调用 dispatchChildVisibleState 通知子 Fragment 可见状态
*
* @param visible
*/
private void dispatchChildVisibleState(boolean visible) {
FragmentManager childFragmentManager = getChildFragmentManager();
List<Fragment> fragments = childFragmentManager.getFragments();
if (!fragments.isEmpty()) {
for (Fragment child : fragments) {
if (child instanceof LazyLoadFragment2 && !child.isHidden() && child.getUserVisibleHint()) {
((LazyLoadFragment2) child).dispatchUserVisibleHint(visible);
}
}
}
}
protected abstract int getLayoutID();
protected abstract void init();
/**
* 第一次fragment可见(进行初始化工作)
*/
public void onFirstUserVisible() {
}
/**
* fragment可见(切换回来或者onResume)
*/
public void onUserVisible() {
}
/**
* 第一次fragment不可见(不建议在此处理事件)
*/
public void onFirstUserInvisible() {
}
/**
* fragment不可见(切换掉或者onPause)
*/
public void onUserInvisible() {
}
@Override
public void onDestroyView() {
super.onDestroyView();
isViewCreated = false;
mIsFirstVisible = true;
}
@Override
public void onDestroy() {
super.onDestroy();
isViewCreated = false;
mIsFirstVisible = true;
}
}
使用
public class TabFragment2 extends LazyLoadFragment2 {
private RecyclerView mRecyclerView;
private LinearLayout mLlProgress;
private String mTabId;
private String url;
private List<ShopInfo.PDataBean> mDatas = new ArrayList<>();
private TabAdaper mTabAdaper;
public static TabFragment2 newInstance(String tabId) {
TabFragment2 tabFragment = new TabFragment2();
Bundle bundle = new Bundle();
bundle.putString("tabId", tabId);
tabFragment.setArguments(bundle);
return tabFragment;
}
@Override
protected int getLayoutID() {
return R.layout.fragment_tab;
}
@Override
protected void init() {
mRecyclerView = mRootView.findViewById(R.id.recyclerView);
mLlProgress = mRootView.findViewById(R.id.ll_progress);
if (getArguments() != null) {
mTabId = getArguments().getString("tabId");
url = "http://mgapp.sitezt.cn/api/info/itemsearch/searchall?search.Keyword=&search.Page=1" +
"&search.PageSize=20&search.Sort=0&search.SortOrder=1&search.Category=" + mTabId + "&search.SecondCategory=-1" +
"&search.QueryTime=-1";
}
Log.e("xyh", "init: " + mTabId);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
mTabAdaper = new TabAdaper(getActivity(), mDatas, R.layout.item_shop);
mRecyclerView.setAdapter(mTabAdaper);
}
@Override
public void onFirstUserVisible() {
super.onFirstUserVisible();
Log.e("xyh", "onFirstUserVisible: " + mTabId);
loadData();
}
@Override
public void onUserVisible() {
super.onUserVisible();
Log.e("xyh", "onUserVisible: " + mTabId);
//如果每次切换到fragment都需要加载数据
//loadData();
}
@Override
public void onFirstUserInvisible() {
super.onFirstUserInvisible();
Log.e("xyh", "onFirstUserInvisible: " + mTabId);
}
@Override
public void onUserInvisible() {
super.onUserInvisible();
Log.e("xyh", "onUserInvisible: " + mTabId);
}
private void loadData() {
mLlProgress.setVisibility(View.VISIBLE);
OkHttpManager.getInstance().asyncJsonStringByURL(url, new OkHttpManager.StringCallback() {
@Override
public void onResponse(String result) {
mLlProgress.setVisibility(View.GONE);
//Log.e("xyh", "onResponse: " + result);
ShopInfo shopInfo = new Gson().fromJson(result, ShopInfo.class);
mDatas.addAll(shopInfo.getPData());
mTabAdaper.notifyDataSetChanged();
}
@Override
public void onFailure(IOException e) {
mLlProgress.setVisibility(View.GONE);
}
});
}
}
Fragment新功能,setMaxLifecycle()
Fragment新功能,setMaxLifecycle了解一下
setUserVisibleHint()方法已经过时了,我们点进setUserVisibleHint()方法的源码来看一下,对于过时的方法源码中都会注明使用哪个方法来替代,可以看到注释中写明了使用FragmentTransaction的setMaxLifecycle()方法来替代setUserVisibleHint()方法,这个setMaxLifecycle()方法是什么呢,下面我们就来具体看一下。
setMaxLifecycle()方法
最新的Fragment代码淘汰了setUserVisibleHint方法,转而支持用setMaxLifecycle方法,setMaxLifecycle言外之意是设置最大生命周期,懂行的人应该知道,Fragment一直都是无法直接设置生命周期,必须通过add、attach、remove、detach、show、hide方法间接干预。
setMaxLifecycle定义在FragmentTransaction中,和之前的add、attach、remove、detach、show、hide等方法是并列关系。
@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
@NonNull Lifecycle.State state) {
addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
return this;
}
setMaxLifecycle()方法,需要传入的参数有两个:fragment和state。fragment不用多说,就是要设置的目标Fragment,不过需要注意的是此时Fragment必须已经被添加到了FragmentManager中,也就是调用了add()方法,否则会抛出异常。state就是Lifecycle中定义的枚举类型,同样需要注意传入的state应该至少为CREATED,换句话说就是只能传入CREATED、STARTED和RESUMED,否则同样会抛出异常。
Lifecycle.State
Lifecycle.State一共有五个状态,最低要求是Lifecycle.State.CREATED,所以该方法可用的参数有CREATED、STARTED、RESUMED。
public enum State {
DESTROYED,
INITIALIZED,
CREATED,
STARTED,
RESUMED;
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
setMaxLifecycle()方法的作用
是为Fragment的状态设置上限,如果当前Fragment的状态已经超过了设置的上限,就会强制被降到相应状态。
由于setMaxLifecycle()方法要求传入的state至少为CREATED,因此我们只需研究CREATED、STARTED和RESUMED这三个状态:
- 参数传入Lifecycle.State.CREATED
Lifecycle.State.CREATED对应Fragment的CREATED状态,如果当前Fragment状态低于CREATED,也就是INITIALIZING,那么Fragment的状态会变为CREATED,依次执行onAttach()、onCreate()方法;如果当前Fragment状态高于CREATED,那么Fragment的状态会被强制降为CREATED,以当前Fragment状态为RESUMED为例,接下来会依次执行onPause()、onStop()和onDestoryView()方法。如果当前Fragment的状态恰好为CREATED,那么就什么都不做。
- 参数传入Lifecycle.State.STARTED
Lifecycle.State.STARTED对应Fragment的STARTED状态,如果当前Fragment状态低于STARTED,那么Fragment的状态会变为STARTED,以当前Fragment状态为CREATED为例,接下来会依次执行onCreateView()、onActivityCreate()和onStart()方法;如果当前Fragment状态高于STARTED,也就是RESUMED,那么Fragment的状态会被强制降为STARTED,接下来会执行onPause()方法。如果当前Fragment的状态恰好为STARTED,那么就什么都不做。
- 参数传入Lifecycle.State.RESUMED
Lifecycle.State.RESUMED对应Fragment的RESUMED状态,如果当前Fragment状态低于RESUMED,那么Fragment的状态会变为RESUMED,以当前Fragment状态为STARTED为例,接下来会执行onResume()方法。如果当前Fragment的状态恰好为RESUMED,那么就什么都不做。
光介绍结论可能不是很直观,下面就写几个例子。
FragmentPagerAdapter变动
由于setMaxLifecycle带来了生命周期设置,替换掉了老旧的setUserVisibleHint方法,所以在FragmentPagerAdapter中也进行了适配。
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
private final int mBehavior;
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}
public FragmentPagerAdapter(@NonNull FragmentManager fm,@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
可以看出一个参数的构造方法默认传入BEHAVIOR_SET_USER_VISIBLE_HINT,将其赋值给mBehavior,那么这个mBehavior在什么地方用到了呢。在FragmentPagerAdapter.java文件中全局搜索一下,发现只有两个地方用到了mBehavior:instantiateItem()方法和setPrimaryItem()方法。instantiateItem()方法我们很熟悉,是初始化ViewPager中每个Item的方法,setPrimaryItem()方法我此前没有接触过,简单地看了一下源码发现它的作用是设置ViewPager将要显示的Item,在ViewPager切换时会调用该方法,我们来看一下FragmentPagerAdapter中的setPrimaryItem()方法:
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// 如果mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,则将上一个Fragment的状态设置为STARTED
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
// 如果mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,则将当前Fragment的状态设置为RESUMED
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
方法的逻辑还是很简单的,如果mBehavior的值为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,那么就调用setMaxLifecycle()方法将上一个Fragment的状态设置为STARTED,将当前要显示的Fragment的状态设置为RESUMED;反之如果mBehavior的值为BEHAVIOR_SET_USER_VISIBLE_HINT,那么依然使用setUserVisibleHint()方法设置Fragment的可见性,相应地可以根据getUserVisibleHint()方法获取到Fragment是否可见,从而实现懒加载,具体做法我就不说了。
Fragment懒加载新方案
setUserVisibleHint()方法已经过时了,过去使用setUserVisibleHint来控制Fragment懒加载,在最新版的FragmentPagerAdapter里有新思路,可以切换到BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT模式,在FragmentonResume里判断,更符合显示逻辑。
在Fragment变为可见时都会执行onResume()方法,我们可以利用这一点来实现懒加载,将Fragment加载数据的逻辑放到onResume()方法中,这样就保证了Fragment可见时才会加载数据。声明一个变量标记是否是首次执行onResume()方法,因为每次Fragment由不可见变为可见都会执行onResume()方法,需要防止数据的重复加载。此外,如果我们使用的是FragmentPagerAdapter,切换导致Fragment被销毁时是不会执行onDestory()和onDetach()方法的,只会执行到onDestroyView()方法,因此在onDestroyView()方法中我们还需要将这个变量重置,否则当Fragment再次可见时就不会重新加载数据了。
/**
* setMaxLifecycle()方式懒加载
*/
public abstract class LazyLoadFragment3 extends Fragment {
protected Context mContext;
protected View mRootView;
private boolean mIsFirstLoad = true; // 是否第一次加载
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getContext();
}
//在Fragment每次变为可见时都会执行onResume()方法,我们可以利用这一点来实现懒加载
@Override
public void onResume() {
super.onResume();
if (mIsFirstLoad) {
initData();
mIsFirstLoad = false;
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mRootView = inflater.inflate(getLayoutID(), container, false);
return mRootView;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
init();
}
@Override
public void onDestroyView() {
super.onDestroyView();
mIsFirstLoad = true;
}
protected abstract int getLayoutID();
protected abstract void init();
protected abstract void initData();
}
public class MainActivity4 extends AppCompatActivity {
private TabLayout mTabLayout;
private ViewPager mViewPager;
private String[] titles = {"女装", "美食", "美妆", "居家用品", "男装", "鞋品"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout);
mViewPager = (ViewPager) findViewById(R.id.viewPager);
MyPagerAdapter4 myPagerAdapter = new MyPagerAdapter4(getSupportFragmentManager(), titles);
mViewPager.setAdapter(myPagerAdapter);
mViewPager.setOffscreenPageLimit(titles.length);
mTabLayout.setupWithViewPager(mViewPager);
}
}
在构造FragmentPagerAdapter时传入:BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
public class MyPagerAdapter4 extends FragmentPagerAdapter {
private String[] titles;
private String[] tabIds = {"1", "17", "31", "47", "59", "72"};
public MyPagerAdapter4(@NonNull FragmentManager fm, String[] titles) {
super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.titles = titles;
}
@NonNull
@Override
public Fragment getItem(int position) {
return TestFragment4.newInstance(tabIds[position]);
}
@Override
public int getCount() {
return titles.length;
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
}