activity生命周期

https://developer.android.google.cn/guide/components/activities/activity-lifecycle.html  --翻译水平有限,欢迎指正

当一个用户在应用程序内,离开,回到你的应用程序,应用程序的activity实例切换他们在生命周期中的不同状态。Activity类提供了许多回调,允许activity知道状态发生了变化:系统正在创建、停止或恢复activity,或销毁activity所在的进程。

在生命周期回调方法中,可以声明当用户离开并重新进入activity时activity的行为。例如,如果您正在构建一个流媒体播放器,当用户切换到另一个应用程序时,您可能会暂停视频并终止网络连接。当用户返回时,您可以重新连接到网络,并允许用户从相同的位置恢复视频。换句话说,每个回调允许您执行适合于给定的状态更改的特定工作。在正确的时间做正确的工作和正确的处理转换会使您的应用程序更加健壮和高效。例如,生命周期回调的良好实现可以帮助确保您的应用程序避免:

  • 如果用户在使用你的应用程序时接到一个电话或切换到另一个应用程序时崩溃。
  • 当用户没有积极地使用它时,会消耗宝贵的系统资源。
  • 如果用户离开你的应用程序并在稍后返回到它时丢失用户的进度。
  • 当屏幕在横向和纵向旋转时用户的进程崩溃或丢失

本文档详细解释了activity生命周期。文档首先描述生命周期模式。接下来,它解释了每个回调:当它们执行时内部发生了什么,以及在它们中应该实现什么。然后简要地介绍了activity状态与被系统杀死的进程的脆弱性之间的关系。最后,讨论了与activity状态之间的转换相关的几个主题。

 

activity生命周期概念


 为了在activity生命周期的各个阶段之间进行转换,activity类提供了6个回调的核心集合:onCreate()、onStart()、onResume()、onPause()、onStop()和onDestroy()。当一个activity进入一个新状态时,系统调用这些回调。

当用户开始离开activity时,系统调用方法来拆除activity。在某些情况下,这种拆卸只是局部的;该activity仍然驻留在内存中(比如当用户切换到另一个应用程序时),并且仍然可以返回到前台。如果用户返回到该activity,那么activity将从用户停止的地方恢复。系统可能会杀死给定的进程,以及它的activity,这取决于activity当时的状态。

根据您的activity的复杂性,您可能不需要实现所有的生命周期方法。然而,重要的是你要了解每一个,并实现那些确保你的应用程序的行为符合用户期望的回调方法。

 

生命周期回调


 onCreate()

这个回调必须实现,当系统第一次创建activity时触发这个回调。在activity创建过程中,activity进入 Created 状态。在onCreate()方法中,您将执行基本的应用程序启动逻辑,这种逻辑应该在activity的整个生命周期中只发生一次。例如,onCreate()的实现可能将数据绑定到列表,初始化后台线程,并实例化一些类范围变量。此方法接收参数savedInstanceState,它是包含activity先前保存状态的Bundle对象。如果该activity以前从未存在过,那么Bundle对象的值为null。

下面的onCreate()方法示例显示了activity的基本配置,例如声明用户界面(在XML布局文件中定义)、定义成员变量和配置一些UI。在本例中,XML布局文件通过传递文件的资源ID  R.layout.main_activity 给setContentView()指定。

TextView mTextView;

// activity实例的一些瞬态
String mGameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // 调用超类onCreate来完成像视图层次结构这样的activity。
    super.onCreate(savedInstanceState);

    // 恢复实例状态
    if (savedInstanceState != null) {
        mGameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // 为该activity设置用户界面布局。
    setContentView(R.layout.main_activity);

    // 初始化成员TextView以便稍后处理它。
    mTextView = (TextView) findViewById(R.id.text_view);
}

// 只有在使用onSaveInstanceState()之前保存了一个实例时,才调用这个回调。
//我们在onCreate()中恢复一些状态,而我们可以选择在这里恢复其它状态,可能在onStart()完成后可用。savedInstanceState bundle与onCreate()中使用的bundle相同
@Override public void onRestoreInstanceState(Bundle savedInstanceState) { mTextView.setText(savedInstanceState.getString(TEXT_VIEW_KEY)); } // 在activity可能被临时销毁时调用,在这里保存实例状态。 @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()的替代方法,您可以在activity代码中创建新的View对象,并将新视图插入到ViewGroup中,从而构建视图层次结构。然后,通过将根ViewGroup传递给setContentView()来使用该布局。

您的activity并不驻留在Created的状态中。在onCreate()方法完成执行之后,activity进入 Started 状态,系统将快速连续地调用onStart()和onResume()方法。

onStart()

当activity进入 Started 状态时,系统将调用此回调。随着应用程序为activity进入前台并可交互做准备,onStart()调用使用户可以看到activity。例如,这个方法用于应用程序初始化维护UI的代码。它还可以注册一个BroadcastReceiver 来监控反映在UI中的更改。

onStart()方法完成得非常快,与 Created 状态一样,该activity不会驻留在 Started 状态中。一旦这个回调完成,activity就进入Resumed 状态,系统调用onResume()方法。

onResume()

当activity进入 Resumed 状态时,它来到前台,然后系统调用onResume()回调。这是应用程序与用户交互的状态。应用程序会保持这个状态,直到某件事情使它失去焦点。这样的事件可能会是,例如,收到一个电话,用户导航到另一个activity,或者设备屏幕关闭。

当中断事件发生时,activity进入 Paused 状态,系统调用onPause()回调。

 如果activity从 Paused 状态返回到 Resumed 状态,系统将再次调用onResume()方法。出于这个原因,您应该实现onResume()来初始化在onPause()期间释放的组件。例如,您可以如下那样初始化摄像机:
@Override
public void onResume() {
    super.onResume();  // Always call the superclass method first

    // Get the Camera instance as the activity achieves full user focus
    if (mCamera == null) {
        initializeCamera(); // Local method to handle camera init
    }
}

 请注意,每次您的activity进入前台时,系统都会调用此方法,包括第一次创建的时候。因此,您应该实现onResume()来初始化在onPause()期间释放的组件,并执行每次activity进入 Resumed 状态时必须执行的任何其它初始化。例如,您应该启动动画和初始化该activity只在有获得用户焦点时才使用的组件。

onPause()

系统将此方法视为用户离开您的activity的第一个迹象(尽管它并不总是意味着activity正在被销毁)。使用onPause()方法暂停动画和音乐播放这样的操作,它们在activity处于 Paused 状态时不应该继续,并且您希望很快恢复。一个activity进入这个状态可能有几个原因。例如:

  • 某些事件中断应用程序执行,如onResume()部分所述。这是最常见的情况。
  • 在Android 7.0 (API级别24)或更高版本中,多个应用程序在多窗口模式下运行。因为在任何时候都只有一个应用程序(窗口)获得焦点,系统会暂停所有其它应用程序。
  • 一个新的半透明的activity(如对话框)打开。只要activity仍然是部分可见的但没有获得焦点,它就会暂停。

当您的activity暂停并且用户不需要它们,可以使用onPause()方法来释放系统资源,例如广播接收器、传感器(如GPS)或任何可能影响电池寿命的资源。

例如,如果应用程序使用了摄像头,onPause()方法是释放它的好地方。下面的onPause()示例与上面的onResume()示例相对应,释放onResume()示例初始化的摄像机。

@Override
public void onPause() {
    super.onPause();  // Always call the superclass method first

    // Release the Camera because we don't need it when paused
    // and other activities might need to use it.
    if (mCamera != null) {
        mCamera.release();
        mCamera = null;
    }
}

 onPause()执行非常短暂,没有足够的时间来执行保存操作。出于这个原因,您不应该使用onPause()来保存应用程序或用户数据、进行网络调用或执行数据库事务;这样的工作在方法结束之前可能无法完成。相反,您应该在onStop()期间执行重负荷的关闭操作。

onPause()方法的完成并不意味着activity离开Paused 状态。相反,activity仍然处于这个状态,直到activity恢复,或者对用户完全不可见。如果activity恢复,系统将再次调用onResume()回调。如果activity从Paused 状态返回到 Resumed 状态,系统会保持activity实例继续驻留在内存中,并在系统调用onResume()时恢复该activity实例。在这种情况下,您不需要重新初始化在 Resumed 状态之前的任何回调方法中创建的组件。如果activity完全不可见,系统将调用onStop()。

onStop()

当您的activity不再对用户可见时,它已经进入了 Stopped 状态,系统调用onStop()回调。例如,当新启动的activity覆盖整个屏幕时,可能会出现这种情况。当activity结束运行且将要终止时,系统也会调用onStop()。

在onStop()方法中,应用程序应该释放几乎所有用户不使用时不需要的资源。例如,如果您在onStart()中注册了一个广播接收器来监听可能会影响UI的更改,那么可以在onStop()中取消广播接收器的注册,因为用户不能再看到UI了。使用onStop()来释放可能会泄漏内存的资源也是很重要的,因为系统可能会在不调用activity的最终onDestroy()回调的情况下杀死宿主activity的进程。

您还应该使用onStop()来执行相对cpu密集型的关闭操作。例如,如果您找不到更合适的时间来将信息保存到数据库,那么您可以在onStop()期间这样做。下面的示例展示了onStop()的一个实现,它将草稿的内容保存到持久存储中:

@Override
protected void onStop() {
    // call the superclass method first
    super.onStop();

    // 保存note的当前草稿,因为activity停止了,我们想确保目前的记录进展不会丢失。
    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进入 Stopped 状态时,activity对象将驻留在内存中:它维护所有状态和成员信息,但不附加到窗口管理器。当activity恢复时,activity会恢复这些信息。您不需要重新初始化在 Resumed 状态之前的任何回调方法中创建的组件。系统还可以跟踪布局中每个 View 对象的当前状态,因此,如果用户将文本输入到EditText小部件中,那么该内容将被保留,因此您不需要保存和恢复它。

注意:一旦停止activity,如果系统需要回收内存的话,系统可能会销毁包含activity的进程。即使系统在activity停止时销毁了进程,系统仍然保留 View 对象的状态(比如EditText小部件中的文本),并将它们保存在一个 Bundle 中(一组键值对),并在用户返回到activity时重新恢复它们。

在 Stopped 状态下,activity要么返回与用户交互,要么activity结束运行并离开。如果activity返回,系统将调用onRestart()。如果activity已结束运行,系统将调用onDestroy()。

onDestroy()

在activity被销毁之前调用。这是activity接收到的最后一个调用。系统调用这个回调,要么因为activity是由于某人调用finish()而结束的,或者因为系统正在临时销毁包含activity的进程以节省空间。您可以使用isfinish()方法来区分这两个场景。当出现定向变化时,系统也会调用此方法,然后立即调用onCreate()来重新创建新方向的进程(以及它包含的组件)。

onDestroy()回调释放了以前的回调(如onStop())尚未释放的所有资源。

 

activity状态和内存溢出


 系统不会直接杀死一个activity。相反,它会杀死activity运行的进程,不仅销毁activity,还销毁进程中的其它东西。

 当需要释放RAM时,系统会杀死进程;它杀死一个给定进程的可能性取决于当时的进程状态。进程状态反过来取决于流程中运行的activity的状态。

用户还可以通过在Settings 下使用应用程序管理器来杀死相应的应用程序来杀死进程。
 
 

activity之间导航


  在应用程序的生命周期中,应用程序可能会多次进入并退出某个activity。例如,用户可以点击设备的Back按钮,或者activity可能需要启动不同的activity。本节将介绍如何实现成功的activity转换。这些主题包括从另一个activity开始activity、保存activity状态和恢复activity状态。

 从另一个activity开始一个activity

一个activity通常需要在某一时刻启动另一个activity。例如,当应用程序需要从当前屏幕移动到新屏幕时,就会出现这种情况。

取决于您的activity是否希望从即将开始的新activity中返回结果,您可以使用startActivity()或starticvityforresult()方法启动新的activity。在这两种情况下,都传递一个Intent对象。

Intent对象指定要启动的确切activity或描述要执行的操作类型(系统为您选择合适的activity,甚至可能来自不同的应用程序)。Intent对象也可以携带少量数据,以供被启动的activity使用。

startActivity()

如果新启动的activity不需要返回结果,那么当前activity可以通过调用startActivity()方法启动它。

在自己的应用程序中,您通常需要简单地启动一个已知的activity。例如,下面的代码片段展示了如何启动一个名为SignInActivity的activity。

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

 您的应用程序可能还需要执行一些操作,比如使用activity中的数据发送电子邮件、文本消息或状态更新。在这种情况下,应用程序可能没有自己的activity来执行这些操作,因此您可以利用设备上的其它应用程序提供的activity,这些应用程序可以为您执行操作。这就是intent真正有价值的地方:您可以创建一个intent来描述您想要执行的操作,系统从另一个应用程序启动适当的activity。如果有多个activity可以处理intent,那么用户可以选择使用哪个。例如,如果您希望允许用户发送电子邮件消息,您可以创建以下intent:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

 额外添加到intent的EXTRA_EMAIL是一个发送email的电子邮件地址的字符串数组。当电子邮件应用程序响应这个intent时,它会读取额外的字符串数组,并将它们放在电子邮件组合表单的“to”字段中。在这种情况下,电子邮件应用程序的activity启动,用户完成后,您的activity将恢复。

startActivityForResult()

有时,当activity结束时,您希望从activity中得到结果。例如,您可能启动一个activity,让用户在联系人列表中选择一个联系人;当它结束时,返回被选中的人。要做到这一点,调用starticvityforresult (Intent, int)方法,其中integer参数标识调用。该标识符用于从相同的activity中消除对starticvityforresult (Intent, int)的多个调用之间的歧义。它不是全局标识符,不存在与其它应用程序或activity相冲突的风险。结果通过onActivityResult(int, int, Intent) 方法返回。

当子activity退出时,它可以调用setResult(int)将数据返回给其父activity。子activity必须始终提供一个结果代码,它可以是标准结果 RESULT_CANCELEDRESULT_OK,或以RESULT_FIRST_USER开始的任何自定义值。此外,子activity可以选择返回包含它想要的任何附加数据的Intent对象。父activity使用onActivityResult(int、int、Intent)方法,以及最初提供的父activity的整数标识符来接收信息。

如果某个子activity因任何原因而失败,例如崩溃,父activity收到的结果带有代码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));
             }
         }
     }
 }

 协调activity

当一个activity启动另一个activity时,它们都会经历生命周期的转换。第一个activity停止操作,进入 Paused 或 Stopped 状态,而另一个activity被创建。如果这些activity共享保存到磁盘或其它地方的数据,第一个activity在第二个activity被创建之前并没有完全停止,理解这点很重要。相反,启动第二个的进度与停止第一个的进度重叠。

生命周期回调的顺序是很明确的,特别是当两个activity在同一个进程(应用程序)和一个启动另一个的时候。以下是activityA开始activity B时发生的操作顺序:

  1. activityA的onPause()方法执行。
  2. activityB的onCreate()、onStart()和onResume()方法依次执行。(activityB现在有用户焦点。)
  3. 然后,如果activityA在屏幕上不再可见,它的onStop()方法将执行。

这个可预测的生命周期回调序列允许您管理从一个activity到另一个activity的信息转换。

保存和恢复activity状态

在一些场景中,由于正常的应用程序行为,您的activity会被销毁,比如当用户按下后退按钮或您的activity通过调用finish()方法来表示自己的销毁时。如果activity处于 Stopped 状态,并且在很长一段时间内没有使用,或者前台activity需要更多的资源,系统也可能销毁包含您的activity的进程,以回收内存。

当您的activity由于用户按下后退按钮或activity调用finish()方法结束自己被销毁时,系统的activity实例将永远消失,因为行为表明不再需要此activity。然而,如果系统由于系统约束(而不是正常程序行为)销毁activity,那么尽管实际的activity实例已失,系统仍然记得它存在,这样如果用户导航回它,系统使用描述activity状态被销毁时的一组保存数据创建一个新的activity实例。系统用来恢复先前状态的保存数据称为 instance state,是存储在Bundle对象中的键值对集合。

默认情况下,系统使用Bundle实例状态来保存activity布局中每个 View 对象的信息(例如输入EditText小部件的文本值)。因此,如果您的activity实例被销毁并重新创建,那么布局的状态将恢复到它的之前状态,并且您不需要写代码来实现。但是,您的activity可能会有更多您想要恢复的状态信息,比如跟踪用户在activity中的进程的成员变量。

Bundle对象只适合保存小数量的数据,因为它消耗系统进程内存。为了保存更多的数据,您应该采用组合的方法来保存数据,使用持久的本地存储、onSaveInstanceState()方法和ViewModel类。

如果Bundle是合适的,您可以使用onSaveInstanceState()方法。

保存activity状态

当您的activity开始停止时,系统将调用onSaveInstanceState()方法,这样您的activity就可以通过一系列键值对保存状态信息。该方法的默认实现保存了关于activity的视图层次结构的状态瞬态信息,例如EditText小部件中的文本或ListView小部件的滚动位置。应用程序应该在onPause()方法之后和onStop()之前实现onSaveInstanceState()回调。不要在onPause()中实现这个回调。

警告:您必须始终调用onSaveInstanceState()的超类实现,因此默认实现可以保存视图层次结构的状态。

为了为您的activity保存更多的状态信息,您必须重写onSaveInstanceState(),并将键值对添加到在您的activity被意外销毁事件中保存的Bundle对象。例如:

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);
}

 注意:为了让Android系统恢复activity中的view 状态,每个view 必须有一个唯一的ID,这个ID是由Android提供 的android:id属性。

为了保存持久数据,例如用户首选项或数据库的数据,当您的activity处于前台时,您应该抓住适当的机会。如果没有出现这样的机会,您应该在onStop()方法中保存这些数据。

恢复activity状态

当您的activity在先前被销毁后重新创建时,您可以从系统传递给您的activity的 Bundle 中恢复您保存的状态。onCreate()和onRestoreInstanceState()回调方法都接收包含实例状态信息的同一个Bundle 。

因为onCreate()方法在系统创建activity的新实例或重新创建前一个实例时都会被调用,所以在尝试读取它之前,必须检查状态 Bundle 是否为空。如果它是空的,那么系统将创建activity的新实例,而不是恢复先前被销毁的activity实例。

例如,下面的代码片段展示了如何在onCreate()中恢复一些状态数据:

@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(),而不是在onCreate()中恢复状态,系统在onStart()方法之后调用它。系统只有在有保存的状态可恢复时才调用onRestoreInstanceState(),所以您不需要检查这个 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()的超类实现,这样默认实现可以恢复视图层次结构的状态。

 

posted @ 2018-03-15 11:57  caroline2016  阅读(216)  评论(1编辑  收藏  举报