重学 Android - Activity 上--生命周期

最近在看任玉刚的《Android艺术探索 》,准备边看边做笔记。
第一章自然是 Activity

Activity 生命周期

Activity 生命周期

1.正常情况下的生命周期

阶段 定义 备注
onCreate Activity 正在创建 加载布局资源,不可进行耗时操作
onRestart Activity从不可见到可见,比如按Home键回到桌面又返回
onStart Activity可见,但不能操作 可以重新开始 onPause中暂停的动画等效果
onResume Activity可见,可交互
onPause Activity 正在停止,开始跳转,不可交互,执行完后,新Activity的onResume才会执行 界面动画停止,数据存储,不能进行耗时操作
onStop Activity即将停止,Activity 设置透明主题不会调用 可以做稍重一点的回收工作
onDestroy Activity已销毁 释放资源,回收工作

2.异常情况下的生命周期

异常情况下的生命周期

情况1: 资源相关的系统配置发生改变导致 Activity 被杀死并重新创建

比如说当前屏幕从竖屏切换到横屏,在未特殊处理的情况下,Activty 会被销毁并重新创建 Activity,并且会调用 onSaveInstanceState ,保存当前 Activity 状态,并且把保存的状态传递给重新创建 Activity 时调用的onCreate 和 onRestoreInstanceState 方法。同时需要知道的是,在异常销毁的情况下,系统会默认保存销毁前的状态,并在重建时恢复状态。例如,当前布局,布局中 View 的数据等等。具体怎么恢复需要查看各个 View 的源码,它们的都有各自的 onSaveInstanceState 和 onRestoreInstanceState 方法。

接着来实际测试一下 EditText ,会不会自动保存恢复,在这之前先看一下 TextVIew (EditText 继承自 TextView)的 onSaveInstanceState 和 onRestoreInstanceState 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
thumbnail: https://images.unsplash.com/photo-1563952565009-c5afd2643dd6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60

@Override
public Parcelable onSaveInstanceState() {
//省略代码

if (freezesText || hasSelection) {
SavedState ss = new SavedState(superState);

if (freezesText) {
if (mText instanceof Spanned) {
final Spannable sp = new SpannableStringBuilder(mText);

if (mEditor != null) {
removeMisspelledSpans(sp);
sp.removeSpan(mEditor.mSuggestionRangeSpan);
}

ss.text = sp;
} else {
ss.text = mText.toString();
}
}


//省略代码

if (mEditor != null) {
ss.editorState = mEditor.saveInstanceState();
}
return ss;
}

return superState;
}

//android-29 TextVIew#onRestoreInstanceState
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}

SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());

// XXX restore buffer type too, as well as lots of other stuff
if (ss.text != null) {
setText(ss.text);
}

//省略代码

if (ss.editorState != null) {
createEditorIfNeeded();
mEditor.restoreInstanceState(ss.editorState);
}
}

以上代码可以看到,在 onSaveInstanceState 中将控件的文本以及样式保存了下来,并且在 onRestoreInstance 中恢复。

再来上实操一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class AActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.let {
print("onCreate , ${it["activity"]}")
}

setContentView(R.layout.activity_scrolling)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putString("activity","activity 中保存的数据")
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
print("onRestoreInstanceState , ${savedInstanceState["activity"]}")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".BaseActivity">

<androidx.appcompat.widget.AppCompatEditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="请输入文本"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

咦?怎么没有恢复?

1
2
3
4
5
6
7
8
9
10
11
12
13
AActivity.onCreate
AActivity.onStart
AActivity.onResume
AActivity.onPause
AActivity.onStop
AActivity.onSaveInstanceState
AActivity.onDestroy
AActivity.onCreate
onCreate ,activity 中保存的数据
AActivity.onStart
AActivity.onRestoreInstanceState
onRestoreInstanceState ,activity 中保存的数据
AActivity.onResume

从生命周期打印日志可以看出 activty 中手动保存的数据都得到了正确恢复,但是 EditText 的数据并没有得到恢复

经过一番搜索(面向 google),「Android View onSaveInstanceState not called」提到,需要给 View 设置 id ,布局文件或者代码中都行,系统才会正确的找到 View ,并做保存恢复的处理,这是该书中未提到的。

还有一点,我注意到书中强调只有异常销毁情况才会调用 onSaveInstanceState,但经测试,从A跳转到B,或者返回桌面都会调用 onSaveInstanceState 方法,但返回 A 后不会调用 onRestoreInstanceState。再者,即使手动杀死 App ,重新打开 onCreate 也不能恢复数据。

猜测应该是因为 Activity 失去焦点后都用被系统杀掉的可能,所以在某个版本的 Activity 或者继承类中添加了该特性,如「Android面试题-onSaveInstanceState源码内核分析」中提到的。

Android P 版本以后 onSaveInstanceState 在 onStop 之后调用,之前的版本在 onStop 之前调用。
基于 2020-04-18 当前最新编译环境, API 22、27、29 的虚拟机均有调用

情况2:系统资源不足导致优先级低的 Activity 被销毁

这种情况不太好模拟,但数据保存和重建的过程是一致的。而 Activity 的优先级从高到低是这样的:

优先级 描述 周期
前台 Activity 可见,可交互 onResume
Activity 可见,不可交互 弹出 Dialog
后台 Activity不可见,不可交互 onStop

当系统资源紧缺时,系统会按照优先级低到高的顺序杀死目标 Activity 所在进程,并且会调用 onSaveInstanceState 进行保存数据以及重建时调用 onRestoreInstanceState 恢复数据。
并且一个进程中如果没有四大组件在运行,这个进程会优先被杀死,所以如果有后台工作要进行可以用 Service 放在后台,保证较高优先级。

上面提到,当前系统配置发生改变时(比如旋转屏幕),Activity 会销毁重建。这时可以通过在 AndroidManifest.xml 下的 activity 标签设置 configChanges = “orientation|screenSize”,使得旋转屏幕时不销毁重 Activity,并且旋转后会调用 Activity#onConfigurationChanged 方法,而不会调用其他生命周期方法。除了 orientation 还有一下系统配置变化时会引起这种情况:

来源: android:configChanges

描述
“density” 显示密度发生变更 — 用户可能已指定不同的显示比例,或者有不同的显示现处于活跃状态。此项为 API 级别 24 中的新增配置。
“fontScale” 字体缩放系数发生变更 — 用户已选择新的全局字号。
“keyboard” 键盘类型发生变更 — 例如,用户插入外置键盘。
“keyboardHidden” 键盘无障碍功能发生变更 — 例如,用户显示硬键盘。
“layoutDirection” 布局方向发生变更 — 例如,自从左至右 (LTR) 更改为从右至左 (RTL)。

此项为 API 级别 17 中的新增配置。
“locale” 语言区域发生变更 — 用户已为文本选择新的显示语言。
“mcc” IMSI 移动设备国家/地区代码 (MCC) 发生变更 — 检测到 SIM 并更新 MCC。
“mnc” IMSI 移动设备网络代码 (MNC) 发生变更 — 检测到 SIM 并更新 MNC。
“navigation” 导航类型(轨迹球/方向键)发生变更。(这种情况通常不会发生。)
“orientation” 屏幕方向发生变更 — 用户旋转设备。

请注意:如果应用面向 Android 3.2(API 级别 13)或更高版本的系统,则还应声明 “screenSize” 配置,因为当设备在横向与纵向之间切换时,该配置也会发生变更。
“screenLayout” 屏幕布局发生变更 — 不同的显示现可能处于活跃状态。
“screenSize” 当前可用屏幕尺寸发生变更。该值表示当前可用尺寸相对于当前纵横比的变更,当用户在横向与纵向之间切换时,它便会发生变更。此项为 API 级别 13 中的新增配置。
“smallestScreenSize” 物理屏幕尺寸发生变更。

该值表示与方向无关的尺寸变更,因此它只有在实际物理屏幕尺寸发生变更(如切换到外部显示器)时才会变化。对此配置所作变更对应 smallestWidth 配置的变化。

此项为 API 级别 13 中的新增配置。
“touchscreen” 触摸屏发生变更。(这种情况通常不会发生。)
“uiMode” 界面模式发生变更 — 用户已将设备置于桌面或车载基座,或者夜间模式发生变更。如需了解有关不同界面模式的更多信息,请参阅 UiModeManager。

此项为 API 级别 8 中的新增配置。

重学 Android - Activity 上--生命周期

http://chinnsenn.com/2020/04/17/RelearnAndroid_activity/

作者

ChinnSenn

發表於

2020-04-17

更新於

2023-04-20

許可協議

評論