目录
onSaveInstanceState()和onRestoreInstanceState()
前言
该篇续 初识Activity ,上一篇主要介绍了一些基础用法,该篇为进阶。
Activity 任务堆栈
从上一篇的使用中,我们一定会有一个感性的认识,即Activity是可层层叠加的,启动一个 新的Activity 即覆盖在 原Activity 之上。那么在 Android 中是怎么管理这些 Activity 的呢?
答案是Task(任务),一个任务就是一组放在栈中的活动集合,这个栈也被称为“返回栈”
堆栈中的 根Activity 就是应用程序的 主Activity。而堆栈中 最上方的Activity 则是当前运行的 / 用户正在操作的 UI Activity,每当我们启动一个 Activity 时,则该 Activity 入栈,按 Back 或人为销毁则出栈。
除了 最顶层的Activity 外,其它的 Activity 都有可能在系统内存不足时被回收,一个 Activity 的实例越是处在栈的底层,它被系统回收的可能性越大。系统 负责管理栈中 Activity 的实例,它根据 Activity 所处的状态来改变其在栈中的位置。
Activity 基本状态
除了中间状态,Activity 基本上以以下四种状态存在:
- Resumed(继续/运行态)
此 Activity 位于屏幕前台并具有用户焦点。(有时也将此状态称作 “运行中”。)
- Paused(暂停)
存在另一个 Activity 位于屏幕前台并具有用户焦点,但此 Activity 仍可见。也就是说,另一个 Activity 显示在 此 Activity 上方,并且该 Activity 部分透明或未覆盖整个屏幕;此时此 Activity处于暂停状态。暂停的 Activity 处于完全活动状态(
Activity
对象保留在内存中,它保留了所有状态和成员信息,并与窗口管理器保持连接),但在内存极度不足的情况下,可能会被系统终止。
- Stopped(停止)
该 Activity 被另一个 Activity 完全遮盖(该 Activity 目前位于 “后台”)。 已停止的 Activity 同样仍处于活动状态(
Activity
对象保留在内存中,它保留了所有状态和成员信息,但未与窗口管理器连接)。不过,它对用户不再可见,在他处需要内存时可能会被系统终止。
- Killed(销毁)
该活动被系统结束或未启动,系统会倾向于回收这种状态的活动,以保证手机内存的充足。
ps:如果 Activity 处于暂停或停止状态,系统可通过要求其结束(调用其
finish()
方法)或直接终止其进程,将其从内存中删除。(将其结束或终止后)再次打开 Activity 时,则必须重建。
Activity 生命周期
先上 Android Activity 的 生命周期图示
当一个 Activity 转入和转出上述不同状态时,系统会通过各种回调方法向其发出通知。 Activit类 中定义了 7 个回调方法,覆盖了活动生命周期的每一个环节。这些回调方法 除 onRestart()外都是两两对立的,可分为 3 种生存期。
相关函数
下面我们简单过一下这些回调函数:
- onCreate()
活动第一次创建时被调用,我们一般在此完成活动的初始化操作。
- onStart()
在活动由不可见变为可见的时候调用。
- onResume()
活动准备好和用户进行交互时调用,此时,该活动一定处于运行状态,位于栈顶。
- onPause()
系统准备去启动或恢复另一个活动的时候调用。
- onStop()
活动完全不可见时调用。
- onDestroy()
活动销毁前调用,执行之后,活动的状态将变为销毁状态。
- onRestart()
活动由停止状态变为运行状态之前调用。
验证
程序正常启动
onCreate ()->onStart ()->onResume ()
正常退出
onPause ()->onStop ()->onDestory ()
Activity1 启动另一个 Activity2
(以下皆为 Activity1 回调)
启动:onPause ()->onStop ()
再返回:onRestart ()->onStart ()->onResume ()
程序按 back 退出再进入
程序按 back 退出: onPause ()->onStop ()->onDestory ()
再进入:onCreate ()->onStart ()->onResume ()
程序按 home 退出再进入
程序按 home 退出: onPause ()->onStop ()
再进入:onRestart ()->onStart ()->onResume ()
屏幕翻转
onPause ()->onStop ()->onDestory ()->onCreate ()->onStart ()->onResume ()
知晓当前活动
Log.i("MainActivity",getClass().getSimpleName());
//或
Log.i("MainActivity",getClass().getName());
随时随地退出程序
为何要随时随地退出程序呢?
例如我进入多个Activity :Activity1 --> Activity2 --> Activity3...
此时如果我想退出程序,需要按 多次 Back,无疑这是非常影响体验的。
为此我们创建一个ActivityManager类,来 管理Activity
ActivityManager.java
package com.example.mytest; import android.app.Activity; import java.util.ArrayList; import java.util.List; /** * 静态方法,不需new,通过类名方法名即可调用 */ public class ActivityManager { public static List<Activity> activities = new ArrayList<>(); public static void addActivity(Activity activity){ activities.add(activity); } public static void removeActivity(Activity activity){ activities.remove(activity); } public static void finishAll(){ for (Activity activity : activities){ if (!activity.isFinishing()){ activity.finish(); } } activities.clear(); //杀死当前程序进程的代码,保证程序彻底退出。 android.os.Process.killProcess(android.os.Process.myPid()); } }
再创建一个继承自 AppCompatActivity 的 BaseActivity,并从写onCreate()和onDestory()
BaseActivity.java
package com.example.mytest; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityManager.addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); ActivityManager.removeActivity(this); } }
让 MainActivity 和 Item1 都需继承自这个 BaseActiivty,而不是其父类 AppCompatActivity
MainActivity-->Item1-->onClick退出
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ActivityManager.finishAll(); } });
效果
Activity 的现场保存
通过前面生命周期的探究,我们知道在 屏幕翻转 或 点击Home 时,Android 会重启正在运行的 Activity。重启行为旨在通过利用与新设备配置匹配的备用资源自动重新加载您的应用,来帮助它适应新配置。
Demo:
Add_Button 点击事件使变量 a 增一,并显示到 TextView 上
屏幕翻转后,TextView 显示的值又归零了
假设此处是一调查问卷,当用户 有电话打入 或 需要暂时退出。 那么,之前所输入的信息就会全部清空。这也是非常影响体验的(吐槽一下前不久进行的志愿者注册就是这个亚子)。那么该如何解决这呢?
对于翻转屏幕造成的数据丢失,我们可以采用一个简单的策略:
锁定屏幕显示方向
在 AndroidManifest.xml 中添加 Activity 属性
//横屏锁定
android:screenOrientation="landscape"
//竖屏锁定
android:screenOrientation="portrait"
显然,这种方法并不是正解。
那么还有没有简单的方法呢?
自行处理变更
答案是 有,我们可以告诉系统 自己来处理变更
在 AndroidManifest.xml 中添加 Activity 属性
android:configChanges="orientation|keyboardHidden|screenSize"
效果图
上面讲的方法,只能虽然达到了现场保护的要求,并且简单易用,但是只能算前菜;下面才是主菜!
onSaveInstanceState()和onRestoreInstanceState()
这里我们重写
onSaveInstanceState( Bundle outState)
onRestoreInstanceState(Bundle savedInstanceState)
并通过 Log 打印出来
可以发现:
当 Activity 开始停止时,系统会调用,onSaveInstanceState ( ) 以便我们的 Activity 可以使用一组键值对来保存状态信息。
当 Activity 在被销毁后重新创建时,系统会在onResume ( ) 前调用onRestoreInstanceState ( )方法,以便我们从 Bundle 系统恢复保存状态。
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
//保存数据
outState.putString("number",textView.getText().toString());
Log.i("MainActivity","onSaveInstanceState()");
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//获取数据
String s = savedInstanceState.getString("number");
textView.setText(s);
a = Integer.valueOf(s);
Log.i("MainActivity","onRestoreInstanceState()");
}
注意到:void onCreate(Bundle savedInstanceState) 中,也是 Bundle 参数。
实际上, onCreate () 和 onRestoreInstanceState () 都会收到 Bundle 包含实例状态信息。
自然,我们也可以直接在onCreate()取得保存状态
if(null != savedInstanceState){
String s = savedInstanceState.getString("number");
textView.setText(s);
a = Integer.valueOf(s);
}
效果
PS:onSaveInstanceState()仅适合保存临时数据,它 并非百分百调用(比如点击了 back 键)。显然一些永久性的数据,我们并不能在此中保存。
我们需关注另一个回调函数 onPause()
无论出现怎样的情况,比如程序突然死亡了,能保证的就是 onPause() 方法是一定会调用的;这个特性使得 onPause()是持久化相关数据的最后的可靠时机。当然 onPause()方法不能做大量的操作,否则会影响下一个 Activity 入栈。
全屏显示
在onCreate()函数中加入以下代码
// 设置全屏模式
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 若有标题栏,还需去除标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
效果
参考鸣谢
郭霖 - 第一行代码(第二版)
Android 组件系列 ----Activity 组件详解