При использовании Binder для связи между процессами будьте особенно осторожны, когда удаленный процесс находится в кешированном или замороженном состоянии. Вызовы кешированных или замороженных приложений могут привести к их сбою или ненужному потреблению ресурсов.
Состояния приложений, сохраненные в кеше и замороженные
Android поддерживает приложения в разных состояниях, чтобы управлять системными ресурсами, такими как память и ЦП.
Состояние в кеше
Если в приложении нет видимых пользователю компонентов, таких как действия или сервисы, оно может быть переведено в кешированное состояние. Подробнее о процессах и жизненном цикле приложений… Приложения, сохраненные в кеше, остаются в памяти устройства на случай, если пользователь захочет вернуться к ним, но они не должны работать в фоновом режиме.
При связывании одного процесса приложения с другим, например с помощью bindService, статус серверного процесса повышается до уровня важности клиентского процесса (за исключением случаев, когда указан Context#BIND_WAIVE_PRIORITY). Например, если клиент не находится в кешированном состоянии, то и сервер тоже.
И наоборот, состояние серверного процесса не определяет состояние его клиентов. Таким образом, у сервера могут быть подключения Binder к клиентам, чаще всего в форме обратных вызовов, и пока удаленный процесс находится в кешированном состоянии, сервер не кешируется.
При разработке API, в которых обратные вызовы инициируются в процессе с повышенными привилегиями и передаются приложениям, рекомендуется приостанавливать отправку обратных вызовов, когда приложение переходит в состояние кэширования, и возобновлять ее, когда оно выходит из этого состояния. Это позволяет избежать ненужной работы в процессах приложений, хранящихся в кеше.
Чтобы отслеживать, когда приложения переходят в кешированное состояние или выходят из него, используйте ActivityManager.addOnUidImportanceListener:
Java
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new ActivityManager.OnUidImportanceListener() { ... },
IMPORTANCE_CACHED);
Kotlin
// in ActivityManager or Context
activityManager.addOnUidImportanceListener({ uid, importance ->
// ...
}, IMPORTANCE_CACHED)
Замороженное состояние
Система может заморозить кешированное приложение, чтобы сэкономить ресурсы. Когда приложение заморожено, оно не получает процессорного времени и не может выполнять никакие действия. Подробнее о заморозке приложений в кеше…
Когда процесс отправляет синхронную (не oneway) транзакцию Binder другому удаленному процессу, который заморожен, система завершает удаленный процесс. Это предотвращает зависание вызывающего потока в вызывающем процессе на неопределенное время в ожидании разморозки удаленного процесса, что может привести к голоданию потока или взаимной блокировке в вызывающем приложении.
Когда процесс отправляет асинхронную (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
}
Kotlin
// 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, которые регистрируют удаленные процессы. Этот класс автоматически обрабатывает уведомления о завершении работы связывателя и предоставляет варианты обработки обратных вызовов для замороженных приложений.
При создании 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));
Kotlin
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.getCallingUid(), чтобы получить UID и сопоставить его с состоянием важности, отслеживаемым слушателем. Это позволяет системным службам узнать, находится ли вызывающее приложение в кэшированном состоянии.IBinder.addFrozenStateChangeCallback. Когда системный сервис получает объект binder от приложения (например, в рамках регистрации обратных вызовов), он должен зарегистрироватьFrozenStateChangeCallbackв этом конкретном экземпляреIBinder. Это позволяет напрямую уведомлять системную службу о том, что процесс приложения, в котором размещен связыватель, заморожен или разморожен.
Рекомендации для системных сервисов
Мы рекомендуем всем системным сервисам, которые могут взаимодействовать с приложениями, отслеживать состояние кеширования и заморозки процессов приложений, с которыми они обмениваются данными. Если этого не сделать, возможны следующие последствия:
- Потребление ресурсов. Выполнение задач для приложений, которые находятся в кеше и не видны пользователю, может привести к нерациональному использованию системных ресурсов.
- Сбои в работе приложений. Синхронные вызовы связывателя к замороженным приложениям приводят к сбоям в их работе. Асинхронные вызовы связывателя в замороженные приложения приводят к сбою, если переполняется буфер асинхронных транзакций.
- Неожиданное поведение приложений. Размороженные приложения сразу же получают все буферизованные асинхронные транзакции Binder, отправленные им, пока они были заморожены. Приложения могут находиться в замороженном состоянии неограниченное время, поэтому буферизованные транзакции могут быть очень старыми.
Системные сервисы часто используют RemoteCallbackList для управления удаленными обратными вызовами и автоматической обработки неактивных процессов. Чтобы обрабатывать замороженные приложения, расширьте существующее использование RemoteCallbackList, применив правило замороженного вызываемого объекта, как описано в разделе Использование RemoteCallbackList.