一、了解活动生命周期
在生命周期回调方法中,你可以声明用户离开以及重新进入活动时活动的行为。例如,如果你正在构建流媒体视频播放器,当用户切换到其他应用时,你应该暂停视频并终止网络连接;当用户返回时,则要重新连接到网络并恢复视频。
良好的生命周期回调实现可以避免:
用户在使用应用程序时收到电话或切换到其他应用程序发生崩溃;
用户没有使用它时消耗系统资源;
用户离开应用程序并在稍后返回时丢失用户进度;
当屏幕在横向和纵向之间切换时崩溃或丢失用户的进度。
二、活动生命周期概念
三、生命周期回调
1. onCreate()
此回调必须实现,首次创建活动时触发,活动进入创建状态。该方法有一个 savedInstanceState
参数,它是一个包含活动先前保存状态的 Bundle 对象。如果活动从未存在过,则该对象的值为 null。
下面的 onCreate() 方法示例显示了活动的基本设置:
TextView mTextView;
// some transient state for the activity instance
String mGameState;
@Override
public void onCreate(Bundle savedInstanceState) {
// call the super class onCreate to complete the creation of activity like
// the view hierarchy
super.onCreate(savedInstanceState);
// recovering the instance state
if (savedInstanceState != null) {
mGameState = savedInstanceState.getString(GAME_STATE_KEY);
}
// set the user interface layout for this activity
// the layout file is defined in the project res/layout/main_activity.xml file
setContentView(R.layout.main_activity);
// initialize member TextView so we can manipulate it later
mTextView = (TextView) findViewById(R.id.text_view);
}
// This callback is called only when there is a saved instance that is previously saved by using
// onSaveInstanceState(). We restore some state in onCreate(), while we can optionally restore
// other state here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
mTextView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}
// invoked when the activity may be temporarily destroyed, save the instance state here
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putString(GAME_STATE_KEY, mGameState);
outState.putString(TEXT_VIEW_KEY, mTextView.getText());
// call superclass to save any view hierarchy
super.onSaveInstanceState(outState);
}
除了定义 XML 文件并传递给 setContentView()
,你还可以在代码中创建新的 View 对象,插入到一个 ViewGroup 来构建视图层次结构,然后传递根 ViewGroup 给 setContentView()
来使用该布局。
活动不会停留在创建状态。onCreate()
方法完成后,接着进入启动状态,系统将快速连续调用 onStart()
和 onResume()
方法。
2. onStart()
当活动进入启动状态时,系统调用此回调,活动对用户可见。这个回调为活动进入前台并变得可交互做最后准备。应用程序使用此方法来初始化维持 UI 的代码 。
跟创建状态一样,活动不会停留在启动状态。一旦这个回调完成,活动进入恢复状态,系统调用 onResume()
方法。
3. onResume()
当活动进入恢复状态时,它进入前台,然后系统调用 onResume()
回调。这是应用程序与用户交互的状态。应用停留在这种状态,直到某些事情发生将焦点从应用中移开,例如,接到电话,用户导航到其他活动或者设备屏幕关闭。
当发生中断事件时,活动进入暂停状态,系统调用 onPause()
回调。
4. onPause()
系统调用此方法表示用户正在离开活动,它表明该活动不再处于前台(但是如果用户处于多窗口模式,它仍然可见)。
活动会进入此状态有几个原因,例如:
正如
onResume()
部分所述,某些事件会中断应用程序的执行,这是最常见的情况。在 Android 7.0(API 等级 24)或更高版本中,多个应用程序以多窗口模式运行。因为任何时候只有其中一个应用程序(窗口)有焦点,此时系统会暂停所有其他应用程序。
一个新的、半透明的活动(如对话框)打开。只要该活动仍然部分可见但不具有焦点,它仍然处于暂停状态。
可以在 onPause()
方法内释放用户不需要的系统资源,因为这会影响电池寿命,还有处理传感器(如 GPS)。但是,如上面所说,在多窗口模式下,暂停的活动仍可以完全可见。因此,应该考虑使用 onStop()
而不是 onPause()
来完全释放或调整与 UI 相关的资源和操作,以更好地支持多窗口模式。
onPause()
的执行时间非常短,不一定有足够的时间执行保存操作。所以,不应该在 onPause()
方法内进行保存数据、网络连接、数据库事务等操作。相反,应该在 onStop()
方法中执行这些工作 。
onPause()
方法的完成并不意味着活动离开暂停状态。活动保持这种状态,直到活动重新恢复或者变得完全不可见为止。如果活动重新恢复,系统再次调用 onResume()
回调。如果活动完全不可见,则系统调用 onStop()
。
5. onStop()
当活动对用户不再可见时,它进入停止状态,并且系统调用 onStop()
回调。例如,当一个新的活动覆盖整个屏幕时,可能会发生这种情况。活动即将销毁时系统也可能会调用 onStop()
。
在 onStop()
方法中释放不需要的资源,并执行适当的保存数据操作。
下面例子展示了在 onStop()
方法中将草稿的内容保存到本地:
@Override
protected void onStop() {
// call the superclass method first
super.onStop();
// save the note's current draft, because the activity is stopping
// and we want to be sure the current note progress isn't lost.
ContentValues values = new ContentValues();
values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());
// do this update in background on an AsyncQueryHandler or equivalent
mAsyncQueryHandler.startUpdate (
mToken, // int token to correlate calls
null, // cookie, not used here
mUri, // The URI for the note to update.
values, // The map of column names and new values to apply to them.
null, // No SELECT criteria are used.
null // No WHERE columns are used.
);
}
当活动进入停止状态时,该 Activity 对象依然驻留在内存中:它维持所有状态和成员信息,但没有与窗口管理器关联。如果活动恢复,将重用这些信息。系统还会保存每个 View 对象的当前状态,因此如果用户在 EditText 控件中输入了内容,系统会保留该内容,所以无需手动保存和恢复它。
注意:一旦活动停止,系统可能会销毁包含该活动的进程。即使系统在活动停止时销毁进程,系统仍然会将 View 对象的状态(如 EditText 中的文本)保留在 Bundle 中,并在用户返回活动时恢复它们(配置更改的情况)。
活动离开停止状态,要么重新启动与用户交互,要么完成运行然后销毁。如果活动重新启动,系统会调用 onRestart()
;如果活动销毁,则系统调用 onDestroy()
。
6. onDestroy()
onDestroy()
在活动被销毁之前调用。
系统调用此回调有两个原因:
活动正在结束(用户手动丢弃活动或者活动调用了
finish()
方法);由于配置更改(如设备旋转),活动被暂时性销毁。
可以使用 isFinishing()
方法区分这两种情况 。
如果活动正在结束,则 onDestroy()
是活动最后接收的生命周期回调。如果 onDestroy()
是因为配置更改而被调用,系统会立即创建一个新的活动实例,然后在新实例中调用 onCreate()
。
应该在 onDestroy()
回调中释放所有资源。
四、活动状态以及从内存中弹出
系统不会直接杀死一个活动来释放内存,它杀死的是进程,这不仅会销毁活动,还会销毁进程中所有正在运行的东西。
五、保存和恢复 UI 状态
当 UI 配置更改时,例如旋转或切换到多窗口模式,系统会默认销毁活动,并删除存储在活动实例中的任何 UI 状态。用户离开当前活动,系统也可能会杀死当前进程。
如果要恢复的 UI 数据是轻量级的,比如基本数据类型或简单对象(比如 String),那么可以单独使用 onSaveInstanceState()
。但在大多数情况下,使用 ViewModel
和 onSaveInstanceState()
组合会更好,因为 onSaveInstanceState()
会产生序列化和反序列化的开销。
1. 实例状态
如果用户通过按下 “后退” 按钮或者活动通过调用 finish()
方法使其自身销毁 。在这些情况下,用户的期望与系统的行为相匹配,因此我们不需要对此做额外的恢复工作。
但是,如果是由于系统限制(例如配置更改或内存压力)而销毁活动,则尽管 Activity 实例已经不存在,但系统还是会保存它的数据。当用户导航回到该活动,系统会使用之前保存的数据新建一个该活动的新实例。
系统用于恢复先前状态的数据称为实例状态,是存储在 Bundle 对象中的键值对的集合。默认情况下,系统使用 Bundle 实例状态来保存活动布局中每个 View 对象的信息(例如 EditText 控件中的文本值)。因此,如果活动实例被销毁并重新创建,那么布局的状态将恢复到之前的状态,并且不需要额外编写代码来实现。但是,活动中可能包含更多想要恢复的状态信息,例如记录用户进度的成员变量。
注意:为了使 Android 系统恢复活动中视图的状态,每个视图必须具有由
android:id
属性提供的唯一 ID 。
一个 Bundle 对象不适合保存大量的数据,因为它需要在主线程上执行序列化并消耗内存。要保留大量的数据,你应该采用综合方法来保存数据,使用持久本地存储、onSaveInstanceState()
方法和 ViewModel
类。
2. 使用 onSaveInstanceState() 保存简单轻量级的 UI 状态
当活动开始停止时,系统会调用 onSaveInstanceState()
方法将状态信息保存到实例状态 bundle 中。此方法默认保存视图的瞬时信息,例如 EditText 中的文本或 ListView 的滚动位置 。
为了保存活动的其他实例状态信息,必须覆盖 onSaveInstanceState()
并添加键值对,以便在活动意外销毁时保存这些信息。重写 onSaveInstanceState()
时最好调用超类实现,如果你希望默认保存视图的状态。例如:
static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
注意:当用户明确关闭活动或在其他情况下活动调用了
finish()
时,onSaveInstanceState()
不会被调用。
如果要保存持久性数据(例如用户首选项或数据库数据),则应该在活动处于前台时,在适当的时候保存。或者在 onStop()
方法期间保存这些数据。
3. 使用保存的实例状态恢复活动的 UI 状态
当活动被销毁之后重新创建时,你可以从系统传递给活动的 Bundle 中获取已保存的实例状态。无论是 onCreate()
或者是 onRestoreInstanceState()
回调方法都会收到相同的 Bundle。
因为系统创建活动的新实例或者是重新创建之前的实例,都会调用 onCreate()
方法,因此必须在读取状态之前检查 Bundle 是否为空。
例如,下面的代码片段显示了如何恢复一些状态数据:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
} else {
// Probably initialize members with default values for a new instance
}
// ...
}
你也可以选择在 onRestoreInstanceState()
方法中恢复状态,onRestoreInstanceState()
在 onStart()
之后调用。系统仅在存在保存状态才调用此方法,因此不需要检查 Bundle 是否为空:
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}
注意:始终调用
onRestoreInstanceState()
的超类实现,以便默认可以恢复视图的状态。
六、在活动之间进行导航
1. 启动活动
可以使用 startActivity()
或 startActivityForResult()
方法启动一个新活动,取决于你是否想要从新活动返回结果。无论哪种情况,你都要传入一个 Intent 对象。
该 Intent 对象指定要启动的确切活动或者描述你要执行的操作(系统会选择适合的活动)。一个 Intent 对象还可以携带少量的数据供启动的活动使用。
1.1 startActivity()
如果新启动的活动不需要返回结果,则可以通过调用 startActivity()
方法来启动它 。
例如,以下代码片段显示了如何启动一个名为 SignInActivity 的活动 。
Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);
你还可以使用隐式启动的方式启动其他应用的活动。例如,如果你想让用户发送电子邮件,而自己的应用中不具备该功能的活动,则可以创建以下 Intent:
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
EXTRA_EMAIL
用来标识发送地址,它对应的值是一个字符串数组。当一个电子邮件应用程序响应这个 Intent 时,它读取提供的字符串数组,并将它们放在电子邮件表单的 “to”
字段中。
1.2 startActivityForResult()
有时候你想在子活动结束时返回结果给父活动。
当一个子活动退出时,它可以调用 setResult(int)
将数据返回给父活动。子活动必须提供结果代码(RESULT_CANCELED
、RESULT_OK
,或者以 RESULT_FIRST_USER
开头的自定义值)。除此之外,子活动还可以返回一个包含其他数据的 Intent 对象。父活动使用 onActivityResult(int, int, Intent)
方法来接收信息。
如果子活动由于某种原因(例如崩溃)而没有正常退出,那么父活动会收到 RESULT_CANCELED
的结果。
public class MyActivity extends Activity {
// ...
static final int PICK_CONTACT_REQUEST = 0;
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
// When the user center presses, let them pick a contact.
startActivityForResult(
new Intent(Intent.ACTION_PICK,
new Uri("content://contacts")),
PICK_CONTACT_REQUEST);
return true;
}
return false;
}
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == PICK_CONTACT_REQUEST) {
if (resultCode == RESULT_OK) {
// A contact was picked. Here we will just display it
// to the user.
startActivity(new Intent(Intent.ACTION_VIEW, data));
}
}
}
}
2. 协调活动
当一个活动启动另一个活动时,它们都会经历生命周期转换。第一个活动停止操作并进入暂停或停止状态,而另一个活动则被创建。重要的是,我们要意识到,如果这些活动共享数据,我们要管理好活动之间的信息过渡。
不过不用过分担心,生命周期回调的顺序已被很好地定义,特别是当两个活动处于同一个进程并且一个正在启动另一个时。
以下是活动 A 启动活动 B 时发生的操作顺序:
活动 A 的
onPause()
方法执行。活动 B 的
onCreate()
,onStart()
和onResume()
方法按顺序执行(活动 B 获得用户焦点)。然后,如果活动 A 在屏幕上不再可见,则执行其
onStop()
方法。
我们可以利用这种可预测的生命周期回调序列,管理从一个活动到另一个活动的信息过渡。