As aplicações de Android correm sobe uma máquina virtual de Java, de nome Dalvik. Uma das particularidades desta máquina virtual, é que as aplicações estão limitadas a uma Sandbox de execução, ou seja, o seu espaço de memória é privado. Este sistema é muito semelhante ao comportamento as aplicações nativas do sistema operativo subjacente do Android, o GNU/Linux.
Uma das consequências disto, é que a troca de informação entre aplicações excluí logo à partida a partilha de informação através de memória partilhada. O que resta, é a troca de mensagens entre aplicações.
O Android fornece vários mecanismos para envio de mensagens entre aplicações, alguns são mais apropriados que outros, dependendo do cenário de utilização. Temos por exemplo os BroadcastReceivers, que se tratam de classes que recebem informação que é enviada para o sistema operativo, e este trata de re-transmitir a informação para as várias aplicações que se registaram como “Receivers” para esse tipo de informação.
Este método não é o ideal em muitos casos, veja-se por exemplo que a informação fica passível a ser enviada para várias aplicações e não só para uma, e que a comunicação é só numa direcção, ou seja, a mensagem é enviada, recebida, mas não há sequer confirmação de que a mesma foi recebida, nem é possível retornar qualquer tipo de informação. Existe ainda o senão de que num ambiente em que existe mais que um BroadcastReceiver, o sistema operativo envia a informação para a 1ª aplicação, e se esta assim decidir pode consumir a mensagem e não a passar de volta para as seguintes.
Para resolver, este e outros cenários, a SDK fornece um mecanismo de troca de mensagens entre aplicações, aquilo que usualmente se chama de IPC (Inter Process Communication).
Este mecanismo é apoiado por uma linguagem de ligação da duas aplicações que queremos que comuniquem entre si, chamada de AIDL¹ (Android Interface Definition Language). Esta linguagem tem uma sintaxe bastante simples, e consiste apenas na declaração da assinatura dos métodos responsáveis pela comunicação.
A título de exemplo, vamos produzir 2 aplicações muito simples, de nomes appA e appB, que comunicarão entre si. A appA enviará uma mensagem à appB com o pedido para calcular o produto de 2 números inteiros. A appB receberá estes parâmetros na mensagens, e devolverá o resultado do produto dos mesmos. Apesar da simplicidade do exemplo, servirá para demonstrar os problemas envolvidos na produção da solução, tais como, o que fazer quando a aplicação com que estamos a comunicar não existe.
O primeiro passo será definir o ficheiro AIDL. Este ficheiro contem a definição da função que a appA irá chamar.
package ipc; interface ServicoIPC { int multiplicar(in int num1, in int num2); }
Repare que ambos os argumentos têm o prefixo in
. Isto faz parte da especificação AIDL, neste caso é in
porque estamos a lidar com tipos de dados simples de Java, e esses apenas podem ser do tipo in
.
O ficheiro que contem o código em cima especificado deverá estar presente tanto na appA como na appB, na pasta src
ou em dentro de qualquer package dentro da mesma. O ficheiro terá obrigatoriamente de ter a extensão .aidl
, uma vez que em tempo de compilação estes ficheiros são tratados de forma especial atendendo à sua extensão.
Do lado da appB, que é aplicação que irá receber a mensagem, e fazer o cálculo, temos de declarar um Service
de Android para lidar com a recepção da mensagem. O código em baixo apresentado ilustra esta situação.
package com.exemplo.appb; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class ServicoRemoto extends Service { @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent) { return new ServicoIPC.Stub() { /** * Método que calcula o produtor de num1 com num2 */ public int multiplicar(int num1, int num2) throws RemoteException { Log.d("appB","A appB recebeu um mensagem para calcular o produto de "+num1+" com "+num2); return num1 * num2; } }; } }
Após isto, temos obviamente de adicionar o Service
criado em cima ao manifest da aplicação, neste caso da appB. É também aqui que vamos expor o Service
ao exterior. Fazemos isso adicionando as linhas coloridas ao ficheiro AndroidManifest.xml
:
... <application …> <service android:name="ServicoRemoto" android:process="com.exemplo.appb.SetPrefsService"> <intent-filter> <action android:name="com.exemplo.appb.IPC" /> </intent-filter> </service> </application> ...
Do lado da appA, temos agora de efectuar a ligação com a appB. Primeiro temos de garantir que temos o ficheiro .aidl
na árvore de fonte de código da mesma, como já tinha sido referido. É importante também garantir que o ficheiro AIDL esteja na mesma package de Java em ambas as aplicações. Por motivos de escalabilidade convém separar a lógica da ligação e do envio da mensagem, do resto do código, assim fica mais simples usar e manter o uso da comunicação inter-procedural conforme a aplicação vai crescendo.
Para tal, vamos fazer uma classe que implementa o ServiceConnection
. O seguinte código exemplifica uma classe deste género. Note-se que seria nesta classe que o programador pode gerir os pedidos, a altura em que são enviados, possíveis filas de prioridade de pedidos, etc.
package com.exemplo.appa; import android.content.ComponentName; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class ConServicoRemoto implements ServiceConnection{ ServicoIPC service = null; private boolean estaVivo= false; /** * Constructor. */ public ConServicoRemoto() { // construtor vazio } public int adicionarRemoto(int val1, int val2) { if (service==null) { Log.d("appA","O Servico não está ligado. Causa possível: appB não está instalada"); return 0; } try { return service.multiplicar(val1, val2); } catch (RemoteException e) { Log.d("appA","RemoteException ao tentar fazer pedido de multiplicar à appB"); e.printStackTrace(); return 0; } } @Override public void onServiceConnected(ComponentName name, IBinder boundService) { service = ServicoIPC.Stub.asInterface((IBinder) boundService); Log.d("appA","Ligou-se à appB"); estaVivo=true; } @Override public void onServiceDisconnected(ComponentName name) { service=null; estaVivo=false; Log.d("appA","Desconectou da appB"); } /** * * @return True se a ligação à aplicação appB estiver activa. False caso contrário. */ public boolean ligacaoEstaActiva(){ return estaVivo; } }
Agora falta apenas fazer uma activity de Android, ainda na appA, a exemplificar o uso da classe acima declarada.
Para a nossa activity, declaramos na pasta layout
o seguinte ficheiro main.xml
que será a interface gráfica da mesma.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <EditText android:id="@+id/valor1" android:layout_width="60dip" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="50dip" /> <EditText android:id="@+id/valor2" android:layout_width="60dip" android:layout_height="wrap_content" android:layout_below="@id/valor1" android:layout_centerHorizontal="true" android:layout_marginTop="50dip" /> <Button android:id="@+id/btn_calcular" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/valor2" android:layout_centerHorizontal="true" android:layout_marginTop="50dip" android:text="Calcular"/> <TextView android:id="@+id/resultado" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/btn_calcular" android:layout_marginTop="40dip" android:layout_centerHorizontal="true" android:text="O resultado aparecerá aqui" /> </RelativeLayout>
E por fim temos o código da classe de nome Principal que fará uso desta interface gráfica acima declarada, e da classe ConServicoRemoto
.
Conclusão
Repare-se que o código é simplista, não existem verificações se o utilizador de facto escreve números inteiros, e não existe tratamento de erros na classe ConServicoRemoto
. Isso fica ao critério do leitor, uma vez que este exemplo está focado unicamente no tema abordado.
AIDL na documentação oficial da SDK de Android: http://developer.android.com/guide/developing/tools/aidl.html