Serviços e transferência de dados

Esta página descreve como registrar e descobrir serviços e como enviar dados para um serviço chamando métodos definidos em interfaces em arquivos .hal.

Registrar serviços

Servidores de interface HIDL (objetos que implementam a interface) podem ser registrados como serviços nomeados. O nome registrado não precisa estar relacionado ao nome da interface ou do pacote. Se nenhum nome for especificado, o nome "default" será usado. Ele deve ser usado para HALs que não precisam registrar duas implementações da mesma interface. Por exemplo, a chamada C++ para registro de serviço definida em cada interface é:

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

A versão de uma interface HIDL está incluída na própria interface. Ele é associado automaticamente ao registro de serviço e pode ser recuperado por uma chamada de método (android::hardware::IInterface::getInterfaceVersion()) em todas as interfaces HIDL. Os objetos do servidor não precisam ser registrados e podem ser transmitidos por parâmetros de método HIDL para outro processo que faz chamadas de método HIDL no servidor.

Descobrir serviços

As solicitações pelo código do cliente são feitas para uma determinada interface por nome e versão, chamando getService na classe HAL desejada:

// C++
sp<V1_1::IFooService> service = V1_1::IFooService::getService();
sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service");
// Java
V1_1.IFooService service = V1_1.IFooService.getService(true /* retry */);
V1_1.IFooService alternateService = V1_1.IFooService.getService("another", true /* retry */);

Cada versão de uma interface HIDL é tratada como uma interface separada. Assim, IFooService versão 1.1 e IFooService versão 2.2 podem ser registradas como "foo_service" e getService("foo_service") em qualquer interface recebe o serviço registrado para essa interface. Por isso, na maioria dos casos, nenhum parâmetro de nome precisa ser fornecido para registro ou descoberta (ou seja, nome "padrão").

O objeto da interface do fornecedor também desempenha um papel no método de transporte da interface retornada. Para uma interface IFoo no pacote android.hardware.foo@1.0, a interface retornada por IFoo::getService sempre usa o método de transporte declarado para android.hardware.foo no manifesto do dispositivo, se a entrada existir. Se o método de transporte não estiver disponível, o valor null será retornado.

Em alguns casos, pode ser necessário continuar imediatamente, mesmo sem receber o serviço. Isso pode acontecer, por exemplo, quando um cliente quer gerenciar as notificações de serviço ou em um programa de diagnóstico (como atrace), que precisa receber e recuperar todos os hwservices. Nesse caso, outras APIs são fornecidas, como tryGetService em C++ ou getService("instance-name", false) em Java. A API legada getService fornecida em Java também precisa ser usada com notificações de serviço. O uso dessa API não evita a condição de corrida em que um servidor se registra depois que o cliente o solicita com uma dessas APIs sem nova tentativa.

Notificações de fim de serviço

Os clientes que querem receber uma notificação quando um serviço morre podem receber notificações de morte enviadas pelo framework. Para receber notificações, o cliente precisa:

  1. Crie uma subclasse da classe/interface hidl_death_recipient da HIDL (no código C++, não na HIDL).
  2. Substitua o método serviceDied().
  3. Instancie um objeto da subclasse hidl_death_recipient.
  4. Chame o método linkToDeath() no serviço a ser monitorado, transmitindo o objeto de interface da IDeathRecipient. Esse método não assume a propriedade do destinatário de falecimento ou do proxy em que ele é chamado.

Um exemplo de pseudocódigo (C++ e Java são semelhantes):

class IMyDeathReceiver : hidl_death_recipient {
  virtual void serviceDied(uint64_t cookie,
                           wp<IBase>& service) override {
    log("RIP service %d!", cookie);  // Cookie should be 42
  }
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);

O mesmo beneficiário pode ser registrado em vários serviços diferentes.

Transferência de dados

Os dados podem ser enviados para um serviço chamando métodos definidos em interfaces em arquivos .hal. Há dois tipos de métodos:

  • Os métodos de bloqueio esperam até que o servidor produza um resultado.
  • Os métodos Oneway enviam dados em apenas uma direção e não bloqueiam. Se a quantidade de dados em trânsito nas chamadas RPC exceder os limites de implementação, elas poderão bloquear ou retornar uma indicação de erro (o comportamento ainda não foi determinado).

Um método que não retorna um valor, mas não é declarado como oneway, ainda está bloqueando.

Todos os métodos declarados em uma interface HIDL são chamados em uma única direção, do HAL ou para o HAL. A interface não especifica em qual direção ela é chamada. As arquiteturas que precisam de chamadas originadas pelo HAL precisam fornecer duas (ou mais) interfaces no pacote HAL e oferecer a interface adequada de cada processo. As palavras cliente e servidor são usadas em relação à direção de chamada da interface (ou seja, o HAL pode ser um servidor de uma interface e um cliente de outra interface).

Callbacks

A palavra callback se refere a dois conceitos diferentes, que são distinguidos por callback síncrono e callback assíncrono.

Callbacks síncronos são usados em alguns métodos HIDL que retornam dados. Um método HIDL que retorna mais de um valor (ou retorna um valor de tipo não primitivo) retorna os resultados por uma função de callback. Se apenas um valor for retornado e for um tipo primitivo, um callback não será usado e o valor será retornado pelo método. O servidor implementa os métodos HIDL e o cliente implementa os callbacks.

Os callbacks assíncronos permitem que o servidor de uma interface HIDL origine chamadas. Isso é feito transmitindo uma instância de uma segunda interface pela primeira. O cliente da primeira interface precisa atuar como o servidor da segunda. O servidor da primeira interface pode chamar métodos no objeto da segunda interface. Por exemplo, uma implementação de HAL pode enviar informações de forma assíncrona de volta para o processo que a está usando, chamando métodos em um objeto de interface criado e atendido por esse processo. Os métodos em interfaces usados para callback assíncrono podem estar bloqueando (e retornando valores para o autor da chamada) ou oneway. Para conferir um exemplo, consulte "Callbacks assíncronos" em HIDL C++.

Para simplificar a propriedade da memória, as chamadas de método e os callbacks recebem apenas parâmetros in e não oferecem suporte a parâmetros out ou inout.

Limites por transação

Os limites por transação não são impostos à quantidade de dados enviados em métodos e callbacks do HIDL. No entanto, as chamadas que excedem 4 KB por transação são consideradas excessivas. Se isso for observado, é recomendável reestruturar a interface HIDL indicada. Outra limitação são os recursos disponíveis para a infraestrutura HIDL para processar várias transações simultâneas. Várias transações podem estar em andamento simultaneamente devido a várias linhas de execução ou processos que enviam chamadas para um processo ou várias chamadas oneway que não são processadas rapidamente pelo processo de recebimento. O espaço total máximo disponível para todas as transações simultâneas é de 1 MB por padrão.

Em uma interface bem projetada, o limite de recursos não deve ser excedido. Se isso acontecer, a chamada que excedeu o limite pode bloquear até que os recursos fiquem disponíveis ou sinalizar um erro de transporte. Cada ocorrência de excesso de limites por transação ou recursos de implementação de HIDL com transações em andamento agregadas é registrada para facilitar a depuração.

Implementações de método

O HIDL gera arquivos de cabeçalho que declaram os tipos, métodos e callbacks necessários no idioma de destino (C++ ou Java). O protótipo de métodos e callbacks definidos por HIDL é o mesmo para o código do cliente e do servidor. O sistema HIDL oferece implementações de proxy dos métodos no lado do autor da chamada que organizam os dados para o transporte de IPC e o código stub no lado do autor da chamada que transmite os dados para implementações de métodos do desenvolvedor.

O autor da chamada de uma função (método HIDL ou callback) tem a propriedade das estruturas de dados transmitidas para a função e retém a propriedade após a chamada. Em todos os casos, o autor da chamada não precisa liberar o armazenamento.

  • Em C++, os dados podem ser somente leitura (as tentativas de gravação podem causar uma falha de segmentação) e são válidos durante a duração da chamada. O cliente pode fazer uma cópia profunda dos dados para propagá-los além da chamada.
  • No Java, o código recebe uma cópia local dos dados (um objeto Java normal), que pode ser mantida e modificada ou pode ser coletada.

Transferência de dados não RPC

O HIDL tem duas maneiras de transferir dados sem usar uma chamada de RPC: memória compartilhada e uma fila de mensagens rápida (FMQ, na sigla em inglês), ambas com suporte apenas em C++.

  • Memória compartilhada. O tipo HIDL integrado memory é usado para transmitir um objeto que representa a memória compartilhada alocada. Pode ser usado em um processo de recebimento para mapear a memória compartilhada.
  • Fila de mensagens rápidas (FMQ). O HIDL fornece um tipo de fila de mensagens com modelo que implementa a transmissão de mensagens sem espera. Ele não usa o kernel ou o programador no modo de passagem ou de vinculação (a comunicação entre dispositivos não tem essas propriedades). Normalmente, o HAL configura o fim da fila, criando um objeto que pode ser transmitido pelo RPC por um parâmetro de tipo HIDL integrado MQDescriptorSync ou MQDescriptorUnsync. Esse objeto pode ser usado pelo processo de recebimento para configurar a outra extremidade da fila.
    • As filas de sincronização não podem transbordar e só podem ter um leitor.
    • As filas não sincronizadas podem transbordar e ter muitos leitores, cada um precisa ler os dados a tempo ou perdê-los.
    Nenhum tipo é permitido para underflow (a leitura de uma fila vazia falha) e cada tipo só pode ter um escritor.

Para mais detalhes sobre a FMQ, consulte Fila de mensagens rápidas (FMQ).