一. 前言
在Android开发中经常会使用到ViewPager, ViewPager如果和Fragment一起使用的话, 就要考虑懒加载和预加载的问题. ViewPager有个方法setOffscreenPageLimit 这个方法可以配置缓存数量. 那是不是直接设置0就可以实现懒加载了呢? 不是的, 查看源码:
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) { //DEFAULT_OFFSCREEN_PAGES为1
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
设置0后会无效的, limit 还是被设置成默认的1
懒加载就是当用户滑动到当前的frament才能去加载数据, 这样避免加载了数据但是没有使用到, 造成了浪费.
预加载是为了提前加载数据, 让用户减少等待时间.
懒加载和预加载应该根据具体的业务要求去使用. 没有谁好谁坏之分.
但两者的前提都是要搞清楚Fragment在ViewPager中的生命周期, 下面先来弄清楚生命周期的调用.
二. Fragment在ViewPager中的生命周期
首先写一个简单的Activity里面有ViewPager 代码如下:
public class MainActivity extends AppCompatActivity {
private ViewPager viewById;
private List<Fragment> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewById = findViewById(R.id.vp);
list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
SimpleFragment simpleFragment = SimpleFragment.newInstance(i);
list.add(simpleFragment);
}
MineAdapter mineAdapter = new MineAdapter(getSupportFragmentManager(),list);
viewById.setOffscreenPageLimit(1);
viewById.setAdapter(mineAdapter);
}
}
关于这个SimpleFragment代码就不用贴出来了 很简单就是在各个生命周期中加入log.
这个MineAdapter 很简单的. 如下:
public class MineAdapter extends FragmentPagerAdapter {
List<Fragment> mPages;
public MineAdapter(FragmentManager fm, List<Fragment> pages) {
super(fm);
mPages=pages;
}
@Override
public Fragment getItem(int i) {
return mPages.get(i);
}
@Override
public int getCount() {
return mPages.size();
}
}
运行程序 来看看Fragment生命周期 日志如下:
通过日志可以看到首次进入这个activity页面的时候. 首先加载的是0号和1号, 而且不是说等0号(当前显示界面)加载完 再加载1号. 而是0号和1号生命周期交错进行. 并且都一直走到了onResume方法.
滑动到1号 这时候 1号为当前展示界面. 0号和2号为缓存. 继续看日志
这时候1号只走了一个方法setUserVisibleHint true . 然后2号生命周期从onAttach到onResume.
再滑动一下, 这时候2号为当前展示界面. 这时候会缓存1号和3号, 0号进入销毁.
三. 总结生命周期中的规律
通过以上的日志 可以总结关键几点.
- setUserVisibleHint 方法都是比较先走的. 首次进入的时候同一个Fragment的setUserVisibleHint 要走两次 一次true, 一次false.
- 被预加载的Fragment的生命周期 除了setUserVisibleHint true没走之外 其他的生命周期也走了.
- 预加载的Fragment 到显示的时候 其实只走了 setUserVisibleHint true.
- Fragment 销毁的时候 只走到了onDestroyView方法 并没有走onDestroy onDetach方法. 这点对于 执行一些回收操作非常有必要了解.
四. 懒加载的实现
懒加载是滑动到当前Fragment的时候才去调用的方法. 一般在实际业务中就是滑到了要展示的页面去调接口获取数据.
写一个基础的BaseLazyFragment , 继承这个BaseLazyFragment 重写lazyInit()方法. 这个方法里写你需要执行的懒加载操作.
public class BaseLazyFragment extends Fragment {
private boolean isViewPrepared; // 标识fragment视图已经初始化完毕
private boolean hasFetchData; // 标识已经触发过懒加载数据
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
lazyFetchDataIfPrepared(); //经过了预加载页面, 然后展示
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isViewPrepared = true;
lazyFetchDataIfPrepared(); //首次进入, 没有预加载直接加载数据
}
/**
* 懒加载方法,获取数据什么的放到这边来使用,在切换到这个界面时才进行网络请求
*/
private void lazyFetchDataIfPrepared() {
// 用户可见fragment && 没有加载过数据 && 视图已经准备完毕
if (getUserVisibleHint() && !hasFetchData && isViewPrepared) {
hasFetchData = true; //已加载过数据
lazyInit();
}
}
/**
* 执行需要懒加载的方法
*/
protected void lazyInit() {
Log.i("zmin........." + getArguments().getInt("key"), ".............加载完成数据");
}
@Override
public void onDestroyView() {
super.onDestroyView();
hasFetchData = false;
isViewPrepared = false;
Log.i("zmin........." + getArguments().getInt("key"), ".............onDestroyView");
}
}
五. 预加载
1. 预加载在viewpager中
viewpager其实对预加载有很好的支持. 可以直接调用方法setOffscreenPageLimit来设置缓存的数量.
2. 监听可见和不可见的状态
在实际业务中, 可能存在这样一种需求. 虽然是需要预加载的, 但是要监听Fragment的可见状态. 比如Fragment中有视频播放. 如果Fragment可见的话就要播放. 不可见的时候就需要暂停. 这时候还需要考虑的是Fragment可能会跳转到其他界面. Fragment虽然可见和不可见有个生命周期方法setUserVisibleHint回调, 但是无法直接得知当前状态是一直不可见的,还是由可见转为不可见的 .
下面来实现这个功能 :
public class BaseAppearFragment extends Fragment {
private boolean isViewPrepared; // 标识fragment视图已经初始化完毕
private boolean hasAppear; //标识界面当前可见
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//当前fragment转为可见状态
if (isVisibleToUser && isViewPrepared && !hasAppear) {
onFragmentAppear();
hasAppear = true;
}
//当前fragment转为不可见状态
if (!isVisibleToUser && hasAppear) {
onFragmentDismiss();
hasAppear = false;
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.i("zmin........." + getArguments().getInt("key"), ".............onCreateView");
return inflater.inflate(R.layout.activity_fragment, null);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isViewPrepared = true;
TextView tv = getView().findViewById(R.id.tv);
tv.setText(String.valueOf(getArguments().getInt("key")));
Log.i("zmin........." + getArguments().getInt("key"), ".............onViewCreated");
}
@Override
public void onResume() {
super.onResume();
Log.i("zmin........." + getArguments().getInt("key"), ".............onResume(");
if (getUserVisibleHint()) {
onFragmentAppear();
hasAppear = true;
}
}
@Override
public void onDestroyView() {
isViewPrepared = true;
super.onDestroyView();
Log.i("zmin........." + getArguments().getInt("key"), ".............onDestroyView");
}
/**
* 界面可见
*/
public void onFragmentAppear() {
Log.i("zmin........." + getArguments().getInt("key"), "......界面可见..onFragmentAppear");
}
/**
* 界面由可见转为不可见
*/
public void onFragmentDismiss() {
Log.i("zmin........." + getArguments().getInt("key"), "...由可见转为不可见.........onFragmentDismiss");
}
}
可以看到主要在在setUserVisibleHint和onResume方法中做判断. 因为Fragment切换的时候, 很多生命周期方法是不走的.
- 当0号滑动到1号的时候, 这时候1号只走setUserVisibleHint方法. onResume方法是不走的.
- 当1号跳转到其他界面再返回的时候 会执行onResume但是不执行setUserVisibleHint .
- 而首次进入的时候setUserVisibleHint 和onResume都执行.
六 总结
通过详细的日志 分析了Fragment生命周期的执行. 从而实现懒加载和预加载中对可见状态监听. 很多业务场景下需要用到. 如果要懒加载可以直接继承BaseLazyFragment 类即可. 如果要监听可见隐藏状态则可以继承 BaseAppearFragment . 如果还想自己去看看打印的日志. 可以clone代码, github地址 https://github.com/zmin666/ZminDemo
希望这些总结对你有帮助.