您的位置:首页 > 移动开发 > Android开发

对于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实例。这样也算是目前的一种解决方案吧,这些目前还不最完美的解决方案,待日后了解更加深入之后,再仔细探讨这个问题。



谢谢各位的耐心阅读以及提出的宝贵意见与建议。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android Fragment 懒加载