При использовании Binder для обмена данными между процессами следует проявлять особую осторожность, когда удалённый процесс находится в кэшированном или замороженном состоянии. Вызовы к кэшированным или замороженным приложениям могут привести к их сбоям или ненужному потреблению ресурсов.
Кэшированные и зависшие состояния приложения
Android поддерживает приложения в различных состояниях для управления системными ресурсами, такими как память и процессор.
Кэшированное состояние
Если приложение не содержит видимых пользователю компонентов, таких как действия или сервисы, его можно перевести в кэшированное состояние. Подробнее см. раздел «Процессы и жизненный цикл приложения» . Кэшированные приложения хранятся в памяти на случай, если пользователь вернется к ним, но предполагается, что они не будут активно работать.
При привязке одного процесса приложения к другому, например, с помощью bindService , состояние процесса сервера становится как минимум столь же важным, как и состояние процесса клиента (за исключением случаев, когда указан Context#BIND_WAIVE_PRIORITY ). Например, если клиент не находится в кэшированном состоянии, то и сервер тоже.
Напротив, состояние серверного процесса не определяет состояние его клиентов. Таким образом, сервер может иметь соединения с клиентами через Binder, чаще всего в виде обратных вызовов, и пока удалённый процесс находится в кэшированном состоянии, сам сервер не кэшируется.
При проектировании API, где обратные вызовы инициируются в процессе с повышенными правами доступа и доставляются приложениям, рекомендуется приостанавливать отправку обратных вызовов, когда приложение переходит в кэшированное состояние, и возобновлять её при выходе из этого состояния. Это предотвратит ненужную работу в кэшированных процессах приложения.
Для отслеживания перехода приложений в кэшированное состояние и выхода из него используйте ActivityManager.addOnUidImportanceListener :
Java
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new ActivityManager.OnUidImportanceListener() { ... },
IMPORTANCE_CACHED);
Котлин
// in ActivityManager or Context
activityManager.addOnUidImportanceListener({ uid, importance ->
// ...
}, IMPORTANCE_CACHED)
Замороженное состояние
Система может заморозить кэшированное приложение для экономии ресурсов. Когда приложение заморожено, оно не получает процессорного времени и не может выполнять никакой работы. Для получения более подробной информации см. раздел «Заморозка кэшированных приложений» .
Когда процесс отправляет синхронную (не oneway ) транзакцию связывания другому удалённому процессу, который заморожен, система завершает работу удалённого процесса. Это предотвращает бесконечное зависание вызывающего потока в вызывающем процессе в ожидании разморозки удалённого процесса, что может привести к голоданию потоков или взаимоблокировкам в вызывающем приложении.
Когда процесс отправляет асинхронную ( oneway ) транзакцию связывания в зависшее приложение (обычно путем уведомления об ошибке обратного вызова, что, как правило, является oneway методом), транзакция буферизуется до тех пор, пока удаленный процесс не будет разморожен. Если буфер переполнится, процесс принимающего приложения может завершиться с ошибкой. Кроме того, буферизованные транзакции могут устареть к моменту разморозки процесса приложения и их обработки.
Чтобы избежать перегрузки приложений устаревшими событиями или переполнения их буферов, необходимо приостановить отправку обратных вызовов, пока процесс принимающего приложения находится в режиме ожидания.
Для отслеживания момента зависания или размораживания приложений используйте IBinder.addFrozenStateChangeCallback :
Java
// 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
}
Котлин
// The binder token of the remote process
val binder: IBinder = service.getBinder()
// Keep track of frozen state
val remoteFrozen = AtomicBoolean(false)
// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(myExecutor) { isFrozen ->
remoteFrozen.set(isFrozen)
}
// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
// dispatch callback to remote process
}
C++
Для кода платформы, использующего API связывания C++:
#include <binder/Binder.h>
#include <binder/IBinder.h>
// The binder token of the remote process
android::sp<android::IBinder> binder = service->getBinder();
// Keep track of frozen state
std::atomic<bool> remoteFrozen = false;
// Define a callback class
class MyFrozenStateCallback : public android::IBinder::FrozenStateChangeCallback {
public:
explicit MyFrozenStateCallback(std::atomic<bool>* frozenState) : mFrozenState(frozenState) {}
void onFrozenStateChanged(bool isFrozen) override {
mFrozenState->store(isFrozen);
}
private:
std::atomic<bool>* mFrozenState;
};
// Update remoteFrozen when the remote process freezes or unfreezes
if (binder != nullptr) {
binder->addFrozenStateChangeCallback(android::sp<android::IBinder::FrozenStateChangeCallback>::make(
new MyFrozenStateCallback(&remoteFrozen)));
}
// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.load()) {
// dispatch callback to remote process
}
Используйте RemoteCallbackList
Класс RemoteCallbackList — это вспомогательный класс для управления списками обратных вызовов IInterface , которые регистрируются удаленными процессами. Этот класс автоматически обрабатывает уведомления о завершении работы Binder и предоставляет возможности для обработки обратных вызовов для зависших приложений.
При создании объекта RemoteCallbackList можно указать политику заморозки вызываемых объектов:
-
FROZEN_CALLEE_POLICY_DROP: Обратные вызовы к зависшим приложениям отбрасываются без уведомления. Используйте эту политику, когда события, произошедшие во время кэширования приложения, не важны для него, например, события с датчиков в реальном времени. -
FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: Если во время зависания приложения отправляется несколько обратных вызовов, в очередь ставится и доставляется только самый последний. Это полезно для обратных вызовов, основанных на состоянии, где имеет значение только самое последнее обновление состояния, например, обратный вызов, уведомляющий приложение о текущей громкости мультимедиа. -
FROZEN_CALLEE_POLICY_ENQUEUE_ALL: Все широковещательные обратные вызовы, отправляемые во время зависания приложения, ставятся в очередь и доставляются после разморозки приложения. Будьте осторожны с этой политикой, поскольку она может привести к переполнению буфера, если в очередь поставлено слишком много обратных вызовов, или к накоплению устаревших событий.
В следующем примере показано, как создать и использовать экземпляр RemoteCallbackList , который отправляет обратные вызовы зависшим приложениям:
Java
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));
Котлин
val callbacks =
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 или обратного вызова от приложения служба может использоватьBinder.getCallingUid()для получения UID и сопоставления его с состоянием важности, отслеживаемым слушателем. Это позволяет системным службам узнать, находится ли вызывающее приложение в кэшированном состоянии.IBinder.addFrozenStateChangeCallback: Когда системная служба получает объект привязки от приложения (например, в рамках регистрации обратных вызовов), она должна зарегистрироватьFrozenStateChangeCallbackдля этого конкретного экземпляраIBinder. Это напрямую уведомляет системную службу о том, когда процесс приложения, в котором размещен этот объект привязки, становится замороженным или размороженным.
Рекомендации по системным службам
Мы рекомендуем всем системным службам, которые могут взаимодействовать с приложениями, отслеживать кэшированное и заблокированное состояние процессов приложений, с которыми они взаимодействуют. Несоблюдение этого требования может привести к следующим последствиям:
- Потребление ресурсов: Выполнение задач для приложений, которые кэшированы и недоступны пользователю, может приводить к нерациональному использованию системных ресурсов.
- Сбои в работе приложений: Синхронные вызовы binder к зависшим приложениям приводят к их сбою. Асинхронные вызовы binder к зависшим приложениям приводят к сбою, если происходит переполнение буфера асинхронных транзакций.
- Неожиданное поведение приложения: Размороженные приложения немедленно получают все буферизованные асинхронные транзакции Binder, отправленные им во время заморозки. Приложения могут находиться в замороженном состоянии неограниченное время, поэтому буферизованные транзакции могут быть очень устаревшими.
Системные службы часто используют RemoteCallbackList для управления удаленными обратными вызовами и автоматической обработки завершенных процессов. Для обработки зависших приложений расширьте существующее использование RemoteCallbackList , применив политику обработки зависших вызываемых процессов, как описано в разделе «Использование RemoteCallbackList» .