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

Posted 看书的小蜗牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android后台杀死系列之一:FragmentActivity及PhoneWindow后台杀死处理机制相关的知识,希望对你有一定的参考价值。

新Activity启动及旧Activity保存流程

App在后台久置后,再次从桌面或最近的任务列表唤醒时经常会发生崩溃,这往往是App在后台被系统杀死,再次恢复的时候遇到了问题,而在使用FragmentActivity+Fragment的时候会更加频繁。比如,如果Fragment没有提供默认构造方法,就会在重建的时候因为反射创建Fragment失败而崩溃,再比如,在onCreate里面new 一个FragmentDialog,并且show,被后台杀死后,再次唤醒的时候,就会show两个对话框,这是为什么?其实这就涉及了后台杀死及恢复的机制,其中涉及的知识点主要是FragmentActivity、ActivityManagerService、LowMemoryKiller机制、ActivityStack、Binder等一系列知识点。放在一篇文章里面可能会有些长,因此,android后台杀死系列写了三篇:

本篇是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,一个被系统恢复的,一个新建的。