Activity生命周期异常分析
我们知道正常情况下Activity创建的时候会执行 onCreate onStart onResume 方法;当锁屏情况下Activity会执行 onPause onStop方法;当屏幕再次显示的时候会执行onReStart onStart onResume方法。但是在一些特殊情况下,如语言切换,横竖屏切换等配置改变以及内存吃紧的情况下,activity就有可能被杀死,从而导致生命周期异常。这对于开发人员来说,了解这些是很有必要的。下面我们来具体分析这两种情况吧
(一)情况1:资源相关的配置信息发生改变导致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的状态是怎么保存的,你在看View源码的时候,你会发现在View的源码中onSaveInstance和onRestoreInstanceState方法,Activity中也有这2个方法,难道Activity通过调用View的这2个方法来恢复状态的?(说明:Fragment其实也有这2个方法)
答案是肯定的,关于保存和恢复View的层级结构,系统的工作流程是这样的:首先Activity被意外终止时,Activity会调用onSaveInstance方法去保存数据,然后Activity会委托Window去保存数据,接着Window再委托它上面的顶层容器去保存数据,顶层容器(DecorView)是一个ViewGroup.最后顶层容器再去一一通知它的子元素来保存数据,这样整个保存过程就这样完成了。可以发现,这是一种典型的委托思想,上层委托下层,容器委托子元素去处理一件事情,这种思想在Android中有很多应用,比如View的绘制过程,事件分发过程都是采用类似的思想。(说明:fragment跟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源码中,找到了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中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死。
相关推荐
前言第1章 Activity的生命周期和启动模式1.1 Activity的生命周期全面分析1.1.1 典型情况下的生命周期分析1.1.2 异常情况下的生命周期分
1.1.2 异常情况下的生命周期分析 / 8 1.2 Activity的启动模式 / 16 1.2.1 Activity的LaunchMode / 16 1.2.2 Activity的Flags / 27 1.3 IntentFilter的匹配规则 / 28 第2章 IPC机制 / 35 2.1 Android ...
交互分析:分析Activity生命周期耗时,帮助提升页面打开速度,优化用户UI体验 网络请求分析:监控流量使用情况,发现并定位各种网络问题 内存分析:全面监控内存使用情况,降低内存占用 进程监控:针对多进程应用,...
1.1.2 异常情况下的生命周期分析 8 1.2 Activity的启动模式 16 1.2.1 Activity的LaunchMode 16 1.2.2 Activity的Flags 27 1.3 IntentFilter的匹配规则 28 第2章 IPC机制 35 2.1 Android IPC简介 35 2.2 ...
1.1.2 异常情况下的生命周期分析 / 8 1.2 Activity的启动模式 / 16 1.2.1 Activity的LaunchMode / 16 1.2.2 Activity的Flags / 27 1.3 IntentFilter的匹配规则 / 28 第2章 IPC机制 / 35 2.1 Android ...
目录 第1章 Activity的生命周期和启动模式 / 1 1.1 Activity的生命周期全面分析 / 1 1.1.1 典型情况下的生命周期分析 / 2 1.1.2 异常情况下的生命周期分析 / 8 1.2 Activity的启动模式 / 16 1.2.1 Activity的...
又如何管理插件中四大组件的生命周期呢,没有生命周期的四大组件是没有意义的。而且Activity是必须要在AndroidManifest中注册的,不注册就会抛出异常,那么怎么能绕过这个限制呢,还有,一个apk中肯定会用过各种资源...
(1)针对多窗口类浏览器模式问题,指出并分析了该问题存在的原因,利用Activity的运行机制,通过Fragment栈对主要模块的Webview进行管理,实现对不同模块之间切换的控制。 (2)针对跨域数据交互问题,指出并分析了...
2.7 了解应用程序生命周期 2.8 调试应用程序 2.8.1 启动模拟器 2.8.2 StrictMode 2.8.3 参考资料 2.9 小结 第3章 使用Android资源 3.1 资源 3.1.1 字符串资源 3.1.2 布局资源 3.1.3 ...
2.7 了解应用程序生命周期 2.8 调试应用程序 2.8.1 启动模拟器 2.8.2 StrictMode 2.8.3 参考资料 2.9 小结 第3章 使用Android资源 3.1 资源 3.1.1 字符串资源 3.1.2 布局资源 3.1.3 ...