懒加载已经不是个陌生的话题,简单来说就是个最基本的性能优化。
面临的问题
- 预加载越多越卡
- 假设一个fragment 为1M 那么五个就是1*5M 到后面就会OOM
- 预加载会在不可见的时候加载,浪费太多的流量,浪费服务器资源
分析问题:
- 看ViewPager源码是怎么展示的--->数据是adapter展示的看setAdapter---> 看adapter的填充方法populate()----> 里面有缓存相关的方法 重点 设置item的方法:setPrimaryItem()
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (fragment != this.mCurrentPrimaryItem) {
if (this.mCurrentPrimaryItem != null) {
this.mCurrentPrimaryItem.setMenuVisibility(false);
this.mCurrentPrimaryItem.setUserVisibleHint(false);
}
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
this.mCurrentPrimaryItem = fragment;
}
}
然后走的是 finishUpdate--- >this.mCurTransaction.commitNowAllowingStateLoss() — > fragment 的生命周期
- 可以看到通过setUserVisibleHint来设置fragment是否可见,它是在fragment生命周期之前调用生效的,因为预加载的原因 我们无法控制fragment 生命周期,所以 要控制fragment的数据加载时机 关键还是这个函数
解决问题
一、直接利用 setUserVisibleHint判断
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isViewCreated) {
if (isVisibleToUser) {
// 加载数据的逻辑
} else {
// 停止加载数据的逻辑
}
}
}
因为 setUserVisibleHint 是在fragment生命周期之前调用的 ,有可能 加载数据的时候 view还没有创建 不加判断就会导致空指针。所以,需要在onCreateView里设置 isViewCreated = true 但是这样还是有问题:第一次加载 页面一直处在loading......页面,分析发现: 当onCreateView执行的时候 setUserVisibleHint已经执行过了 所以 不会再次调用,所以需要在onCreateView再次手动调用一次 代码如下:
isViewCreated = true;
initView(rootView)
if (getUserVisibleHint()) {
setUserVisibleHint(true);
}
二、解决数据重复问题
上面的代码写出来后依然会出现一个问题 就是 数据会重复加载 尤其是 最后一个tab到第一个tab的时候 怎么解决? 只有当前一个tab不可见而当前tab可见才展示数据 .... 也就是说当前状态和上次状态不一样才加载真正的逻辑这样才是对的。 需要再次加一个标识位记录下上次的可见状态
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isViewCreated) {
if (isVisibleToUser && !isVisibleStateUP) {
isVisibleStateUP = isVisibleToUser
// 加载数据的逻辑
} else if (!isVisibleToUser && isVisibleStateUP){
isVisibleStateUP = isVisibleToUser
// 停止加载数据的逻辑
}
}
}
三、解决页面跳转的问题
经过上面的代码之后貌似是没有问题的 ,但是当我们 从fragment跳到activity里的时候又出现问题了,都直接执行生命周期的方法了 所以没法走我们的懒加载的逻辑了 这个时候解决问题的思路就需要和生命周期来绑定了,当执行onPause我们执行上面停止加载的逻辑 ,onResume的时候执行 加载数据的逻辑 不就好了吗?
@Override
public void onResume() {
super.onResume();
if (getUserVisibleHint() && !isVisibleStateUP) {
isVisibleStateUP = isVisibleToUser
// 加载数据的逻辑
}
}
@Override
public void onPause() {
super.onPause();
if (getUserVisibleHint() && isVisibleStateUP) {
isVisibleStateUP = isVisibleToUser
// 停止加载数据的逻辑
}
}
四、解决嵌套viewPager 懒加载失效的问题
以上代码还是会出现两个问题 第一次加载的时候 会加载嵌套里的子fragment(比如 :Tab2是嵌套 默认Tab1,但是默认Tab1的时候 Tab2里的子fragment 也会执行加载数据 预加载了)。解决办法:加判断父fragment和子fragment 同时可见才执行 Tab1-->Tab2 时 嵌套viewPager的时候嵌套里的子fragment没有执行数据加载和停止加载数据的逻辑。 为了解决这个问题 我们必须设置一个标志位,判断当外层fragment可见的时候,让内嵌的所有fragment显示,手动分发事件
- 首先判断父fragment 是否可见
private boolean isParentInvisible() {
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof LazyFragment5) {
LazyFragment5 fragment = (LazyFragment5) parentFragment;
return !fragment.isVisibleStateUP;
}
return false;
}
- 手动分发子fragment里的事件
protected void dispatchChildVisibleState(boolean state) {
FragmentManager fragmentManager = getChildFragmentManager();
List<Fragment> fragments = fragmentManager.getFragments();
if (fragments != null) {
for (Fragment fragment: fragments) {
if (fragment instanceof LazyFragment5 &&!fragment.isHidden() &&
fragment.getUserVisibleHint()) {
((LazyFragment5)fragment).dispatchUserVisibleHint(state);
}
}
}
}
3.分发父fragment事件
// 分发 可见 不可见 的动作
private void dispatchUserVisibleHint(boolean visibleState) {
// 记录上一次可见的状态 实时更新状态
this.isVisibleStateUP = visibleState;
// 父fragment和子fragment 同时可见才执行的 (注意 isParentInvisible是取反的)
if (visibleState && isParentInvisible()) {
return;
}
if (visibleState) {
// 加载数据
//只对第一层有效
onFragmentLoad();
//对子fragment有效
dispatchChildVisibleState(true);
} else {
// 停止加载数据
//只对第一层有效
onFragmentLoadStop();
//对子fragment有效
dispatchChildVisibleState(false);
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isViewCreated) {
if (isVisibleToUser && !isVisibleStateUP) {
dispatchUserVisibleHint(true);
} else if (!isVisibleToUser && isVisibleStateUP){
dispatchUserVisibleHint(false);
}
}
}