android

Activity生命周期异常分析

 

我们知道正常情况下Activity创建的时候会执行 onCreate onStart onResume 方法;当锁屏情况下Activity会执行 onPause onStop方法;当屏幕再次显示的时候会执行onReStart onStart onResume方法。但是在一些特殊情况下,如语言切换,横竖屏切换等配置改变以及内存吃紧的情况下,activity就有可能被杀死,从而导致生命周期异常。这对于开发人员来说,了解这些是很与必要的。下面我们来具体分析这俩种资源相关的配置信息发生改变导致Activity生命周期异常

 

理解这种情况,首先我们要对系统的资源加载机制有一定了解,这里简单说明一下,就拿string资源文件来说,当我们的项目要做国际化的时候,系统会根据当前的local来加载不同的string资源文件(设定了不同国家的string文件),还有当横屏手机和竖屏手机会拿到不同的图片(设定了landscape或者portrait状态下的图片),上面这两种配置发生改变的情况下,Activity默认会被销毁并创建。

 

 

 异常情况下Activity生命周期异常分析

当系统配置发生改变后,Activity会被销毁,其onPause,onStop,onDestroy方法均会被调用,同时由于Activity是在异常情况下终止运行的,此时系统会调用onSaveInstanceState来保存当前Activity的状态。同时在恢复时Activity运行时,又会调用onRestoreInstanceState方法来保存Activity的状态。

   同时我们要知道,当Activity在异常终止然后又重新启动创建时,系统默认会为我们在onSaveInstanceState方法中用Bundle保存当前Activity一些状态,如文本框输入的数据,ListView滚动的位置,然后又会在onCreate方法之前调用onRestoreInstanceState恢复这些状态。

 

1.1 Activity状态恢复机制

至于这些View的状态是怎么保存的,你在看Fragment和View源码的时候,你会发现在View的源码中onSaveInstance和onRestoreInstanceState方法,Activity中也有这2个方法,难道Activity通过调用View的这2个方法来恢复状态的?

   答案是肯定的,关于保存和恢复View的层级结构,系统的工作流程是这样的:

首先Activity被意外终止时,Activity会调用onSaveInstance方法去保存数据,然后Activity会委托Window去保存数据,接着Window再委托它上面的顶层容器去保存数据,顶层容器(DecorView)是一个ViewGroup.最后顶层容器再去一一通知它的子元素来保存数据,这样整个保存过程就这样完成了。可以发现,这是一种典型的委托思想,上层委托下层,容器委托子元素去处理一件事情,这种思想在Android中有很多应用,比如View的绘制过程,事件分发过程都是采用类似的思想。

 1.2 Activity中View状态恢复机制,源码分析

 也就是说,Activity调用onSaveInstanceState方法恢复数据的同时,View也会执行onSaveInstance方法保存数据,同时调用onRestoreInstanceState时View也会调用onRestoreInstanceState恢复之前的状态。我们来看看源码来分析,分析吧

(1) Activity的onSaveInstanceState方法源码

  protected void onSaveInstanceState(Bundle outState) {

        outState.putBundle("android:viewHierarchyState", this.mWindow.saveHierarchyState());

        Parcelable p = this.mFragments.saveAllState();

        if(p != null) {

            outState.putParcelable("android:fragments", p);

        }

 

        this.getApplication().dispatchActivitySaveInstanceState(this, outState);

}           

 

可以看出onSaveInstanceInstanceState中,window会调用saveHierarchyState,

同时会通过Buddle把所有Fragment的数据放在key值为” android:fragments”的对象中,如果是FragmentActivity则key为“android:support:fragments”

 

 (2)下面看看Window的saveHierarchyState方法源码

我们都知道Activity是挂在一个Window下面的,用AS发现Window的saveHierarchyState方法是一个抽象方法,但是又无法查看到其子类PhoneWindow的源码。最后通过用everyThing在Android源码中,找到了该方法的源码。

 

 

/** {@inheritDoc} */

    @Override

    public void restoreHierarchyState(Bundle savedInstanceState) {

        if (mContentParent == null) {

            return;

        }

 

        SparseArray<Parcelable> savedStates

                = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);

        if (savedStates != null) {

            mContentParent.restoreHierarchyState(savedStates);

        }

 

        // restore the focused view

        int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);

        if (focusedViewId != View.NO_ID) {

            View needsFocus = mContentParent.findViewById(focusedViewId);

            if (needsFocus != null) {

                needsFocus.requestFocus();

            } else {

                Log.w(TAG,

                        "Previously focused view reported id " + focusedViewId

                                + " during save, but can't be found during restore.");

            }

        }

     

    }

 

 

接着你会发现会执行mContentParent.restoreHierarchyState(savedStates);方法,mContentParent其实是一个ViewGroup,restoreHierarchyState到底干了写什么呢?在View找到了该方法,其实调用了dispatchRestoreInstanceState,最终调用了View的  onRestoreInstanceState方法。

 

protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {

        if(this.mID != -1) {

            Parcelable state = (Parcelable)container.get(this.mID);

            if(state != null) {

                this.mPrivateFlags &= -131073;

                this.onRestoreInstanceState(state);

                if((this.mPrivateFlags & 131072) == 0) {

                    throw new IllegalStateException("Derived class did not call super.onRestoreInstanceState()");

                }

            }

        }

 

    }

 

在ViewGroup对父类View的dispatchRestoreInstanceState方法进行了重载

 

   protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {

        super.dispatchRestoreInstanceState(container);

        int count = this.mChildrenCount;

        View[] children = this.mChildren;

 

        for(int i = 0; i < count; ++i) {

            View c = children[i];

            if((c.mViewFlags & 536870912) != 536870912) {

                c.dispatchRestoreInstanceState(container);

            }

        }

 

    }

明显,ViewGroup会一一告诉每个子View让其去执行dispatchRestoreInstanceState保存各自的状态。

 

总结:ActivityonRestoreInstanceState方法也一样的。通过上面的源码分析可以得出结论,Activity通过异常终止时,会保存Fragment的状态,同时也会委托Window,让DecorView去保存Activity中所有View的状态。

 

1.3 Fragment状态如何保存自己状态的

 

 在做TV项目的时候,当项目启动了一个由多个Fragment组成的Activity时,语言从简体中文切换成英文后,此时再次启动APP,结果发现Fragment中莫名报了View为空的一个空指针的异常。经过长时间的爬坑,通过google搜索也没有很好的解决方法,最后通过查看源码终于解决了该问题,现在就来分析分析该问题是如何解决的。

   很明显该问题是由于配置的改变(多语言切换),导致Activity异常终止又重新创建。此时:

Activity的生命周期是:onResume onStop onDestroy onSaveInstanceState onCreate onStart onResume

Fragment的生命周期 :onResume onStop onDestroyView onCreateView

此时我觉得肯定是由于Fragment部分数据保存造成的。所以我把Activity的onSaveInstanceState方法屏蔽了,结果就异常就解决了,当时很开心,问题解决了,但是这样做不好啊,Activity里的数据就都不能保存了。能不能只屏蔽Fragment中的数据呢?答案是可以的。

继续看Activity 的onSaveInstanceState方法源码,里面有一段这样的代码

Parcelable p = this.mFragments.saveAllState();

        if(p != null) {

            outState.putParcelable("android:fragments", p);

        }

意思是得到mFragment保存的数据,然后Activity把放在Buddle中,其中Key为"android:fragments",这就好办了,只要在Activity方法中,onSaveInstanceState方法中通过Buddle remove调该key值就不让Fragment保留之前的状态吗?通过实践,这种方法确实可行,但是问题又来了,对于Activity来说该key值为"android:fragments",但是FragmentActivity又变成了“android:support:fragments”了。

 

最后我贴出关键代码:

<!--[if !supportLists]-->(1)<!--[endif]-->Activity中onSaveInstanceState方法,其中pageIndex为Activity选中的fragment对应的index值。

   @Override

    protected void onSaveInstanceState(Bundle outState) {

        super.onSaveInstanceState(outState);

        if(outState != null){

            outState.putInt("pageIndex",mJumpType);

            outState.remove(this.getFragmentTagForSaveInstance());

        }

    }

<!--[if !supportLists]-->(2)<!--[endif]-->getFragmentTagForSaveInstance()方法是拿到保存fragment状态时对应的key值。

  protected String getFragmentTagForSaveInstance() {

        try {

            Field f = Activity.class.getDeclaredField("FRAGMENTS_TAG");

            f.setAccessible(true);

            Object fragmentTagObj = f.get(null);

            if (fragmentTagObj != null) {

                return String.valueOf(fragmentTagObj);

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

        return "android:fragments";

    }

 

<!--[if !supportLists]-->(3)<!--[endif]-->Activty onCreate方法拿到key值为pageIndex的值

 

 

 

 

  @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        if(savedInstanceState != null){

            mJumpType = savedInstanceState.getInt("pageIndex",0);

        }

}  

说明:由于Activity周期异常导致,Fragment空指针的异常是google系统级的bug,该问题在stackFlow上也没有很好的解决办法,希望这篇文章对大家有

帮助。

 

(二)情况2:资源内存不足导致低优先级的Activity被杀死

   这种情况不好模拟,但是其数据存储和恢复过程情况和情况一完全一致。这里描述一下Activity的优先级情况。Activity按照优先级从高到低,可以分为如下三种:

<!--[if !supportLists]-->(1)<!--[endif]-->前台Activity       - 正在和用户交互的Activity,优先级级最高

 

<!--[if !supportLists]-->(2)<!--[endif]-->可见单非前台Activity - 比如Activity弹出了一个对话框,导致Activit 可见,但是位于后台无法和用户进行交互

 

<!--[if !supportLists]-->(3)<!--[endif]-->后台Activity  -  已经被暂停的Activity,比如执行了onStop方法,优先级最低。

 

当系统内存不足时,系统就会按照上述优先级去杀死目前Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。如果一个进程中没有四大组件在执行,那么这个进程很容易就被杀死。比较好的方法是后台工作放在Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死。

 

 

 

 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值