Android Activity 探索(二)— 生命周期

一、了解活动生命周期

在生命周期回调方法中,你可以声明用户离开以及重新进入活动时活动的行为。例如,如果你正在构建流媒体视频播放器,当用户切换到其他应用时,你应该暂停视频并终止网络连接;当用户返回时,则要重新连接到网络并恢复视频。

良好的生命周期回调实现可以避免:
  • 用户在使用应用程序时收到电话或切换到其他应用程序发生崩溃;

  • 用户没有使用它时消耗系统资源;

  • 用户离开应用程序并在稍后返回时丢失用户进度;

  • 当屏幕在横向和纵向之间切换时崩溃或丢失用户的进度。

二、活动生命周期概念

Android Activity 探索(二)— 生命周期_第1张图片
Activity 的生命周期示意图

三、生命周期回调

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() 回调中释放所有资源。

四、活动状态以及从内存中弹出

Android Activity 探索(二)— 生命周期_第2张图片
进程状态与活动状态与系统杀死进程的可能性之间的关系

系统不会直接杀死一个活动来释放内存,它杀死的是进程,这不仅会销毁活动,还会销毁进程中所有正在运行的东西。

五、保存和恢复 UI 状态

当 UI 配置更改时,例如旋转或切换到多窗口模式,系统会默认销毁活动,并删除存储在活动实例中的任何 UI 状态。用户离开当前活动,系统也可能会杀死当前进程。

如果要恢复的 UI 数据是轻量级的,比如基本数据类型或简单对象(比如 String),那么可以单独使用 onSaveInstanceState()。但在大多数情况下,使用 ViewModelonSaveInstanceState() 组合会更好,因为 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_CANCELEDRESULT_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 时发生的操作顺序:
  1. 活动 A 的 onPause() 方法执行。

  2. 活动 B 的 onCreate()onStart()onResume() 方法按顺序执行(活动 B 获得用户焦点)。

  3. 然后,如果活动 A 在屏幕上不再可见,则执行其 onStop() 方法。

我们可以利用这种可预测的生命周期回调序列,管理从一个活动到另一个活动的信息过渡。

你可能感兴趣的:(Android Activity 探索(二)— 生命周期)