lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Обработка изменений в режиме выполнения

Некоторые конфигурации устройств могут изменяться в режиме выполнения (например, ориентация экрана, доступность клавиатуры и язык). Когда происходит такое изменение, Android перезапускает выполнение Activity (вызывается onDestroy(), затем onCreate()). Поведение при перезапуске позволяет приложению учитывать новые конфигурации путем автоматической перезагрузки в приложение альтернативных ресурсов, которые соответствуют новой конфигурации устройства.

Для правильной обработки перезапуска важно, чтобы операция восстанавливала предыдущее состояние в течение нормального жизненного цикла операции, при котором Android вызывает onSaveInstanceState() перед уничтожением операции, чтобы вы могли сохранить данные о состоянии приложения. Затем вы можете восстановить состояние во время выполнения метода onCreate() или onRestoreInstanceState().

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

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

  1. Сохранение объекта во время изменения конфигурации

    Необходимо позволить перезапуск операции при изменении конфигурации, при этом перенести объект с сохранением состояния в новый экземпляр операции.

  2. Самостоятельная обработка изменения конфигурации

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

Сохранение объекта во время изменения конфигурации

Если перезапуск операции требует восстановления больших объемов данных, повторного подключения к сети или выполнения других масштабных действий, то полный перезапуск вследствие изменения конфигурации может тормозить работу пользователя. Кроме того, может быть невозможно полное восстановление состояния операции из объекта Bundle, который система сохраняет с помощью обратного вызова onSaveInstanceState(), поскольку он не предназначен для переноса больших объектов (таких как растровые изображения), и данные в нем должны быть преобразованы в последовательную форму, а затем обратно, что потребляет много памяти и тормозит изменение конфигурации. В такой ситуации вы можете облегчить бремя повторной инициализации операции путем сохранения фрагмента Fragment в случае перезапуска операции вследствие изменения конфигурации. Этот фрагмент может содержать ссылки на объекты с сохранением состояния, которые требуется сохранить.

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

Сохранение объектов с сохранением состояния во фрагменте во время изменения конфигурации в режиме выполнения:

  1. Расширьте класс Fragment и объявите ссылки на объекты, сохраняющие состояние.
  2. Вызовите setRetainInstance(boolean), когда фрагмент создан.
  3. Добавьте фрагмент в операцию.
  4. Используйте FragmentManager для извлечения фрагмента при перезапуске операции.

В качестве привмера приведено определение фрагмента следующим образом:

public class RetainedFragment extends Fragment {

    // data object we want to retain
    private MyDataObject data;

    // this method is only called once for this fragment
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setData(MyDataObject data) {
        this.data = data;
    }

    public MyDataObject getData() {
        return data;
    }
}

Внимание! Хотя можно сохранить любой объект, не следует передавать объект, связанный с Activity, например, Drawable, Adapter, View или любой другой объект, связанный с Context. В этом случае произойдет утечка всех видов и ресурсов исходного экземпляра операции. (Утечка ресурсов означает, что приложение удерживает их, и система не может очистить от них память, поэтому может теряться значительный объем памяти).

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

public class MyActivity extends Activity {

    private RetainedFragment dataFragment;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);

        // create the fragment and data the first time
        if (dataFragment == null) {
            // add the fragment
            dataFragment = new DataFragment();
            fm.beginTransaction().add(dataFragment, “data”).commit();
            // load the data from the web
            dataFragment.setData(loadMyData());
        }

        // the data is available in dataFragment.getData()
        ...
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // store the data in the fragment
        dataFragment.setData(collectMyLoadedData());
    }
}

В этом примере onCreate() добавляет фрагмент или восстанавливает ссылку на него. Метод onCreate() также хранит объект, сохраняющий состояние, внутри экземпляра фрагмента. Метод onDestroy() обновляет объект, сохраняющий состояние, внутри сохраненного экземпляра фрагмента.

Самостоятельная обработка изменения конфигурации

Если приложению не требуется обновление ресурсов во время определенного изменения конфигурации и имеются ограничения производительности, не разрешающие перезапуск операции, можно объявить самостоятельную обработку операцией изменения конфигурации , что запрещает системе перезапускать эту операцию.

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

Для объявления самостоятельной обработки изменения конфигурации операцией отредактируйте соответствующий элемент <activity> в файле манифеста и включите в него атрибут android:configChanges со значением, представляющим конфигурацию, которую требуется обрабатывать самостоятельно. Возможные значения перечислены в документации для атрибута android:configChanges (наиболее часто используются значения "orientation" для предотвращения перезапуска при изменении ориентации экрана и "keyboardHidden" для предотвращения перезапуска при изменении доступности клавиатуры). Можно объявить несколько значений конфигурации в атрибуте, разделяя их символом вертикальной черты |.

Например, следующий код манифеста объявляет операцию, которая обрабатывает как изменение ориентации экрана, так и изменение доступности экрана:

<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">

Теперь, при изменении одного из этих элементов конфигурации, операция MyActivity не перезапускается. Вместо этого MyActivity получает вызов onConfigurationChanged(). Этот метод получает объект Configuration, в котором указана новая конфигурация устройства. При считываении полей Configuration можно определить новую конфигурацию и внести соответствующие изменения, обновляя ресурсы, которые используются в интерфейсе. В момент вызова этого метода объект Resources операции обновляется и возвращает ресурсы на основе новой конфигурации, поэтому можно просто сбросить элементы пользовательского интерфейса без перезапуска операции системой.

Внимание! Начиная с Android 3.2 (уровень API 13), «размер экрана» также изменяется при переходе между книжной и альбомной ориентациями устройства. Таким образом, если в режиме выполнения требуется предотвратить перезапуск вследствие изменения ориентации при разработке для уровня API 13 или выше (как объявлено в атрибутах minSdkVersion и targetSdkVersion ), необходимо включить значение "screenSize" в дополнение к значению "orientation". То есть необходимо объявить android:configChanges="orientation|screenSize". Однако, если приложение нацелено на уровень API 12 или ниже, ваша операция всегда обрабатывает это изменение конфигурации самостоятельно (это изменение конфигурации не перезапускает операцию даже при работе на устройстве с Android 3.2 или более поздней версией).

Например, следующая реализация метода onConfigurationChanged() проверяет текущую ориентацию устройства:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

Объект Configuration представляет все текущие конфигурации, а не только изменившиеся. В большинстве случаев неважно, как именно изменилась конфигурация, поэтому можно просто переназначить все ресурсы, предоставляющие альтернативу для обрабатываемой конфигурации. Например, так как объект Resources обновлен, можно сбросить любые виды ImageView с помощью setImageResource() , и после этого будут использоваться ресурсы, соответствующие новой конфигурации (как описано в разделе Предоставление ресурсов).

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

Помните! При объявлении операции, обрабатывающей изменение конфигурации, вы отвечаете за сброс любых элементов, для которых вы предоставили альтернативы. Если вы объявили, что операция обрабатывает изменение ориентации, и у вас есть изображения, которые должны быть заменены при переходе между альбомной и книжной ориентацией, вы должны переназначить каждый ресурс каждому элементу во время выполнения метода onConfigurationChanged().

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

Более подробную информацию об изменениях конфигурации, которые можно обрабатывать внутри операции, см. в документации android:configChanges и описании класса Configuration.