目录
getActivity()
空指针;
Fragment
重叠异常—正确使用Hide()/show()
的姿势;
Pre
Android App 有一种特殊情况,就是在 App 运行在后台的时候,系统资源紧张的时候导致把 App 的资源全部回收(杀死 App 进程),这个时候把 App 再从后台返回前台时,app 会重启。这种情况简称“内存重启”。
getActivity()空指针 多数原因:你在调用了getActivity()
时,当前的 Fragment 已经onDetach()
了宿主 Activity.比如:你在 pop 了 Fragment 之后,该 Fragment 的异步任务仍然在执行,并且在执行完成后调用了getActivity()
方法,这样就会报 NULLPOINTEXCEPTION;解决方法: (对于 Fragment 已经onDetach()
这种情况,我们应该避免在这之后再去调用宿主 Activity 对象,比如取消这些异步任务,当我们的团队可能会有粗心大意的时候,所以下面给出的方法会保证安全)
在 Fragment 基类里设置一个 Activity mActivity 的全局变量,在onAttach(Activity act)
里赋值,使用 mActivity 代替getActivity()
,保证 Fragment 即使在 onDetach 后,仍持有 Activity 的引用(有 MermoryLeak 风险。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 protected Activity mActivity;@Override public void onAttach (Activity activity) { super .onAttach(activity) this .mActivity = activity; } @Override public void onAttach (Context context) { super .onAttach(context); this .mActivity = (Activity)context; }
Fragment 重叠异常—–正确的才使用hide()/show()
姿势 如果你add()
了几个 Fragment,使用show()/hide()
等方法控制显示,比如微信、QQ 的底部 tab 等情景,如果你什么都不做的话,在“内存重启”后回到前台,app 的这几个 Fragment 界面会重叠。
原因 FragmentManager 帮我们管理 Fragment,当发生“内存重启”,他会从栈底向栈顶的顺序一次性恢复 Fragment;但是因为没有保存 Fragment 的 mHidden 属性,默认为 false,即 show 状态,所以所有的 Fragment 都是以 show 的形式恢复,即发生了界面重叠现象。(如果是replace()
,恢复形式和 Activity 一致,只有当你的 pop 之后上一个 Fragment 才开始重新恢复,所以使用replace()
不会造成重叠现象。)
解决方案 1
findFragmentByTag:即在add()
或者replace()
时绑定一个 tag,一般我们是用 fargment 的类名作为 tag,然后在发生“内存重启”时,通过findFragmentByTag
找到对应的 Fragment,并hide()
需要隐藏的 Fragment。 下面是标准的恢复写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Override protected void onCreate (Bundle SavedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity); TargetFragment targetFragment; HideFragment hideFragment; if (savedInstanceState!=null ){ targetFragment = getSupportFragmentManager().findFragmentByTag(TargetFragment.class.getName()); hideFragment = getSupportFragmentManager().findFragmentByTag(hideFragment.CLASS.GETnAME()); getFragmentManager().beginTransaction() .show(targetFragment) .hide(hideFragment) .commit(); }else { targetFragment = TargentFragment.newInstance(); hideFragment = HideFragment.newInstance(); getFragmentManager().beginTransaction() .add(R.id.container,targetFragment,targetFragment.getClass().getName()) .add(R.id.container,hideFragment,hideFragment.getClass().getName()) .hide(hideFragment) .commit(); } }
如果你想要恢复到用户离开时那个 Fragment 的界面,你还需要在onSaveInstanceState(Bundle outState)
里保存离开时的按个可见的 tag 或者下标,在onCreate()
“内存重启”代码块中,去除 tag/下标,进行恢复。
解决方法 2:
使用getSupportFragment().getFragments()
恢复:使用getFragment()
可以获取到当前FragmentManager
管理的栈内所有 Fragment。 写法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity); TargetFragment targetFRAGMENT; HideFragment hideFragment; if (savedInstanceState !=null ){ List<Fragment> fragmentList = getSupportFragmentManager().getFragments(); for (Fragment fragment : fragmentList){ if (fragment instanceof TargetFragment){ targetFragment = (TargetFragment)fragment; }else if (fragment instancof HideFragment){ hideFragment = (HideFragment)fragment; } getFragmentManager().beginTransaction() .show(targetFragment) .hide(hideFragment) .commit(); }else { targetFragment = TargetFragment.newInstance(); hideFragment = HideFragment.newInstance(); getFragmentManager().beginTransaction() .add(R.id.container) .add(R.id.container,hideFragment) .hide(hideFragment) .commit(); } }
在下一篇中介绍在不同场景下如果选择,何时用 findFragmentByTag(),何时用 getFragments()恢复。
解决方法 3: 发生 Fragment 重叠的根本原因在于FragmentState
没有保存 Fragment 的显示状态,即mHidden
,导致页面重启后,该值为默认的 false,即 show 状态,所以导致了 Fragment 的重叠。因此自己去维护一个mSupportHidden
值即可解决问题: Fragment 基类代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class BaseFragment extends Fragment { private static final String STATE_SAVE_IS_HIDDEN = “STATE_SAVE_IS_HIDDEN”; @Override public void onCreate (@Nullable Bundle savedInstanceState) { boolean isSUpportHidden = savedInstanceState.getBoolean(STATE_SAVE_IS_HIDDEN); FragmentTransaction ft = getFragmentManager().beginTransaction(); if (isSupportHidden){ ft.hide(this ); }else { ft.show{this }; } ft.commit(); } @Override public void onSavedInstanceState (Bundle outState) { ... outState.putBoolean(STATE_SAVE_IS_HIDDEN,isHidden()); } }
解决思路:由 Activity/父 Fragment 来管理子 Fragment 的 Hidden 状态转变为 由 Fragment 自己来管理自己的 Hidden 状态!
补充: 加载根 Fragment 时需要判断saveInstanceState
是否为null
,避免重复加载相同的 Fragment:
1 2 3 4 5 6 7 8 9 10 11 public class MainActivity ... { @Override protected void onCreate (@Nullable Bundle savedInstanceState) { ... if (saveInstanceState == null ){ } } }