Ao usar o binder para se comunicar entre processos, tome cuidado especial quando o processo remoto estiver em um estado armazenado em cache ou congelado. Chamadas para apps em cache ou congelados podem causar falhas ou consumir recursos desnecessariamente.
Estados de apps armazenados em cache e congelados
O Android mantém os apps em estados diferentes para gerenciar recursos do sistema, como memória e CPU.
Estado em cache
Quando um app não tem componentes visíveis para o usuário, como atividades ou serviços, ele pode ser movido para o estado em cache. Consulte Processos e ciclo de vida de um app para mais detalhes. Os apps armazenados em cache são mantidos na memória caso o usuário volte a eles, mas não devem estar funcionando ativamente.
Ao vincular de um processo de app a outro, como usar bindService,
o estado do processo do servidor é elevado para ser pelo menos tão importante
quanto o processo do cliente (exceto quando Context#BIND_WAIVE_PRIORITY é
especificado). Por exemplo, se o cliente não estiver no estado armazenado em cache, o servidor também não estará.
Por outro lado, o estado de um processo do servidor não determina o dos clientes. Assim, um servidor pode ter conexões de binder com clientes, geralmente na forma de callbacks, e, embora o processo remoto esteja no estado em cache, o servidor não está.
Ao projetar APIs em que os callbacks se originam em um processo elevado e são entregues a apps, considere pausar o envio de callbacks quando um app entra no estado em cache e retomar quando ele sai desse estado. Isso evita trabalho desnecessário em processos de apps armazenados em cache.
Para rastrear quando os apps entram ou saem do estado em cache, use
ActivityManager.addOnUidImportanceListener:
// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
new UidImportanceListener() { ... },
IMPORTANCE_CACHED);
Estado congelado
O sistema pode congelar um app em cache para economizar recursos. Quando um app é congelado, ele não recebe tempo de CPU e não pode fazer nada. Para mais detalhes, consulte Congelador de apps em cache.
Quando um processo envia uma transação de vinculação síncrona (não oneway) para outro processo remoto congelado, o sistema encerra o processo remoto. Isso
evita que a linha de execução de chamada no processo de chamada fique pendente indefinidamente
enquanto espera que o processo remoto seja descongelado, o que pode causar privação
de linha de execução ou impasses no app de chamada.
Quando um processo envia uma transação de binder assíncrona (oneway) para um app congelado (normalmente notificando um callback, que geralmente é um método oneway), a transação é armazenada em buffer até que o processo remoto seja descongelado. Se o buffer transbordar, o processo do app destinatário poderá falhar. Além disso, as transações em buffer podem ficar desatualizadas quando o processo do app é descongelado e processado.
Para evitar sobrecarregar os apps com eventos desatualizados ou transbordar os buffers, você precisa pausar o envio de callbacks enquanto o processo do app destinatário está congelado.
Para acompanhar quando os apps são congelados ou descongelados, use
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
}
Usar RemoteCallbackList
A classe RemoteCallbackList é uma auxiliar para gerenciar listas de
callbacks IInterface que processos remotos registram. Essa classe processa automaticamente
notificações de morte do binder e oferece opções para processar callbacks
em apps congelados.
Ao criar RemoteCallbackList, é possível especificar uma política de destinatário congelada:
FROZEN_CALLEE_POLICY_DROP: os callbacks para apps congelados são descartados silenciosamente. Use essa política quando os eventos que ocorreram enquanto o app estava em cache não forem importantes para ele, por exemplo, eventos de sensor em tempo real.FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: se vários callbacks forem transmitidos enquanto um app estiver congelado, apenas o mais recente será enfileirado e entregue quando o app for descongelado. Isso é útil para callbacks baseados em estado em que apenas a atualização de estado mais recente importa, por exemplo, um callback que notifica o app sobre o volume de mídia atual.FROZEN_CALLEE_POLICY_ENQUEUE_ALL: todos os callbacks transmitidos enquanto um app está congelado são enfileirados e entregues quando o app é descongelado. Tenha cuidado com essa política, porque ela pode causar estouros de buffer se muitas callbacks forem enfileiradas ou o acúmulo de eventos obsoletos.
O exemplo a seguir mostra como criar e usar uma instância RemoteCallbackList
que descarta callbacks para apps congelados:
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));
Se você usar FROZEN_CALLEE_POLICY_DROP, o sistema vai invocar
callback.onSomeEvent() somente se o processo que hospeda o callback não estiver congelado.
Serviços do sistema e interações com apps
Os serviços do sistema geralmente interagem com vários apps diferentes usando o binder. Como os apps podem entrar em estados armazenados em cache e congelados, os serviços do sistema precisam tomar cuidado especial para processar essas interações de maneira adequada e ajudar a manter a estabilidade e o desempenho do sistema.
Os serviços do sistema já precisam lidar com situações em que os processos do app são encerrados por vários motivos. Isso envolve interromper o trabalho em nome deles e não tentar continuar entregando callbacks a processos inativos. A consideração de apps congelados é uma extensão dessa responsabilidade de monitoramento.
Rastrear estados de apps em serviços do sistema
Os serviços do sistema, executados em system_server ou como daemons nativos, também podem usar
as APIs descritas anteriormente para rastrear a importância e o estado congelado dos processos
de apps:
ActivityManager.addOnUidImportanceListener: os serviços do sistema podem registrar um listener para rastrear mudanças na importância do UID. Ao receber uma chamada de binder ou um callback de um app, o serviço pode usarBinder.getCallingUid()para receber o UID e correlacionar com o estado de importância rastreado pelo listener. Isso permite que os serviços do sistema saibam se o app de chamada está em um estado armazenado em cache.IBinder.addFrozenStateChangeCallback: quando um serviço do sistema recebe um objeto binder de um app (por exemplo, como parte de um registro para callbacks), ele precisa registrar umFrozenStateChangeCallbacknessa instância específica deIBinder. Isso notifica diretamente o serviço do sistema quando o processo do app que hospeda esse binder é congelado ou descongelado.
Recomendações para serviços do sistema
Recomendamos que todos os serviços do sistema que possam interagir com apps rastreiem o estado armazenado em cache e congelado dos processos de app com que se comunicam. Não fazer isso pode levar a:
- Consumo de recursos:realizar trabalho para apps armazenados em cache e não visíveis para o usuário pode desperdiçar recursos do sistema.
- Falhas de apps:as chamadas de vinculação síncronas para apps congelados causam falhas. Chamadas assíncronas de binder para apps congelados causam falha se o buffer de transação assíncrona transbordar.
- Comportamento inesperado do app:os apps descongelados vão receber imediatamente as transações de binder assíncronas em buffer que foram enviadas a eles enquanto estavam congelados. Os apps podem ficar por um período indefinido no freezer, então as transações em buffer podem ficar muito desatualizadas.
Os serviços do sistema geralmente usam RemoteCallbackList para gerenciar callbacks remotos
e processar automaticamente processos inativos. Para processar apps congelados, estenda o
uso atual de RemoteCallbackList aplicando uma política de destinatário congelado
conforme descrito em Usar RemoteCallbackList.