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

谢谢各位的耐心阅读以及提出的宝贵意见与建议。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值