Android后台杀死系列之一(FragmentActivity及PhoneWindow后台杀死处理机制)

沉舟侧畔千帆进,病树前头万木春。这篇文章主要讲述Android后台杀死系列之一:FragmentActivity及PhoneWindow后台杀死处理机制相关的知识,希望能为你提供帮助。

Android后台杀死系列之一(FragmentActivity及PhoneWindow后台杀死处理机制)

文章图片

App在后台久置后, 再次从桌面或最近的任务列表唤醒时经常会发生崩溃, 这往往是App在后台被系统杀死, 再次恢复的时候遇到了问题, 而在使用FragmentActivity+ Fragment的时候会更加频繁。比如, 如果Fragment没有提供默认构造方法, 就会在重建的时候因为反射创建Fragment失败而崩溃, 再比如, 在onCreate里面new 一个FragmentDialog, 并且show, 被后台杀死后, 再次唤醒的时候, 就会show两个对话框, 这是为什么? 其实这就涉及了后台杀死及恢复的机制, 其中涉及的知识点主要是FragmentActivity、ActivityManagerService、LowMemoryKiller机制、ActivityStack、Binder等一系列知识点。放在一篇文章里面可能会有些长, 因此, android后台杀死系列写了三篇:
  • 开篇: FragmentActivity及PhoneWindow后台杀死处理机制
  • 原理篇1: 后台杀死与App现场恢复(主要讲述AMS如何为App恢复现场的原理)
  • 原理篇2: 后台杀死与LowmemoryKiller(主要讲述App被后台杀死的原理)
本篇是Android后台杀死系列的第一篇, 主要讲解在开发过程中, 由于后台杀死涉及的一些崩溃, 以及如何避免这些崩溃, 还有就是简单的介绍一下onSaveInstanceState与onRestoreInstanceState执行时机与原理, 这两个函数也是Android面试时常问的两个点, 是比简单的启动模式Activity声明周期稍微更深入细致一些的地方, 也通过这个点引入后台杀死及恢复原理。
FragmentActivity被后台杀死后恢复逻辑 当App被后台异常杀死后, 再次点击icon, 或者从最近任务列表进入的时候, 系统会帮助恢复当时的场景, 重新创建Activity, 对于FragmentActivity, 由于其中有Framgent, 逻辑会相对再复杂一些, 系统会首先重建被销毁的Fragment。
举个栗子 我们创建一个Activity, 并且在onCreate函数中新建并show一个DialogFragment, 之后通过某种方式将APP异常杀死(RogueKiller模拟后台杀死工具), 再次从最近的任务唤起App的时候, 会发现显示了两个DialogFragment, 代码如下:
public class DialogFragmentActivity extends AppCompatActivity {@ Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); DialogFragment dialogFragment = new FragmentDlg(); dialogFragment.show(getSupportFragmentManager(), " " ); }

这不仅让我们奇怪, 为什么呢? 虽然被杀死了, 但是onCreate函数在执行的时候还是只执行了一次啊, 为什么会出现两个DialogFragment, 这里其实就有一个DialogFragment是通过Android自身的恢复重建机制重建出来, 在异常杀死的情况下onCreate(Bundle savedInstanceState)函数的savedInstanceState参数也不是null, 而是包含了被杀死时所保存的场景信息。再来看个崩溃的例子, 新建一个CrashFragment, 并且丢弃默认无参构造方法:
public class CrashFragment extends Fragment {public CrashFragment(String tag) { super(); } }

之后再Activity中Add或replace添加这个CrashFragment, 在CrashFragment显示后, 通过RogueKiller模拟后台杀死工具模拟后台杀死, 再次从最近任务列表里唤起App的时候, 就会遇到崩溃,
Caused by: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment xxx.CrashFragment: make sure class name exists, is public, and has an empty constructor that is public at android.support.v4.app.Fragment.instantiate(Fragment.java:431) at android.support.v4.app.FragmentState.instantiate(Fragment.java:102) at android.support.v4.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:1952) at android.support.v4.app.FragmentController.restoreAllState(FragmentController.java:144) at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:307) at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:81)

上面的这两个问题主要涉及后台杀死后FragmentActivity自身的恢复机制, 其实super.onCreate(savedInstanceState)在恢复时做了很多我们没有看到的事情, 先看一下崩溃:
为什么Fragment没有无参构造方法会引发崩溃 看一下support-V4中FragmentActivity中onCreate代码如下:
protected void onCreate(@ Nullable Bundle savedInstanceState) { mFragments.attachHost(null /*parent*/); super.onCreate(savedInstanceState); ... if (savedInstanceState != null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, nc != null ? nc.fragments : null); } mFragments.dispatchCreate(); }

可以看到如果savedInstanceState != null, 就会执行mFragments.restoreAllState逻辑, 其实这里就牵扯到恢复时重建逻辑, 再被后台异常杀死前, 或者说在Activity的onStop执行前, Activity的现场以及Fragment的现场都是已经被保存过的, 其实是被保存早ActivityManagerService中, 保存的格式FragmentState, 重建的时候, 会采用反射机制重新创Fragment
void restoreAllState(Parcelable state, List< Fragment> nonConfig) {... for (int i= 0; i< fms.mActive.length; i+ + ) { FragmentState fs = fms.mActive[i]; if (fs != null) { Fragment f = fs.instantiate(mHost, mParent); mActive.add(f); ...

其实就是调用FragmentState的instantiate, 进而调用Fragment的instantiate, 最后通过反射, 构建Fragment, 也就是, 被加到FragmentActivity的Fragment在恢复的时候, 会被自动创建, 并且采用Fragment的默认无参构造方法, 如果没哟这个方法, 就会抛出InstantiationException异常, 这也是为什么第二个例子中会出现崩溃的原因。
*/ public static Fragment instantiate(Context context, String fname, @ Nullable Bundle args) { try { Class< ?> clazz = sClassMap.get(fname); if (clazz = = null) { // Class not found in the cache, see if it' s real, and try to add it clazz = context.getClassLoader().loadClass(fname); sClassMap.put(fname, clazz); } Fragment f = (Fragment)clazz.newInstance(); if (args != null) { args.setClassLoader(f.getClass().getClassLoader()); f.mArguments = args; } return f; } catch (ClassNotFoundException e) { throw new InstantiationException(" Unable to instantiate fragment " + fname + " : make sure class name exists, is public, and has an" + " empty constructor that is public" , e); } catch (java.lang.InstantiationException e) { throw new InstantiationException(" Unable to instantiate fragment " + fname + " : make sure class name exists, is public, and has an" + " empty constructor that is public" , e); } catch (IllegalAccessException e) { throw new InstantiationException(" Unable to instantiate fragment " + fname + " : make sure class name exists, is public, and has an" + " empty constructor that is public" , e); } } q

可以看到场景二提示的errormsg跟抛出的异常是可以对应上的, 其实Fragment源码里面也说得很清楚:
/** * Default constructor.< strong> Every< /strong> fragment must have an * empty constructor, so it can be instantiated when restoring its * activity' s state.It is strongly recommended that subclasses do not * have other constructors with parameters, since these constructors * will not be called when the fragment is re-instantiated; instead, * arguments can be supplied by the caller with {@ link #setArguments} * and later retrieved by the Fragment with {@ link #getArguments}. * * < p> Applications should generally not implement a constructor.The * first place application code an run where the fragment is ready to * be used is in {@ link #onAttach(Activity)}, the point where the fragment * is actually associated with its activity.Some applications may also * want to implement {@ link #onInflate} to retrieve attributes from a * layout resource, though should take care here because this happens for * the fragment is attached to its activity. */public Fragment() { }

大意就是, Fragment必须有一个空构造方法, 这样才能保证重建流程, 并且, Fragment的子类也不推荐有带参数的构造方法, 最好采用setArguments来保存参数。下面再来看下为什么会出现两个DialogFragment。
为什么出现两个DialogFragment Fragment在被创建之后, 如果不通过add或者replace添加到Activity的布局中是不会显示的, 在保存现场的时候, 也是保存了add的这个状态的, 来看一下Fragment的add逻辑: 此时被后台杀死, 或旋转屏幕, 被恢复的DialogFragmentActivity时会出现两个FragmentDialog, 一个被系统恢复的, 一个新建的。
【Android后台杀死系列之一(FragmentActivity及PhoneWindow后台杀死处理机制)】

    推荐阅读