Activity life cycle exception analysis

Activity life cycle exception analysis 

We know that the onCreate onStart onResume method will be executed when the Activity is created under normal circumstances; the onPause onStop method will be executed when the Activity is locked when the screen is locked; the onReStart onStart onResume method will be executed when the screen is displayed again. However, in some special cases, such as language switching, horizontal and vertical screen switching, and other configuration changes, and when memory is tight, the activity may be killed, resulting in an abnormal life cycle. It is necessary for developers to understand these. Let us analyze these two situations in detail.

 

( 1) Case 1: The resource-related configuration information changes, resulting in an abnormal activity life cycle

 

To understand this situation, we must first have a certain understanding of the system's resource loading mechanism. Here is a brief description. Take the string resource file as an example. When our project is to be internationalized, the system will load it according to the current local. Different string resource files (set the string files of different countries), and when the horizontal screen mobile phone and the vertical screen mobile phone will get different pictures (set the picture in the landscape or portrait state), the above two configurations In the case of changes, the Activity will be destroyed and created by default.

 

 Activity life cycle exception analysis under abnormal conditions

When the system configuration is changed, the Activity will be destroyed, and its onPause, onStop, and onDestroy methods will be called. At the same time, since the Activity is terminated under abnormal conditions, the system will call onSaveInstanceState to save the current Activity state. At the same time, when the Activity is running during restoration, the onRestoreInstanceState method will be called to restore the state of the Activity.

    At the same time, we also need to know that when the Activity terminates abnormally and then restarts the creation, the system will save some states of the current Activity with a Bundle in the onSaveInstanceState method by default, such as the data entered in the text box, the scrolling position of the ListView, and then Call onRestoreInstanceState before the onCreate method to restore these states.

 

1.1 Activity state recovery mechanism

As for how the states of these Views are saved, when you look at the View source code, you will find the onSaveInstance and onRestoreInstanceState methods in the View source code. There are also these two methods in the Activity. Does the Activity call these two methods of the View to restored? (Note: Fragment actually has these two methods)

 

       The answer is yes. Regarding saving and restoring the hierarchical structure of View, the system's workflow is as follows: First, when the Activity is terminated unexpectedly, the Activity will call the onSaveInstance method to save the data, then the Activity will entrust the Window to save the data, and then the Window will Delegate the top-level container above it to save the data. The top-level container (DecorView) is a ViewGroup. Finally, the top-level container notifies its child elements one by one to save the data, so that the whole saving process is completed. It can be found that this is a typical delegation idea. The upper layer delegates to the lower layer, and the container delegates sub-elements to handle one thing. This idea has many applications in Android, such as the drawing process of View and the process of event distribution. Similar Thought. (Note: Fragment and View are also similar)

 

 1.2 View state recovery mechanism in Activity, source code analysis

 也就是说,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源码中,找到了PhoneWindow中该方法的源码。

 

/** {@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.");
            }
        }
     
    }

 

 

     你会发现saveHierarchyState其实质上执行了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);
        }
 }

 

 

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

 

最后我贴出关键代码:

(1)Activity中onSaveInstanceState方法,其中pageIndex为Activity选中的fragment对应的index值,并移除fragment保存的状态。

 

  @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if(outState != null){
            outState.putInt("pageIndex",mJumpType);
            outState.remove(this.getFragmentTagForSaveInstance());
        }
    }

 

(2) 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";
    }

 

 (3)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上也没有很好的解决办法,希望这篇文章对大家分析Activity和fragment生命周期异常的情况有所帮助。

 

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

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

(1) 前台Activity       - 正在和用户交互的Activity,优先级级最高

 

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

 

(3)后台Activity  -  已经被暂停的Activity,比如执行了onStop方法,优先级最低。

 

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

 

 

 

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326792116&siteId=291194637