Fragment 学习

Fragment

Fragment 议为碎片,是为了解决屏幕适配问题以及UI界面的灵活控制而设计的。Fragment 不能独立存在,必须依赖一个 Activity。Fragment 比 Activity更节省内存,拥有自己的生命周期。

生命周期

  1. 由于 Fragment 与 activity 强关联,所以它的生命周期与 activity 在一起讨论
State Activity Fragment
Created onCreate() onAttach()->onCreate()->onCreateView()->onActivityCreated()
Stared onStart() onStart()
Resumed onResume() onResume()
Paused onPause() onPause()
Stopped onStop() onStop()
Destroyed onDestroy() onDestroyView()->onDestroy()->onDetach()
  1. 切换Fragment对生命周期的影响
  • 通过 add 添加 fragmnet,再通过 hide, show 决定显示哪一个
    该方式是将 fragment 隐藏,而不是销毁重建。所以,在切换时调用 onHideChanged() 方法。由于 fragment 只是被隐藏了,所以 fragment 的修改还在。
    注意:若 fragment 已 add ,则不能再调用 add 添加该 fragment,否则会抛出fragment already added
  • 通过 replace 切换 Fragment
    该方式重在替换 fragment,每次都会销毁重建,所以生命周期会重新执行。由于之前的 fragment 会被销毁,所以在fragment的操作不会被恢复。
    每次都销毁重建 fragment,会不会太消耗内存了,有没有不销毁实例的办法?可以使用回退栈,既主动调用 addToBackStack,将 fragment 实例压入栈进行保存,当下次切换时,就不会销毁重建了。但是,它的视图层次的修改还是没有保存。
  • 通过 ViewPager+FragmentPagerAdapter方式切换
    切换时不执行生命周期,切换时调用 setUserVisibaleHint()

fragment 的切换是通过 FragmentTransaction 提供的API实现的:

方法 解释
add 向 activity 中添加一个 fragment
remove 从 activity 中移除指定的 fragment,包含它的视图
hide 隐藏当前的 fragment,仅仅设为不可见,并不销毁
show 显示之前隐藏的 fragment
replace 使用另一个fragment替换当前的,相当于 remove和add的合体
commit 以上方法执行后,只有再调用commit才能生效
  1. TableLayout+ViewPager+PagerAdapter 实现Fragment切换

在与 ViewPager结合实现切换Fragment时,我们需要指定的适配器FragmentPagerAdapter或FragmentStatePagerAdapter。两种适配器用法类似,但也有区别:

Adapter FragmentStatePagerAdapter FragmentPagerAdapter
切换方式 使用 add 添加 fragment,对于不需要的fragment,使用remove销毁fragment实例,销毁时可在 onSaveInstanceState 中保存状态信息 使用 add 添加 fragment,对于不需要的fragment,使用 detah 销毁 fragment的视图,实例仍然保存
适用场景 更节省内存,当页面比较多时适合使用 界面只是少量固定的页面,更新比较少

懒加载

ViewPager为了用户体验,在用户滑动时不卡顿,使用懒加载机制,事先加载并缓存 fragment。例如:当我们使用 viewPager.setOffscreenPageLimit(3) 设置缓存的fragment数量后,当我们要显示某一个fragment时,会将离他最近的3个fragment也创建好,即都执行onAttach-onResume生命周期。

为什么需要懒加载那?

Android的view绘制流程是非常消耗CPU的,尤其是ViewPager+Fragment的时候,既要绘制view又要对多个fragment的数据进行加载,所以,需要使用懒加载。那么什么是懒加载那?

既被动加载,当 fragment 页面可见时,才从网络加载数据并显示。

常见问题

  1. 页面重叠

当使用 add 添加 fragment并使用 hide,show切换时,如果发生页面重启,会导致 fragment 重叠。那什么时候发生页面重启那?

当 activity 异常退出时,如:按home键,系统会调用 onSaveInstanceState ,保存 activity和fragment 的状态。在重启应用时,FragmentManager会从栈底向栈顶的顺序一次性恢复 Fragment,但没有保存 mIsHidden 属性,使所有的 Fragment 都以 show 的形式恢复,导致页面发生重叠。解决方法是什么那?

我们应该在创建activity时,且saveInstanceState不为空,通过 findFragmentByTag找到对应的 fragment,并hide所有的fragment,show需要显示的fragment。

  1. Can not perform this action after onSaveInstanceState异常

该异常出现在 commit 提交一个事务。原因是: commit 必须在fragment绑定的activity恢复状态后调用。为什么这样设计那?
状态保存机制就是为了处理activity因异常退出,而导致状态丢失的问题。如果,fragment在activity没有回复状态之前就调用 commit,就会造成状态丢失。该异常出现在我们在异步回调中调用 transaction.commit 提交一个事务。如何解决那?

普遍的解决方式,是调用 commitAllowingStateLoss,忽略状态丢失带来的影响。

  1. android.os.TransactionTooLargeException: data parcel size 531500 bytes

在使用 FragmentStatePagerAdapter 时,如果用户持续滑动展示很多的 fragment,就会出现该异常。该异常是由于在使用Binder传递数据时超出了 Binder的传输范围(1M。那么,使用 FragmentStatePagerAdapter 为什么会出现该异常那?

通过 FSPA 的源码,我们知道:在销毁一个fragment时,我们会保存该fragment的状态信息。

public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        // 保存fragment的状态数据。注意:这个保存的是所有的历史数据啊!!!
        mSavedState.set(position, fragment.isAdded()
                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
        // 移除 fragment 实例
        mCurTransaction.remove(fragment);
        if (fragment.equals(mCurrentPrimaryItem)) {
            mCurrentPrimaryItem = null;
        }
    }

    public Parcelable saveState() {
    Bundle state = null;
    if (mSavedState.size() > 0) {
        state = new Bundle();
        Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
        mSavedState.toArray(fss);
        // 使用Bundle传递数据
        state.putParcelableArray("states", fss);
    }
    for (int i=0; i<mFragments.size(); i++) {
        Fragment f = mFragments.get(i);
        if (f != null && f.isAdded()) {
            if (state == null) {
                state = new Bundle();
            }
            String key = "f" + i;
            mFragmentManager.putFragment(state, key, f);
        }
    }
    return state;
}

解决方案:重写 savestate 方法,不保存状态数据。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容