Поделиться через


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

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

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

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

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

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

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

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

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

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

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

Состояния активности

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

Схема состояний действий

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

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

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

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

  4. Перезапущено — Возможно, активность, находящуюся в состоянии от приостановки до остановки в жизненном цикле, удалить из памяти Android. Если пользователь возвращается к активности, её необходимо перезапустить, восстановить в её ранее сохраненное состояние и затем отобразить для пользователя.

Действие Re-Creation в ответ на изменения конфигурации

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

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

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

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

Блок-схема жизненного цикла действий

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

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

OnCreate

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

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

OnCreate принимает параметр Bundle, который является словарём для хранения и передачи информации о состоянии и объектов между активностями. Если пакет не равен 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 вызывается, когда система собиралась поместить действие в фон или когда действие становится частично скрытным. Действия должны переопределить этот метод, если в этом есть необходимость.

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

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

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

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

  • Аналогичным образом, если действие отображало любые диалоговые окна или оповещения, их необходимо очистить с помощью метода .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

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

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

OnRestart

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

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

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

Назад / Главная

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

Кнопки

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

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

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

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

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

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

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

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

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

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

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

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

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

Блок-схема состояния пакета

OnSaveInstanceState

OnSaveInstanceState будет вызываться, так как действие останавливается. Он получит параметр пакета, в который действие может хранить свое состояние. При изменении конфигурации Activity может использовать объект Bundle, переданный для сохранения состояния Activity путем переопределения 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, как показано на рисунке ниже:

Отображение не отображает предыдущее значение

Чтобы сохранить значение 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 назначен, когда пользователь вводит некоторые данные и поворачивает устройство, данные по-прежнему отображаются, как показано ниже:

Данные сохраняются в альбомном режиме

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 см. в пошаговом руководстве — сохранение состояния действия.

Ограничения пакета

Хотя 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, анализирует их, а затем представляет результаты в списке, как показано на следующем снимке экрана:

Результаты, отображаемые на экране

При изменении конфигурации , например, при повороте устройства код повторяет процесс. Чтобы повторно использовать исходные результаты и не вызывать ненужные, избыточные сетевые вызовы, можно использовать 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 предоставляет мощную платформу для управления состояниями действий в приложении, но это может быть сложно понять и реализовать. В этой главе появились различные состояния, которые действие может пройти в течение своего существования, а также методы жизненного цикла, связанные с этими состояниями. Далее было предоставлено руководство по тому, какой тип логики должен выполняться в каждом из этих методов.