对于Fragment“懒加载”问题的一点点见解
2016-12-27 23:08
316 查看
1. 问题来源
在开发过程中,或多或少会需要捕获与Fragment生命周期相关的一些事件,去做相关的数据初始化等其他操作,而Fragment的生命周期并不完全像Activity那样,两者之间还是有一些区别的。例如,我们想在用户第一次看到该Fragment的时候去加载该Fragment中的数据,并非每次用户看到Fragment都去加载数据,这时候就需要我们非常清楚Fragment的生命周期方法,才能实现理想中的效果。
然而对于初学或者不太了解Fragment生命周期的朋友,可能会在这里产生一些错误的认知,比如本人刚开始学习Android的时候,就认为Fragment执行了onResume()方法之后,Fragment就处于可与用户交互的状态。
然而实际情况并不是这样,例如现在大部分APP的设计都是底部几个Button来控制切换Fragment的显示与隐藏,在APP启动的时候会同时创建这些Fragment,并添加到Activity中去,然后利用FragmentTransaction的show()和hide()方法动态的显示或隐藏指定的Fragment。在Fragment添加到Activity中去的时候,不管Fragment有没有显示,它都已经走到onResume()生命周期了。此时实际情况是所有的Fragment都处在onResume()生命周期。
–
2. Demo示例
这只是一个简单演示项目,目的为了看起来更加的直观。2.1 XML中嵌入Fragment,或使用FragmentManager的replace方法
在这种方式下,Fragment的生命周期onResume()即可表明当前Fragment对用户可见,且处于可与用户交互的状态。
2.2 Activity+Fragment
在Activity中添加了4个Fragment,Fragment中只重写了生命周期方法,打印log。
Activity界面如图:
代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { FragmentA fragmentA = new FragmentA(); FragmentB fragmentB = new FragmentB(); FragmentC fragmentC = new FragmentC(); FragmentD fragmentD = new FragmentD(); Fragment currentFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 添加Fragment getFragmentManager().beginTransaction() .add(R.id.activity_main, fragmentA, "A") .hide(fragmentA) .add(R.id.activity_main, fragmentB, "B") .hide(fragmentB) .add(R.id.activity_main, fragmentC, "C") .hide(fragmentC) .add(R.id.activity_main, fragmentD, "D") .hide(fragmentD) .commitAllowingStateLoss(); // 默认显示FragmentA showFragment(fragmentA); findViewById(R.id.a).setOnClickListener(this); findViewById(R.id.b).setOnClickListener(this); findViewById(R.id.c).setOnClickListener(this); findViewById(R.id.d).setOnClickListener(this); } // 显示Fragment private void showFragment(Fragment fragment) { if (currentFragment != null) { getFragmentManager().beginTransaction() .show(fragment).hide(currentFragment).commitAllowingStateLoss(); } else { getFragmentManager().beginTransaction() .show(fragment).commitAllowingStateLoss(); } currentFragment = fragment; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.a: showFragment(fragmentA); break; case R.id.b: showFragment(fragmentB); break; case R.id.c: showFragment(fragmentC); break; case R.id.d: showFragment(fragmentD); break; } } }
启动APP,可以看到Log信息如下:
从日志中可以看到,添加Fragment到Activity之后,Fragment都已经执行到了onResume()生命周期方法,但是我们只能看的到FragmentA,是因为其他的Fragment全都被hide了,也就对用户不可见了,但是并不会走onPause()方法。
现在再注意这部分Log
12-26 09:30:34.720 12327-12327/cn.manchester.fragmentlazyload E/FragmentA: onHiddenChanged: true 12-26 09:30:34.720 12327-12327/cn.manchester.fragmentlazyload E/FragmentA: onHiddenChanged: false
这里的onHiddenChanged()方法是在我们调用show()方法的时候,Fragment会回调的方法,其中参数为true的时候,表示该Fragment被隐藏,否则即显示。有些人可能会想,那我用这个方法不就可以判断当前Fragment是否对用户可见了吗。当然如果仅仅是这种情况下,的确是可以这样做。
如果此时按下Home键返回到主屏幕,打印的Log信息如下:
12-26 09:50:48.490 12327-12327/cn.manchester.fragmentlazyload E/FragmentA: onPause: 12-26 09:50:48.490 12327-12327/cn.manchester.fragmentlazyload E/FragmentB: onPause: 12-26 09:50:48.490 12327-12327/cn.manchester.fragmentlazyload E/FragmentC: onPause: 12-26 09:50:48.490 12327-12327/cn.manchester.fragmentlazyload E/FragmentD: onPause: 12-26 09:50:48.960 12327-12327/cn.manchester.fragmentlazyload E/FragmentA: onStop: 12-26 09:50:48.960 12327-12327/cn.manchester.fragmentlazyload E/FragmentB: onStop: 12-26 09:50:48.960 12327-12327/cn.manchester.fragmentlazyload E/FragmentC: onStop: 12-26 09:50:48.960 12327-12327/cn.manchester.fragmentlazyload E/FragmentD: onStop:
可见虽然所有的Fragment对于用户不可见,但是却没有回调onHiddenChanged()方法,由此可知,只有我们在手动调用show()或hide()的时候才会回调onHiddenChanged()方法,仅仅靠这个方法是无法确定Fragment当前的状态的。
这时,我们又会想,那给Fragment一个boolean值,在onPause()的时候,设置为false,表明当前Fragment对用户不可见,不就可以解决这个问题了吗。这时候就需要了解下ViewPager+Fragment的工作机制。
2.3 ViewPager+Fragment
ViewPager+Fragment的“预加载”问题,ViewPager会预先加载当前显示的Fragment的左右两个Fragment,即A,B,C,D 4个Fragment,ViewPager当前显示的是Fragment C的话,它也会预先加载B和D,这样是为了ViewPager在滑动的时候更加的流畅,预先加载B和D的时候并不会回调onHiddenChanged()方法。
Activity界面如图:
Activity代码如下:
public class Main2Activity extends AppCompatActivity { ViewPager viewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); viewPager = (ViewPager) findViewById(R.id.viewPager); List<Fragment> list = new ArrayList<>(); list.add(new FragmentA()); list.add(new FragmentB()); list.add(new FragmentC()); list.add(new FragmentD()); viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(), list)); } public static class MyPagerAdapter extends FragmentPagerAdapter { private List<Fragment> fragmentList; public MyPagerAdapter(FragmentManager fm, List<Fragment> fragmentList) { super(fm); this.fragmentList = fragmentList; } @Override public Fragment getItem(int position) { return fragmentList.get(position); } @Override public int getCount() { return fragmentList.size(); } } }
启动APP,观察Log:
12-26 10:08:17.055 32679-32679/cn.manchester.fragmentlazyload E/FragmentA: setUserVisibleHint: false 12-26 10:08:17.055 32679-32679/cn.manchester.fragmentlazyload E/FragmentA: getUserVisibleHint: false 12-26 10:08:17.055 32679-32679/cn.manchester.fragmentlazyload E/FragmentB: setUserVisibleHint: false 12-26 10:08:17.055 32679-32679/cn.manchester.fragmentlazyload E/FragmentB: getUserVisibleHint: false 12-26 10:08:17.055 32679-32679/cn.manchester.fragmentlazyload E/FragmentA: setUserVisibleHint: true 12-26 10:08:17.055 32679-32679/cn.manchester.fragmentlazyload E/FragmentA: getUserVisibleHint: true 12-26 10:08:17.055 32679-32679/cn.manchester.fragmentlazyload E/FragmentA: onAttach: 12-26 10:08:17.056 32679-32679/cn.manchester.fragmentlazyload E/FragmentA: onCreate: 12-26 10:08:17.056 32679-32679/cn.manchester.fragmentlazyload E/FragmentA: onCreateView: 12-26 10:08:17.057 32679-32679/cn.manchester.fragmentlazyload E/FragmentA: onStart: 12-26 10:08:17.057 32679-32679/cn.manchester.fragmentlazyload E/FragmentB: onAttach: 12-26 10:08:17.057 32679-32679/cn.manchester.fragmentlazyload E/FragmentB: onCreate: 12-26 10:08:17.057 32679-32679/cn.manchester.fragmentlazyload E/FragmentB: onCreateView: 12-26 10:08:17.069 32679-32679/cn.manchester.fragmentlazyload E/FragmentA: onResume: 12-26 10:08:17.069 32679-32679/cn.manchester.fragmentlazyload E/FragmentB: onStart: 12-26 10:08:17.069 32679-32679/cn.manchester.fragmentlazyload E/FragmentB: onResume:
可以看到,预先加载了Fragment B,没有回调onHiddenChanged()方法,而是调用了setUserVisibleHint()方法,该方法的参数是一个boolean值,这个值表明了,当前fragment是否对用户可见,继续滑动到下一个页面的话,又会预先加载Fragment C。
综上所述,若Fragment处于onPause生命周期,此Fragment不可与用户交互,即没有处在foreground。若Fragment处于onResume生命周期,此Fragment也不一定能与用户进行交互,需要结合onResume(),onHiddenChanged(),setUserVisibleHint()方法来确定Fragment实际所处位置。即如下“懒加载”Fragment:
public class LazyLoadFragment extends Fragment { // 第一次加载 private boolean isFirstLoad = true; private boolean isVisibleToUser; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } /** * Activity+Fragment,isVisibleToUser总是为true * @param isVisibleToUser */ @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); this.isVisibleToUser = isVisibleToUser && !isHidden(); } /** * * ViewPager+Fragment,hidden总是为false * @param hidden */ @Override public void onHiddenChanged(boolean hidden) { super.onHiddenChanged(hidden); isVisibleToUser = !hidden && getUserVisibleHint( b864 ); } @Override public void onResume() { super.onResume(); if (isVisibleToUser) { if (isFirstLoad) { lazyLoad(); isFirstLoad = false; } onShow(); } } @Override public void onPause() { super.onPause(); isVisibleToUser = false; } protected void onShow() { } protected void lazyLoad() { } }
上面的代码还不能适应所有情况,比如当activity重建之后,所有添加到activity的中fragment也随之销毁,重建,此时就会导致回调所有fragment的onShow()方法,也可以通过调用fragment的setRetainInstance(true)方法解决这个问题,在activity重建的时候保存fragment实例。这样也算是目前的一种解决方案吧,这些目前还不最完美的解决方案,待日后了解更加深入之后,再仔细探讨这个问题。
–
谢谢各位的耐心阅读以及提出的宝贵意见与建议。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件