Fragment
Fragment 议为碎片,是为了解决屏幕适配问题以及UI界面的灵活控制而设计的。Fragment 不能独立存在,必须依赖一个 Activity。Fragment 比 Activity更节省内存,拥有自己的生命周期。
生命周期
- 由于 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() |
- 切换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才能生效 |
- 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 页面可见时,才从网络加载数据并显示。
常见问题
- 页面重叠
当使用 add 添加 fragment并使用 hide,show切换时,如果发生页面重启,会导致 fragment 重叠。那什么时候发生页面重启那?
当 activity 异常退出时,如:按home键,系统会调用 onSaveInstanceState ,保存 activity和fragment 的状态。在重启应用时,FragmentManager会从栈底向栈顶的顺序一次性恢复 Fragment,但没有保存 mIsHidden 属性,使所有的 Fragment 都以 show 的形式恢复,导致页面发生重叠。解决方法是什么那?
我们应该在创建activity时,且saveInstanceState不为空,通过 findFragmentByTag找到对应的 fragment,并hide所有的fragment,show需要显示的fragment。
- Can not perform this action after onSaveInstanceState异常
该异常出现在 commit 提交一个事务。原因是: commit 必须在fragment绑定的activity恢复状态后调用。为什么这样设计那?
状态保存机制就是为了处理activity因异常退出,而导致状态丢失的问题。如果,fragment在activity没有回复状态之前就调用 commit,就会造成状态丢失。该异常出现在我们在异步回调中调用 transaction.commit 提交一个事务。如何解决那?
普遍的解决方式,是调用 commitAllowingStateLoss,忽略状态丢失带来的影响。
- 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 方法,不保存状态数据。