对于Fragment“懒加载”问题的一点点见解

本文探讨了在Android开发中Fragment懒加载的问题,详细分析了Fragment的生命周期,特别是onResume()、onHiddenChanged()和setUserVisibleHint()方法在不同场景下的行为。通过示例代码展示了XML嵌入Fragment、Activity+Fragment以及ViewPager+Fragment的情况,指出在ViewPager中预加载机制导致的特殊情况,并提出了应对activity重建的解决方案。
摘要由CSDN通过智能技术生成
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界面如图:

CSDN图片服务器异常,请稍后

代码如下:


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();
    }

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

谢谢各位的耐心阅读以及提出的宝贵意见与建议。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中ViewPager+Fragment取消(禁止)预加载延迟加载(懒加载)问题解决方案 方案是为解决特定问题或达成特定目标而制定的一系列计划或步骤。它的作用是提供一种系统性的方法,以有效地应对挑战、优化流程或实现目标。以下是方案的主要作用: 问题解决: 方案的核心目标是解决问题。通过系统性的规划和执行,方案能够分析问题的根本原因,提供可行的解决方案,并引导实施过程,确保问题得到合理解决。 目标达成: 方案通常与明确的目标相关联,它提供了一种达成这些目标的计划。无论是企业战略、项目管理还是个人发展,方案的制定都有助于明确目标并提供达成目标的路径。 资源优化: 方案在设计时考虑了可用资源,以最大化其效用。通过明智的资源分配,方案可以在有限的资源条件下实现最大的效益,提高效率并减少浪费。 风险管理: 方案通常会对潜在的风险进行评估,并制定相应的风险管理策略。这有助于减轻潜在问题的影响,提高方案的可行性和可持续性。 决策支持: 方案提供了决策者所需的信息和数据,以便做出明智的决策。这种数据驱动的方法有助于减少不确定性,提高决策的准确性。 团队协作: 复杂的问题通常需要多个人的协同努力。方案提供了一个共同的框架,帮助团队成员理解各自的职责和任务,促进协作并确保整个团队朝着共同的目标努力。 监控与评估: 方案通常包括监控和评估的机制,以确保实施的有效性。通过定期的评估,可以及时调整方案,以适应变化的环境或新的挑战。 总体而言,方案的作用在于提供一种有序、有计划的方法,以解决问题、实现目标,并在实施过程中最大化资源利用和风险管理。 方案是为解决特定问题或达成特定目标而制定的一系列计划或步骤。它的作用是提供一种系统性的方法,以有效地应对挑战、优化流程或实现目标。以下是方案的主要作用: 问题解决: 方案的核心目标是解决问题。通过系统性的规划和执行,方案能够分析问题的根本原因,提供可行的解决方案,并引导实施过程,确保问题得到合理解决。 目标达成: 方案通常与明确的目标相关联,它提供了一种达成这些目标的计划。无论是企业战略、项目管理还是个人发展,方案的制定都有助于明确目标并提供达成目标的路径。 资源优化: 方案在设计时考虑了可用资源,以最大化其效用。通过明智的资源分配,方案可以在有限的资源条件下实现最大的效益,提高效率并减少浪费。 风险管理: 方案通常会对潜在的风险进行评估,并制定相应的风险管理策略。这有助于减轻潜在问题的影响,提高方案的可行性和可持续性。 决策支持: 方案提供了决策者所需的信息和数据,以便做出明智的决策。这种数据驱动的方法有助于减少不确定性,提高决策的准确性。 团队协作: 复杂的问题通常需要多个人的协同努力。方案提供了一个共同的框架,帮助团队成员理解各自的职责和任务,促进协作并确保整个团队朝着共同的目标努力。 监控与评估: 方案通常包括监控和评估的机制,以确保实施的有效性。通过定期的评估,可以及时调整方案,以适应变化的环境或新的挑战。 总体而言,方案的作用在于提供一种有序、有计划的方法,以解决问题、实现目标,并在实施过程中最大化资源利用和风险管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值