Android Fragment 懒加载
一、为什么要进行懒加载
一般我们在使用add+show+hide去显示、隐藏fragment或者fragment嵌套使用、viewpager+fragment结合使用等场景下,如果不进行懒加载会导致多个fragment页面的生命周期被调用,每个页面都进行网络请求这样会产生很多无用的请求,因为实际显示的只是用户看到的那个页面,其他页面没有必要在这个时候去加载数据。
二、fragment懒加载
懒加载即在页面第一次可见时再进行数据请求以及加载的操作,因为版本的不同实现懒加载的方式也略有不同。
首先我们来看下旧版(support包)懒加载的实现方式,其实旧版主要是利用setUserVisibleHint和onHiddenChanged两个函数来实现懒加载的。
- viewpager+fragment
viewpager+fragment结合的方式,一共有4个fragment分别是fragmentA、fragmentB、fragmentC、fragmentD。
ViewPager 预缓存 Fragment 的个数为1的情况下fragment的生命周期变化。
右划到fragmentB时的变化
返回fragmentA时的变化
由上面我们可以看出每次setUserVisibleHint函数都会比fragment的生命周期函数先回调,且仅当前显示页面的isVisibleToUser为true,其他页面isVisibleToUser均为false。进入fragmentA页面时同时加载了fragmentA和fragmentB且两者都回调了onresume生命周期函数,当划向fragmentB之后加载了fragmentC但是fragmentB未回调任何生命周期函数仅回调了setUserVisibleHint。
因此我们可以利用setUserVisibleHint来进行fragment的懒加载。下面上代码
public class LazyLoadFragment extends Fragment {
//判断是否已进行过加载,避免重复加载
private boolean isLoad=false;
//判断当前fragment是否可见
private boolean isVisibleToUser = false;
//判断当前fragment是否回调了resume
private boolean isResume = false;
@Override
public void onResume() {
super.onResume();
isResume=true;
lazyLoad();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG, “setUserVisibleHint: “+isVisibleToUser+” “+FragmentD.this);
this.isVisibleToUser=isVisibleToUser;
lazyLoad();
}
private void lazyLoad() {
if (!isLoad&&isVisibleToUser&&isResume){
//懒加载。。。
isLoad=true;
}
}
-
add+show+hide
Add fragmentA和fragmentB然后hide B show A,下面是两者相关函数回调结果
然后 show B hide A
再show A hide B
右上边可以看出一开始add A和B时两者都回调了onresume,但是B还 回调了onHiddenChanged且为true,之后show B hide A 没有触发任何生命周期函数,两者仅回调了onHiddenChanged 且A为true B为false。
由此我们可以利用onHiddenChanged 来完成懒加载机制,下面是代码
public class FragmentD extends Fragment {
//判断是否已进行过加载,避免重复加载
private boolean isLoad=false;
//判断当前fragment是否可见
private boolean isHidden=true;
@Override
public void onResume() {
super.onResume();
lazyLoad();
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
Log.i(TAG, “onHiddenChanged: “+hidden+” “+FragmentD.this);
isHidden=hidden;
lazyLoad();
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false; //注意当view销毁时需要把isLoad置为false
}
private void lazyLoad() {
if (!isLoad&&!isHidden){
//懒加载。。。
isLoad=true;
}
}
}
- 复杂嵌套的fragment
当add+hide+show和viewpager+fragment 嵌套组合使用时上面的懒加载就需要做一些调整。
public class FragmentD extends Fragment {
//判断是否已进行过加载,避免重复加载
private boolean isLoad=false;
//判断当前fragment是否可见
private boolean isVisibleToUser = false;
//判断当前fragment是否回调了resume
private boolean isResume = false;
private boolean isCallUserVisibleHint = false;
@Override
public void onResume() {
super.onResume();
isResume=true;
if (!isCallUserVisibleHint) isVisibleToUser=!isHidden();
lazyLoad();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG, “setUserVisibleHint: “+isVisibleToUser+” “+FragmentD.this);
this.isVisibleToUser=isVisibleToUser;
isCallUserVisibleHint=true;
lazyLoad();
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
Log.i(TAG, "onHiddenChanged: "+hidden+" "+FragmentD.this);
isVisibleToUser=!hidden;
lazyLoad();
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false;
isCallUserVisibleHint=false;
isVisibleToUser=false;
isResume=false;
}
private void lazyLoad() {
if (!isLoad&&isVisibleToUser&&isResume){
//懒加载。。。
isLoad=true;
}
}
看完旧版的我们来看下新版的(Androidx)fragment的懒加载应该如何实现。
Google 在 Androidx 在FragmentTransaction中增加了setMaxLifecycle方法来控制 Fragment 所能调用的最大的生命周期函数.该方法可以设置活跃状态下 Fragment 最大的状态,如果该 Fragment 超过了设置的最大状态,那么会强制将 Fragment 降级到正确的状态。
- ViewPager+Fragment
在 FragmentPagerAdapter 与 FragmentStatePagerAdapter 新增了含有behavior字段的构造函数
public FragmentPagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
如果 behavior 的值为 BEHAVIOR_SET_USER_VISIBLE_HINT,那么当 Fragment 对用户的可见状态发生改变时,setUserVisibleHint 方法会被调用。
如果 behavior 的值为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT ,那么当前选中的 Fragment 在 Lifecycle.State RESUMED 状态 ,其他不可见的 Fragment 会被限制在 Lifecycle.State STARTED 状态。
使用了 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 后,确实只有当前可见的 Fragment 调用了 onResume 方法。而导致产生这种改变的原因,是因为 FragmentPagerAdapter 在其 setPrimaryItem 方法中调用了 setMaxLifecycle 方法
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
//如果当前的fragment不是当前选中并可见的Fragment,那么就会调用
// setMaxLifecycle 设置其最大生命周期为 Lifecycle.State.STARTED
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
//对于可见的Fragment,则设置其最大生命周期为
//Lifecycle.State.RESUMED
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
所以在ViewPager+fragment 结构中实现懒加载可以这样:
public class LazyLoadFragment extends Fragment {
//判断是否已进行过加载,避免重复加载
private boolean isLoad=false;
@Override
public void onResume() {
super.onResume();
if (!isLoad){
lazyLoad();
isLoad=true;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false;
}
private void lazyLoad() {
//懒加载。。。
}
}
-
add+hide+show
参考viewpager做法,在add fragment时仅把要显示的fragment通过setMaxLifecycle设置为resume,其他fragment均设置为start。
在show、hide切换显示的fragment时仅把show的fragment通过setMaxLifecycle设置为resume,其他hide的fragment均设置为start -
复杂嵌套
当fragment嵌套fragment等复杂情况下,只要父fragment回调onresume生命周期函数那被嵌套的所有同级子fragment都会回调onresume,所以我们需要再加上fragment是否隐藏来判断是否要进行懒加载。
public class LazyLoadFragment extends Fragment {
//判断是否已进行过加载,避免重复加载
private boolean isLoad=false;
@Override
public void onResume() {
super.onResume();
if (!isLoad&& !isHidden()){
lazyLoad();
isLoad=true;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false;
}
private void lazyLoad() {
//懒加载。。。
}
}
viewpager2 本身已支持懒加载