Жизненный цикл действия

Действия — это фундаментальный стандартный блок приложений Android, и они могут существовать в нескольких различных состояниях. Жизненный цикл действия начинается с создания экземпляра и заканчивается разрушением и включает в себя множество состояний между ними. При изменении состояния действия вызывается соответствующий метод события жизненного цикла, уведомляющий о действии предстоящего изменения состояния и позволяя ему выполнять код для адаптации к этим изменениям. В этой статье рассматривается жизненный цикл действий и объясняется ответственность за то, что действие во время каждого из этих изменений состояния является частью хорошо себя и надежного приложения.

Обзор жизненного цикла действий

Действия — это необычная концепция программирования, относясь к Android. В традиционной разработке приложений обычно используется статический основной метод, который выполняется для запуска приложения. Однако с Android все отличается; Приложения Android можно запускать с помощью любого зарегистрированного действия в приложении. На практике большинство приложений будут иметь только определенное действие, указанное в качестве точки входа приложения. Однако если приложение завершает работу или завершается операционной системой, ОС может попытаться перезапустить приложение в последнем открытом действии или в любом другом месте в стеке предыдущих действий. Кроме того, ОПЕРАЦИОННая система может приостановить действия, если они не активны, и восстановить их, если оно низко в памяти. Необходимо тщательно рассмотреть возможность правильного восстановления состояния приложения в случае перезапуска действия, особенно если это действие зависит от данных от предыдущих действий.

Жизненный цикл действий реализуется в виде коллекции методов вызовов ОС на протяжении всего жизненного цикла действия. Эти методы позволяют разработчикам реализовать функциональные возможности, необходимые для удовлетворения требований к управлению состоянием и ресурсами своих приложений.

Разработчику приложений крайне важно проанализировать требования каждого действия, чтобы определить, какие методы, предоставляемые жизненным циклом действий, необходимо реализовать. Сбой этого может привести к нестабильности приложений, сбоям, blat ресурсов и, возможно, даже базовой нестабильности ОС.

В этой главе подробно рассматриваются жизненный цикл действий, включая:

  • Состояния действий
  • Методы жизненного цикла
  • Сохранение состояния приложения

В этом разделе также содержится пошаговое руководство , включающее практические примеры того, как эффективно сохранять состояние во время жизненного цикла действия. К концу этой главы вы должны понять жизненный цикл действий и как его поддерживать в приложении Android.

Жизненный цикл действия

Жизненный цикл действий Android состоит из коллекции методов, предоставляемых в классе Activity, который предоставляет разработчику платформу управления ресурсами. Эта платформа позволяет разработчикам соответствовать уникальным требованиям к управлению состоянием каждого действия в приложении и правильно обрабатывать управление ресурсами.

Состояния действий

Ос Android выполняет арбитраж действий на основе их состояния. Это помогает Android определять действия, которые больше не используются, позволяя ОС освободить память и ресурсы. На следующей схеме показаны состояния действия, которые могут пройти в течение своего существования:

Activity states diagram

Эти состояния можно разбить на 4 основные группы следующим образом:

  1. Активные или выполняемые действия считаются активными или запущенными, если они находятся на переднем плане, также называемой верхней частью стека действий. Это считается самым приоритетным действием в Android, и такое действие будет убито только операционной системой в чрезвычайных ситуациях, например, если действие пытается использовать больше памяти, чем доступно на устройстве, так как это может привести к тому, что пользовательский интерфейс не отвечает.

  2. Приостановлено . Когда устройство переходит в спящий режим, или действие по-прежнему отображается, но частично скрыто новым, не полноразмерным или прозрачным действием, действие считается приостановленным. Приостановленные действия по-прежнему живы, т. е. поддерживают все сведения о состоянии и члене и остаются присоединенными к диспетчеру окон. Это считается вторым наиболее приоритетным действием в Android и, таким образом, будет убито только операционной системой, если это действие будет соответствовать требованиям к ресурсам, необходимым для обеспечения стабильной и быстродействующей активности.

  3. Остановленная или фоновая — действия, которые полностью затмечены другим действием, считаются остановленными или в фоновом режиме. Остановленные действия по-прежнему пытаются сохранить их состояние и сведения о членах дольше, но остановленные действия считаются самым низким приоритетом трех состояний, и, таким образом, ОС убьет действия в этом состоянии сначала для удовлетворения требований к ресурсам более высокого приоритета действий.

  4. Перезапущено . Действие, которое в любом месте приостановлено, чтобы остановиться в жизненном цикле, можно удалить из памяти Android. Если пользователь возвращает действие, которое необходимо перезапустить, восстановить его ранее сохраненное состояние, а затем отобразить пользователю.

Повторное создание действия в ответ на изменения конфигурации

Чтобы сделать все более сложными, Android создает еще один ключ в сочетании с именем "Изменения конфигурации". Изменения конфигурации — это циклы быстрого уничтожения или повторного создания, возникающие при изменении конфигурации действия, например при повороте устройства (и действие должно быть повторно создано в альбомном или книжном режиме), при отображении клавиатуры (а действие представляется возможностью изменить размер себя) или когда устройство помещается в док-станцию, среди прочего.

Изменения конфигурации по-прежнему вызывают те же изменения состояния активности, которые будут возникать во время остановки и перезапуска действия. Тем не менее, чтобы убедиться, что приложение чувствует себя быстро и хорошо работает во время изменений конфигурации, важно, чтобы они обрабатывались как можно быстрее. Из-за этого Android имеет определенный API, который можно использовать для сохранения состояния во время изменений конфигурации. Далее мы рассмотрим это в разделе "Управление состоянием на протяжении всего жизненного цикла ".

Методы жизненного цикла действий

Пакет SDK для Android и платформа Xamarin.Android предоставляют мощную модель для управления состоянием действий в приложении. При изменении состояния действия действие уведомляется ОПЕРАЦИОННОй системой, которая вызывает определенные методы для этого действия. На следующей схеме показаны эти методы в отношении жизненного цикла действий:

Activity Lifecycle flowchart

Разработчик может обрабатывать изменения состояния, переопределяя эти методы в действии. Однако важно отметить, что все методы жизненного цикла вызываются в потоке пользовательского интерфейса и блокируют выполнение операционной системы следующей части работы пользовательского интерфейса, например скрытие текущего действия, отображение нового действия и т. д. Таким образом, код в этих методах должен быть максимально кратким, чтобы приложение чувствовало себя хорошо. Все длительные задачи должны выполняться в фоновом потоке.

Рассмотрим каждый из этих методов жизненного цикла и их использование:

OnCreate

OnCreate — это первый метод, который необходимо вызвать при создании действия. OnCreate всегда переопределяется для выполнения любых инициализаций запуска, которые могут потребоваться действием, например:

  • Создание представлений
  • Инициализация переменных
  • Привязка статических данных к спискам

OnCreateпринимает параметр пакета, который является словарем для хранения и передачи сведений о состоянии и объектов между действиями, если пакет не имеет значения NULL, это означает, что действие перезапускается и должно восстановить его состояние из предыдущего экземпляра. В следующем коде показано, как получить значения из пакета:

protected override void OnCreate(Bundle bundle)
{
   base.OnCreate(bundle);

   string intentString;
   bool intentBool;

   if (bundle != null)
   {
      intentString = bundle.GetString("myString");
      intentBool = bundle.GetBoolean("myBool");
   }

   // Set our view from the "main" layout resource
   SetContentView(Resource.Layout.Main);
}

Завершив OnCreate работу, Android вызовет OnStart.

OnStart

OnStart всегда вызывается системой после OnCreate завершения. Действия могут переопределить этот метод, если они должны выполнять какие-либо определенные задачи прямо перед тем, как действие становится видимым, например обновление текущих значений представлений в действии. Android будет вызываться OnResume сразу после этого метода.

OnResume

Система вызывает OnResume , когда действие готово к взаимодействию с пользователем. Действия должны переопределить этот метод для выполнения таких задач, как:

  • Увеличение частоты кадров (общая задача в разработке игр)
  • Запуск анимаций
  • Прослушивание обновлений GPS
  • Отображение любых соответствующих оповещений или диалоговых окон
  • Проводка внешних обработчиков событий

Например, в следующем фрагменте кода показано, как инициализировать камеру:

protected override void OnResume()
{
    base.OnResume(); // Always call the superclass first.

    if (_camera==null)
    {
        // Do camera initializations here
    }
}

OnResume важно, так как любая операция, выполняемая в OnPause ней, должна быть не выполнена OnResume, так как это единственный метод жизненного цикла, который гарантированно выполняется после OnPause возвращения действия в жизнь.

OnPause

OnPause вызывается, когда система собиралась поместить действие в фон или когда действие становится частично скрытным. Действия должны переопределить этот метод, если они должны:

  • Фиксация несохраненных изменений в постоянных данных

  • Уничтожение или очистка других объектов, потребляющих ресурсы

  • Снижение частоты кадров и приостановка анимаций

  • Отмена регистрации внешних обработчиков событий или обработчиков уведомлений (т. е. тех, которые привязаны к службе). Это необходимо сделать, чтобы предотвратить утечку памяти активности.

  • Аналогичным образом, если действие отображало любые диалоговые окна или оповещения, их необходимо очистить с .Dismiss() помощью метода.

В качестве примера следующий фрагмент кода выпустит камеру, так как действие не может использовать его при приостановке:

protected override void OnPause()
{
    base.OnPause(); // Always call the superclass first

    // Release the camera as other activities might need it
    if (_camera != null)
    {
        _camera.Release();
        _camera = null;
    }
}

Существует два возможных метода жизненного цикла, которые будут вызываться после OnPause:

  1. OnResume вызывается, если действие должно быть возвращено на передний план.
  2. OnStop вызывается, если действие помещается в фоновом режиме.

OnStop

OnStop вызывается, когда действие больше не отображается пользователю. Это происходит, когда происходит одно из следующих действий:

  • Запускается новое действие и охватывает это действие.
  • Существующее действие выполняется на переднем плане.
  • Действие уничтожается.

OnStop Не всегда может вызываться в ситуациях с низкой памятью, например, когда Android голодает для ресурсов и не может должным образом фонировать действие. По этой причине рекомендуется не полагаться на OnStop вызов при подготовке действия к уничтожению. Следующие методы жизненного цикла, которые могут вызываться после этого, будут ли OnDestroy действия уходят, или OnRestart если действие возвращается для взаимодействия с пользователем.

OnDesk

OnDeties — это окончательный метод, который вызывается для экземпляра действия перед его уничтожением и полностью удален из памяти. В экстремальных ситуациях Android может убить процесс приложения, в котором размещается действие, что приведет к OnDestroy тому, что не вызывается. Большинство действий не реализуют этот метод, так как большинство из них выполняют очистку и завершение работы в OnPause и OnStop методах. Метод OnDestroy обычно переопределяется для очистки длительных задач, которые могут утечки ресурсов. Примером этого могут быть фоновые потоки, которые были запущены в OnCreate.

Методы жизненного цикла не будут вызываться после того, как действие было уничтожено.

OnRestart

OnRestart вызывается после остановки действия до его повторного запуска. Хорошим примером этого будет то, когда пользователь нажимает кнопку "Главная" во время действия в приложении. Когда это происходит OnPause , а затем OnStop вызываются методы, а действие перемещается в фон, но не уничтожается. Если пользователь затем восстановит приложение с помощью диспетчера задач или аналогичного приложения, Android вызовет OnRestart метод действия.

Общие рекомендации по реализации логики OnRestartв ней отсутствуют. Это связано с тем, что OnStart всегда вызывается независимо от того, создается или перезапускается действие, поэтому все ресурсы, необходимые для действия, должны быть инициализированы в OnStart, а не OnRestart.

Следующий метод жизненного цикла, который будет вызываться после OnRestart этого OnStart.

Назад и главная

Многие устройства Android имеют две разные кнопки: кнопку "Назад" и кнопку "Главная". Пример этого можно увидеть на следующем снимке экрана Android 4.0.3:

Back and Home buttons

Существует тонкое различие между двумя кнопками, несмотря на то, что они, как представляется, имеют одинаковый эффект размещения приложения в фоновом режиме. Когда пользователь нажимает кнопку "Назад", он сообщает Android, что они выполняются с действием. Android уничтожит действие. В отличие от этого, когда пользователь нажимает кнопку "Главная", действие просто помещается в фон. Android не убьет действие.

Управление состоянием на протяжении всего жизненного цикла

Если действие остановлено или уничтожено, система предоставляет возможность сохранить состояние действия для последующего восстановления. Это сохраненное состояние называется состоянием экземпляра. Android предоставляет три варианта хранения состояния экземпляра во время жизненного цикла действия:

  1. Хранение примитивных значений в известном пакете Dictionary, который Android будет использовать для сохранения состояния.

  2. Создание пользовательского класса, который будет содержать сложные значения, такие как растровые изображения. Android будет использовать этот пользовательский класс для сохранения состояния.

  3. Обход жизненного цикла изменения конфигурации и выполнение полной ответственности за поддержание состояния в действии.

В этом руководстве рассматриваются первые два варианта.

Состояние пакета

Основным вариантом сохранения состояния экземпляра является использование объекта словаря ключей и значений, известного как пакет. Помните, что при создании OnCreate действия метод передает пакет в качестве параметра, этот пакет можно использовать для восстановления состояния экземпляра. Не рекомендуется использовать пакет для более сложных данных, которые не будут быстро или легко сериализовать пары "ключ-значение" (например, растровые изображения); скорее, его следует использовать для простых значений, таких как строки.

Действие предоставляет методы для сохранения и получения состояния экземпляра в пакете:

  • OnSaveInstanceState — это вызывается Android при уничтожении действия. Действия могут реализовать этот метод, если им нужно сохранить элементы состояния ключа или значения.

  • OnRestoreInstanceState — вызывается после OnCreate завершения метода и предоставляет еще одну возможность для действия восстановить состояние после завершения инициализации.

На следующей схеме показано, как используются эти методы:

Bundle states flowchart

OnSaveInstanceState

OnSaveInstanceState будет вызываться, так как действие останавливается. Он получит параметр пакета, в который действие может хранить свое состояние. При изменении конфигурации устройство может использовать Bundle объект, переданный для сохранения состояния действия путем переопределения OnSaveInstanceState. Рассмотрим следующий пример кода:

int c;

protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);

  this.SetContentView (Resource.Layout.SimpleStateView);

  var output = this.FindViewById<TextView> (Resource.Id.outputText);

  if (bundle != null) {
    c = bundle.GetInt ("counter", -1);
  } else {
    c = -1;
  }

  output.Text = c.ToString ();

  var incrementCounter = this.FindViewById<Button> (Resource.Id.incrementCounter);

  incrementCounter.Click += (s,e) => {
    output.Text = (++c).ToString();
  };
}

Приведенный выше код увеличивает целое число с именем c при нажатии кнопки incrementCounter , отображающей результат в именованном TextView виде output. Когда происходит изменение конфигурации, например при повороте устройства, приведенный выше код потеряет значение c из-за того, что bundle это будет null, как показано на рисунке ниже:

Display does not show previous value

Чтобы сохранить значение c в этом примере, действие может переопределить OnSaveInstanceState, сохранив значение в пакете, как показано ниже:

protected override void OnSaveInstanceState (Bundle outState)
{
  outState.PutInt ("counter", c);
  base.OnSaveInstanceState (outState);
}

Теперь, когда устройство поворачивается на новую ориентацию, целое число сохраняется в пакете и извлекается с помощью строки:

c = bundle.GetInt ("counter", -1);

Примечание.

Важно всегда вызывать базовую реализацию OnSaveInstanceState , чтобы можно было сохранить состояние иерархии представлений.

Просмотр состояния

Переопределение OnSaveInstanceState — это подходящий механизм для сохранения временных данных в действии в изменениях ориентации, таких как счетчик в приведенном выше примере. Однако реализация OnSaveInstanceState по умолчанию будет заботиться о сохранении временных данных в пользовательском интерфейсе для каждого представления, пока каждое представление имеет идентификатор. Например, предположим, что у приложения есть элемент, определенный EditText в XML, как показано ниже.

<EditText android:id="@+id/myText"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"/>

EditText Так как элемент управления id назначен, когда пользователь вводит некоторые данные и поворачивает устройство, данные по-прежнему отображаются, как показано ниже:

Data is preserved in landscape mode

OnRestoreInstanceState

OnRestoreInstanceState будет вызываться после OnStart. Он предоставляет возможность восстановить любое состояние, которое ранее было сохранено в пакете во время предыдущего OnSaveInstanceState. Однако это тот же пакет, который предоставляется OnCreate.

В следующем коде показано, как можно восстановить состояние в OnRestoreInstanceState:

protected override void OnRestoreInstanceState(Bundle savedState)
{
    base.OnRestoreInstanceState(savedState);
    var myString = savedState.GetString("myString");
    var myBool = savedState.GetBoolean("myBool");
}

Этот метод существует для обеспечения некоторой гибкости при восстановлении состояния. Иногда лучше ждать завершения всех инициализаций перед восстановлением состояния экземпляра. Кроме того, подкласс существующего действия может потребовать только восстановления определенных значений из состояния экземпляра. Во многих случаях переопределить OnRestoreInstanceStateне нужно, так как большинство действий может восстановить состояние с помощью предоставленного OnCreateпакета.

Пример сохранения состояния с помощью инструкции Bundleсм. в пошаговом руководстве. Сохранение состояния действия.

Ограничения при использовании Bundle

Хотя OnSaveInstanceState это упрощает сохранение временных данных, у него есть некоторые ограничения:

  • Он не вызывается во всех случаях. Например, нажатие клавиши Home или Back для выхода из действия не приведет к OnSaveInstanceState вызову.

  • Передаваемый пакет OnSaveInstanceState не предназначен для больших объектов, таких как изображения. В случае больших объектов сохранение объекта из OnRetainNonConfigurationInstance предпочтительнее, как описано ниже.

  • Данные, сохраненные с помощью пакета, сериализуются, что может привести к задержкам.

Состояние пакета полезно для простых данных, которые не используют много памяти, в то время как данные экземпляра, отличные от конфигурации, полезны для более сложных данных или данных, которые являются дорогостоящими для извлечения, например из вызова веб-службы или сложного запроса к базе данных. При необходимости данные экземпляра экземпляра, отличные от конфигурации, сохраняются в объекте. В следующем разделе представлен OnRetainNonConfigurationInstance способ сохранения более сложных типов данных с помощью изменений конфигурации.

Сохранение сложных данных

Помимо сохранения данных в пакете, Android также поддерживает сохранение данных путем переопределения OnRetainNonConfigurationInstance и возврата экземпляра, Java.Lang.Object содержащего данные для сохранения. Существует два основных преимущества использования OnRetainNonConfigurationInstance для сохранения состояния:

  • Объект, возвращаемый из OnRetainNonConfigurationInstance объекта, хорошо работает с более крупными, более сложными типами данных, так как память сохраняет этот объект.

  • Метод OnRetainNonConfigurationInstance вызывается по запросу и только при необходимости. Это более экономично, чем использование кэша вручную.

Использование OnRetainNonConfigurationInstance подходит для сценариев, в которых требуется много затрат на получение данных несколько раз, например в вызовах веб-службы. Например, рассмотрим следующий код, который выполняет поиск в Twitter:

public class NonConfigInstanceActivity : ListActivity
{
  protected override void OnCreate (Bundle bundle)
  {
    base.OnCreate (bundle);
    SearchTwitter ("xamarin");
  }

  public void SearchTwitter (string text)
  {
    string searchUrl = String.Format("http://search.twitter.com/search.json?" + "q={0}&rpp=10&include_entities=false&" + "result_type=mixed", text);

    var httpReq = (HttpWebRequest)HttpWebRequest.Create (new Uri (searchUrl));
    httpReq.BeginGetResponse (new AsyncCallback (ResponseCallback), httpReq);
  }

  void ResponseCallback (IAsyncResult ar)
  {
    var httpReq = (HttpWebRequest)ar.AsyncState;

    using (var httpRes = (HttpWebResponse)httpReq.EndGetResponse (ar)) {
      ParseResults (httpRes);
    }
  }

  void ParseResults (HttpWebResponse httpRes)
  {
    var s = httpRes.GetResponseStream ();
    var j = (JsonObject)JsonObject.Load (s);

    var results = (from result in (JsonArray)j ["results"] let jResult = result as JsonObject select jResult ["text"].ToString ()).ToArray ();

    RunOnUiThread (() => {
      PopulateTweetList (results);
    });
  }

  void PopulateTweetList (string[] results)
  {
    ListAdapter = new ArrayAdapter<string> (this, Resource.Layout.ItemView, results);
  }
}

Этот код извлекает результаты из веб-формата в формате JSON, анализирует их, а затем представляет результаты в списке, как показано на следующем снимке экрана:

Results displayed on screen

При изменении конфигурации , например, при повороте устройства код повторяет процесс. Чтобы повторно использовать исходные результаты и не вызывать ненужные, избыточные сетевые вызовы, можно использовать OnRetainNonconfigurationInstance для сохранения результатов, как показано ниже:

public class NonConfigInstanceActivity : ListActivity
{
  TweetListWrapper _savedInstance;

  protected override void OnCreate (Bundle bundle)
  {
    base.OnCreate (bundle);

    var tweetsWrapper = LastNonConfigurationInstance as TweetListWrapper;

    if (tweetsWrapper != null) {
      PopulateTweetList (tweetsWrapper.Tweets);
    } else {
      SearchTwitter ("xamarin");
    }

    public override Java.Lang.Object OnRetainNonConfigurationInstance ()
    {
      base.OnRetainNonConfigurationInstance ();
      return _savedInstance;
    }

    ...

    void PopulateTweetList (string[] results)
    {
      ListAdapter = new ArrayAdapter<string> (this, Resource.Layout.ItemView, results);
      _savedInstance = new TweetListWrapper{Tweets=results};
    }
}

Теперь, когда устройство поворачивается, исходные результаты извлекаются из LastNonConfiguartionInstance свойства. В этом примере результаты состоят из string[] содержащих твитов. Так как OnRetainNonConfigurationInstance требуется Java.Lang.Object возвращать объект, он string[] упаковывается в класс, который подклассы Java.Lang.Object, как показано ниже:

class TweetListWrapper : Java.Lang.Object
{
  public string[] Tweets { get; set; }
}

Например, попытка использовать TextView объект в качестве объекта, возвращаемого от OnRetainNonConfigurationInstance утечки действия, как показано в приведенном ниже коде:

TextView _textView;

protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);

  var tv = LastNonConfigurationInstance as TextViewWrapper;

  if(tv != null) {
    _textView = tv;
    var parent = _textView.Parent as FrameLayout;
    parent.RemoveView(_textView);
  } else {
    _textView = new TextView (this);
    _textView.Text = "This will leak.";
  }

  SetContentView (_textView);
}

public override Java.Lang.Object OnRetainNonConfigurationInstance ()
{
  base.OnRetainNonConfigurationInstance ();
  return _textView;
}

В этом разделе мы узнали, как сохранить простые данные состояния с помощью Bundleи сохранить более сложные типы данных с OnRetainNonConfigurationInstanceпомощью .

Итоги

Жизненный цикл действий Android предоставляет мощную платформу для управления состояниями действий в приложении, но это может быть сложно понять и реализовать. В этой главе появились различные состояния, которые действие может пройти в течение своего существования, а также методы жизненного цикла, связанные с этими состояниями. Далее было предоставлено руководство по тому, какой тип логики должен выполняться в каждом из этих методов.