Обработка кэшированных и зависших приложений

При использовании Binder для взаимодействия между процессами будьте особенно осторожны, когда удалённый процесс находится в кэшированном или замороженном состоянии. Вызовы к кэшированным или замороженным приложениям могут привести к их сбоям или неоправданному потреблению ресурсов.

Кэшированные и замороженные состояния приложений

Android поддерживает приложения в разных состояниях для управления системными ресурсами, такими как память и процессор.

Кэшированное состояние

Если в приложении нет видимых пользователю компонентов, таких как действия или службы, его можно перевести в состояние кэширования . Подробнее см. в разделе «Процессы и жизненный цикл приложения» . Кэшированные приложения хранятся в памяти на случай, если пользователь снова к ним переключится, но не ожидается их активной работы.

При привязке одного процесса приложения к другому, например, с помощью bindService , состояние серверного процесса становится как минимум столь же важным, как и состояние клиентского процесса (за исключением случаев, когда указан Context#BIND_WAIVE_PRIORITY ). Например, если клиент не находится в кэшированном состоянии, то и сервер тоже.

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

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

Чтобы отслеживать, когда приложения входят в кэшированное состояние или выходят из него, используйте ActivityManager.addOnUidImportanceListener :

// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
    new UidImportanceListener() { ... },
    IMPORTANCE_CACHED);

Замороженное состояние

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

Когда процесс отправляет синхронную (не oneway ) транзакцию связывания другому удалённому процессу, который заморожен, система завершает удалённый процесс. Это предотвращает бесконечное зависание вызывающего потока в вызывающем процессе в ожидании разморозки удалённого процесса, что может привести к нехватке потоков или взаимоблокировкам в вызывающем приложении.

Когда процесс отправляет асинхронную ( oneway ) транзакцию связывателя замороженному приложению (обычно посредством уведомления об обратном вызове, который обычно является oneway ), транзакция буферизируется до тех пор, пока удалённый процесс не будет разморожен. Если буфер переполнится, процесс приложения-получателя может аварийно завершить работу. Кроме того, буферизированные транзакции могут устареть к тому времени, когда процесс приложения разморозится и начнёт их обрабатывать.

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

Чтобы отслеживать, когда приложения заморожены или разморожены, используйте IBinder.addFrozenStateChangeCallback :

// The binder token of the remote process
IBinder binder = service.getBinder();

// Keep track of frozen state
AtomicBoolean remoteFrozen = new AtomicBoolean(false);

// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(
    myExecutor,
    new IBinder.FrozenStateChangeCallback() {
        @Override
        public void onFrozenStateChanged(boolean isFrozen) {
            remoteFrozen.set(isFrozen);
        }
    });

// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
    // dispatch callback to remote process
}

Использовать RemoteCallbackList

Класс RemoteCallbackList — вспомогательный класс для управления списками обратных вызовов IInterface , регистрируемых удалёнными процессами. Этот класс автоматически обрабатывает уведомления о завершении работы связующего компонента и предоставляет возможности для обработки обратных вызовов для зависших приложений.

При построении RemoteCallbackList вы можете указать политику замороженного вызываемого объекта:

  • FROZEN_CALLEE_POLICY_DROP : Обратные вызовы к замороженным приложениям отбрасываются без уведомления. Используйте эту политику, когда события, произошедшие во время кэширования приложения, не важны для него, например, события датчиков в реальном времени.
  • FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT : Если во время заморозки приложения транслируется несколько обратных вызовов, только последний из них будет поставлен в очередь и доставлен после разморозки. Это полезно для обратных вызовов на основе состояния, где имеет значение только последнее обновление состояния, например, для обратного вызова, уведомляющего приложение о текущей громкости медиафайлов.
  • FROZEN_CALLEE_POLICY_ENQUEUE_ALL : Все обратные вызовы, переданные во время заморозки приложения, ставятся в очередь и доставляются после разморозки. Будьте осторожны с этой политикой, так как она может привести к переполнению буфера при слишком большом количестве обратных вызовов в очереди или к накоплению устаревших событий.

В следующем примере показано, как создать и использовать экземпляр RemoteCallbackList , который сбрасывает обратные вызовы на замороженные приложения:

RemoteCallbackList<IMyCallbackInterface> callbacks =
        new RemoteCallbackList.Builder<IMyCallbackInterface>(
                        RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
                .setExecutor(myExecutor)
                .build();

// Registering a callback:
callbacks.register(callback);

// Broadcasting to all registered callbacks:
callbacks.broadcast((callback) -> callback.onSomeEvent(eventData));

Если вы используете FROZEN_CALLEE_POLICY_DROP , система вызывает callback.onSomeEvent() только в том случае, если процесс, размещающий обратный вызов, не заморожен.

Системные службы и взаимодействие приложений

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

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

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

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

  • ActivityManager.addOnUidImportanceListener : системные службы могут зарегистрировать прослушиватель для отслеживания изменений важности UID. При получении вызова или обратного вызова связующего компонента от приложения служба может использовать Binder.getCallingUid() для получения UID и его сопоставления с состоянием важности, отслеживаемым прослушивателем. Это позволяет системным службам определить, находится ли вызывающее приложение в кэше.

  • IBinder.addFrozenStateChangeCallback : Когда системная служба получает объект связывателя от приложения (например, в рамках регистрации для обратных вызовов), она должна зарегистрировать FrozenStateChangeCallback для этого конкретного экземпляра IBinder . Это напрямую уведомляет системную службу о том, что процесс приложения, содержащий этот связыватель, заморожен или разморожен.

Рекомендации по системным службам

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

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

Системные службы часто используют RemoteCallbackList для управления удалёнными обратными вызовами и автоматической обработки неработающих процессов. Для обработки зависших приложений расширьте существующее использование RemoteCallbackList , применив политику замороженного вызываемого объекта, как описано в разделе Использование RemoteCallbackList .