Quando um componente de aplicativo é iniciado e o aplicativo não tem outros componentes em execução, o sistema Android inicia um novo processo do Linux para o aplicativo com uma única linha de execução. Por padrão, todos os componentes do mesmo aplicativo são executados no mesmo processo e linha de execução, chamada de principal.
Se um componente de aplicativo for iniciado e já houver um processo para esse aplicativo, porque outro componente de aplicativo já foi iniciado, o componente de aplicativo será iniciado nesse processo e usará a mesma linha de execução. No entanto, é possível organizar diferentes componentes no aplicativo para serem executados em processos separados e criar linhas de execução adicionais para qualquer processo.
Este documento discute como processos e linhas de execução funcionam em um app Android.
Processos
Por padrão, todos os componentes de um aplicativo são executados no mesmo processo, e a maioria dos aplicativos não muda isso. No entanto, se você precisar controlar a qual processo um determinado componente pertence, faça isso no arquivo de manifesto.
A entrada de manifesto para cada tipo de elemento de componente—<activity>, <service>, <receiver> e <provider>—oferece suporte a um android:process atributo que pode especificar um
processo em que o componente é executado. É possível definir esse atributo para que cada componente seja executado no próprio processo ou para que alguns componentes compartilhem um processo enquanto outros não.
Também é possível definir android:process para que componentes de aplicativos diferentes sejam executados no mesmo processo, desde que os aplicativos compartilhem o mesmo ID de usuário do Linux e sejam assinados com os mesmos certificados.
O <application>
elemento também oferece suporte a um atributo android:process, que pode ser usado para definir um valor padrão
que se aplica a todos os componentes.
O Android pode decidir encerrar um processo em algum momento, quando os recursos forem exigidos por outros processos que atendem o usuário mais imediatamente. Os componentes do aplicativo em execução no processo que é desligado são consequentemente destruídos. Um processo é iniciado novamente para esses componentes quando há trabalho a ser feito.
Ao decidir quais processos encerrar, o sistema Android avalia a importância relativa deles para o usuário. Por exemplo, ele encerra mais facilmente um processo que hospeda atividades que não estão mais visíveis na tela, em comparação com um processo que hospeda atividades visíveis. Portanto, a decisão de encerrar um processo depende do estado dos componentes em execução nele.
Os detalhes do ciclo de vida do processo e a relação dele com os estados do aplicativo são discutidos em Processos e ciclo de vida do app.
Conversas
Quando um aplicativo é iniciado, o sistema cria uma linha de execução para ele, chamada de linha de execução principal. Essa linha de execução é muito importante, porque é responsável por enviar eventos aos widgets de interface do usuário apropriados, incluindo eventos de desenho. Ela também é quase sempre a linha de execução em que o aplicativo interage com componentes dos pacotes android.widget e android.view do kit de ferramentas de interface do Android.
Por esse motivo, a linha de execução principal às vezes é chamada de linha de execução de interface. No entanto, em circunstâncias especiais, a linha de execução principal de um app pode não ser a linha de execução de interface. Para mais informações, consulte Anotações de linha de execução.
O sistema não cria uma linha de execução separada para cada instância de um componente. Todos os componentes executados no mesmo processo são instanciados na linha de execução de interface, e as chamadas do sistema para cada componente são enviadas dessa linha de execução. Consequentemente, os métodos que respondem a callbacks do sistema, como onKeyDown() para informar ações do usuário ou um método de callback do ciclo de vida, sempre são executados na linha de execução de interface do processo.
Por exemplo, quando o usuário toca em um botão na tela, a linha de execução de interface do app envia o evento de toque para o widget, que define o estado pressionado e envia uma solicitação de invalidação para a fila de eventos. A linha de execução de interface remove a solicitação da fila e notifica o widget para que ele seja redesenhado.
A menos que você implemente o aplicativo corretamente, esse modelo de linha de execução única poderá gerar um desempenho ruim quando o app realizar um trabalho intenso em resposta à interação do usuário. A execução de operações longas na linha de execução de interface, como acesso à rede ou consultas de banco de dados, bloqueia toda a interface. Quando a linha de execução é bloqueada, nenhum evento pode ser enviado, incluindo eventos de desenho.
Do ponto de vista do usuário, o aplicativo parece parar de responder. Pior ainda, se a linha de execução de interface for bloqueada por mais de alguns segundos, o usuário vai receber a caixa de diálogo "O aplicativo não está respondendo" (ANR, na sigla em inglês). O usuário pode decidir sair do aplicativo ou até mesmo desinstalá-lo.
Lembre-se de que o kit de ferramentas de interface do Android não é thread-safe. Portanto, não manipule a interface de uma linha de execução de worker. Faça toda a manipulação na interface do usuário na linha de execução de interface. Há duas regras para o modelo de linha de execução única do Android:
- Não bloqueie a linha de execução de interface.
- Não acesse o kit de ferramentas de interface do Android de fora da linha de execução de interface.
Linhas de execução de worker
Devido a esse modelo de linha de execução única, é fundamental para a capacidade de resposta da interface do aplicativo que você não bloqueie a linha de execução de interface. Se você tiver operações a serem realizadas que não sejam instantâneas, faça-as em linhas de execução em segundo plano ou de worker separadas. Lembre-se de que não é possível atualizar a interface de qualquer linha de execução que não seja a linha de execução de interface ou principal.
Para ajudar você a seguir essas regras, o Android oferece várias maneiras de acessar a linha de execução de interface de outras linhas de execução. Confira uma lista de métodos que podem ajudar:
Os exemplos a seguir demonstram como descarregar uma tarefa para uma linha de execução em segundo plano e atualizar a linha de execução de interface depois que a tarefa for concluída:
Kotlin
// Kotlin coroutines implementation. fun onClick(v: View) { // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment). lifecycleScope.launch { // Run the blocking task on the IO dispatcher. val bitmap = withContext(Dispatchers.IO) { BitmapFactory.decodeFile("image.png") } // Back on the main thread, update the UI. imageView.setImageBitmap(bitmap) } }
Java
// Java Executor implementation. // (executorService is assumed to be defined elsewhere). public void onClick(View v) { executorService.execute(() -> { // Run the heavy task on a background thread. Bitmap bitmap = BitmapFactory.decodeFile("image.png"); // Update the View on the UI thread. imageView.post(() -> imageView.setImageBitmap(bitmap)); }); }
Essa implementação é thread-safe, porque a operação em segundo plano é feita em uma linha de execução separada, enquanto a ImageView é sempre manipulada na linha de execução de interface.
No entanto, à medida que a complexidade da operação aumenta, esse tipo de código pode ficar complicado e difícil de manter. Para lidar com interações mais complexas com uma linha de execução de worker, considere usar um Handler na linha de execução de worker para processar mensagens entregues pela linha de execução de interface. Para uma explicação completa de como
programar o trabalho em linhas de execução em segundo plano e se comunicar com a linha de execução de interface, consulte
Visão geral do trabalho em segundo plano.
Métodos thread-safe
Em algumas situações, os métodos implementados são chamados de mais de uma linha de execução e, portanto, precisam ser escritos para serem thread-safe.
Isso é verdadeiro principalmente para métodos que podem ser chamados remotamente, como métodos em um serviço vinculado. Quando uma chamada em um
método implementado em um IBinder se origina no mesmo processo em que o
IBinder está em execução, o método é executado na linha de execução do autor da chamada.
No entanto, quando a chamada se origina em outro processo, o método é executado em uma linha de execução escolhida em um pool de linhas de execução que o sistema mantém no mesmo processo que o IBinder.
Ele não é executado na linha de execução de interface do processo.
Por exemplo, enquanto o método
onBind() de um serviço é chamado na linha de execução de interface do processo do
serviço, os métodos implementados no objeto que onBind() retorna, como uma
subclasse que implementa métodos de chamada de procedimento remoto (RPC), são chamados de linhas de execução
no pool. Como um serviço pode ter mais de um cliente, mais de uma linha de execução do pool pode usar o mesmo método IBinder ao mesmo tempo. Portanto, os métodos IBinder precisam ser implementados para serem thread-safe.
Da mesma forma, um provedor de conteúdo pode receber solicitações de dados originadas em outros processos.
As classes ContentResolver e ContentProvider
ocultam os detalhes de como a comunicação entre processos (IPC) é gerenciada,
mas os métodos ContentProvider que respondem a essas solicitações (os métodos
query(),
insert(),
delete(),
update(),
e getType()) são
chamados de um pool de linhas de execução no processo do provedor de conteúdo, não da linha de execução de interface
do processo. Como esses métodos podem ser chamados de qualquer número de linhas de execução ao mesmo tempo, eles também precisam ser implementados para serem thread-safe.
Comunicação entre processos
O Android oferece um mecanismo para IPC usando RPCs, em que um método é chamado por uma atividade ou outro componente do aplicativo, mas executado remotamente em outro processo, com qualquer resultado retornado ao autor da chamada. Isso envolve decompor uma chamada de método e os dados dela em um nível que o sistema operacional possa entender, transmitindo-o do processo local e do espaço de endereço para o processo remoto e o espaço de endereço e, em seguida, remontando e repetindo a chamada.
Os valores de retorno são transmitidos na direção oposta. O Android fornece todo o código para realizar essas transações de IPC. Assim, você pode se concentrar na definição e implementação da interface de programação RPC.
Para realizar a IPC, o aplicativo precisa ser vinculado a um serviço usando bindService(). Para mais informações, consulte a Visão geral dos serviços.