Android使用Fragment切换tab
目前很多应用都是采用的底部导航+Tab的方式.目前项目中采用的是RadioButton + FrameLayout + Fragment
的实现方式,所以总结一下遇到的几个问题.
Activity重建导致Fragment重叠.
- 复现方式: 系统销毁Activity,如改变字体大小.
- 问题原因: Activity销毁时,系统会保存该Activity状态,而
onCreate(Bundle state)
会导致Activity的 - 解决方法:
- 在Activity销毁时不保存状态.如重写
onSaveInstanceState (Bundle outState)
并且不调用super方法,即在Activity销毁时不保存状态.但是该方式会导致Application创建两次. - 在Activity创建时,调用
super.onCreate(null)
,即不根据其保存的状态来恢复Activity状态,而是直接重新创建.这算是一种偷懒的办法吧. - 在利用
FragmentManager
addFragment 时,将每个 Fragment 设置不同的tag. 然后在 Activity 的onCreate (Bundle savedInstanceState)
方法中, 利用FragmentManager
根据不同 tag 来获取已经创建并添加了的 Fragemnt, 而不用重复创建.
- 在Activity销毁时不保存状态.如重写
FragmentTransaction 在 commit 时, 如果 Activity 正在销毁, 会导致崩溃.
异常为:
java.lang.IllegalStateException: Can not perform this action after onSaveInstance!
意思就是不能在 onSaveInstance() 后调用 commit 方法, 而 onSaveInstance() 是做状态保存工作, 调用时间由系统决定的, 它只能保证在 onStop() 前调用.
- 最好在
FragmentActivity#onResumeFragments()
或Activity#onPostResume()
中调用 commit . 这两个方法能确保当前 Activity 的状态已经恢复. - 如果确保这个 commit 丢失也没关系, 可以使用
FragmentTransaction#commitAllowingStateLoss();
- 最好在
Fragment中调用
getContext()
或getActivity()
为null导致崩溃.复现方式: detach一个Fragment,然后异步函数调用
getContext()
.问题原因:
onDetach()
调用后,getContext()
会置为null.解决方法:
在BaseFragment中维护一个mContext,然后重写
onAttach(Context context)
方法:@Override public void onAttach(Activity activity) { super.onAttach(activity); this.mContext = activity; } @Override public Context getContext() { if (super.getContext() != null) { return super.getContext(); } return mContext; }
然后尽量调用getContext()方法.
因为
getActivity()
方法不能重写,所以只好加上判断:if(isAdded()) { //XXXX }
如果使用 ViewPager + Fragment, ViewPager 在初始化时会缓存多个 Fragment, 这里可以通过
setUserVisibleHint
来判断 Fragment 是否可见.一个简单封装的 Fragment 如下:public abstract class BaseFragment extends Fragment { //必须有空的构造函数 public BaseFragment() { } //避免ViewPager在一开始创建 private boolean hasLazyLoad = false; public void setHasLazyLoad(boolean hasLazyLoad) { this.hasLazyLoad = hasLazyLoad; } /** * 懒加载,防止ViewPager重复创建 */ protected void onLazyLoad() { } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (getUserVisibleHint() && !hasLazyLoad) { onLazyLoad(); hasLazyLoad = true; } } @Override public void onDestroyView() { super.onDestroyView(); hasLazyLoad = false; } }
注意需要在
onDestroyView()
中复位标志位,因为 ViewPager 的切换会导致 Fragment 的销毁.在 Fragment 中嵌套 Fragment 时, 最好使用
getChildFragmentManager()
来获取 FragmentManager 示例,同时在 Fragment 销毁时, 利用反射将其置为空. 否则会导致错误java.lang.IllegalStateException: Activity has been destroyed
:@Override public void onDestroyView() { super.onDestroyView(); //解决嵌套 Fragment 的bug try { Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager"); childFragmentManager.setAccessible(true); childFragmentManager.set(this, null); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }