Caso prefira, você encontrará todo esse material, em inglês, no site do Developer Android. A tradução e comentários dos materiais eu faço livremente para ajudar a comunidade que fala português.

sexta-feira, 8 de abril de 2011

Services - Parte 03 - Bound Service

Um bound service é um servidor em uma interface cliente servidor. Um bound service permite que componentes, como atividades, façam o bind para o serviço, enviem requisições, recebam respostas e realizem comunicações entre processos (IPC). Um bound service tipicamente vive apenas enquanto ele serve outra aplicação e não roda em background indefinidamente (como outros tipos de serviço).

O básico

Um bound service é uma implementação de uma classe de serviço que permite que outras aplicações façam o bind para si mesmo e interajam umas com as outras. Para prover o binding para um serviço, você deve implementar o método callback onBind(). Esse método retorna um objeto IBinder que define uma interface de programação que o cliente usa para interagir com o serviço.

Um cliente pode fazer o bind para o serviço chamando bindService(). Quando ele o faz, ele deve prover uma implementação de ServiceConnection, que monitora a conexão com o serviço. O método bindService() retorna imediatamente sem um valor mas quando o sistema Android cria uma conexão entre o cliente e o serviço ele chama onServiceConnected() no ServiceConnection, para entregar o IBinder que o cliente usa para comunicar com o serviço.

Multiplos clientes podem conectar-se ao serviço de uma vez. Contudo, o sistema chama o método onBind() do serviço para retornar o IBinder apenas quando o primeiro cliente faz o bind. O sistema então entrega o mesmo IBinder para quaisquer clientes adicionais que façam o bind, sem chamar o onBind() novamente.

Quando o último cliente finaliza o bind com o cliente, o sistema destrói o serviço (a não ser que o serviço tenha também inicializado startService()).

Quando você implementa seu bound service, a parte mais importante é definir a interface que seu método callback onBind() retornará. Existem maneiras diferentes de definir a interface IBinder.

Criando um Bound Service

Quando criar um serviço que prove o binding, você deve prover um IBinder que prove a interface de programação que o cliente pode usar para interagir com o serviço. Existem três maneiras onde você pode definir a interface:

Extendendo a classe Binder
Se o seu serviço é privado e apenas disponível para sua aplicação e roda no mesmo processo que o cliente (o que é comum), você deve criar sua interface extendendo a classe IBinder e retornando uma instância dele a partir do onBind(). O cliente recebe o Binder e pode usá-lo para acessar diretamente os métodos públicos disponíveis ou na implementação do Binder ou mesmo do serviço.

Essa é a técnica preferida quando seu serviço é meramente um job que roda em background para sua própria aplicação. A única razão pela qual não se deve criar uma interface é se o seu serviço é usado por outra aplicação ou através de processos separados.

Usando um Messenger
Se você precisa que sua interface trabalhe através de diferentes processos você pode criar uma interface para o serviço com um Messenger. Dessa maneira o serviço define um Handler que responde a diferentes tipos de objetos Messenger. Esse Handler é a base para um Messenger que pode então compartilhar um IBinder com o cliente, permitindo ao cliente enviar comandos para o serviço usando os objetos message. Adicionalmente, o cliente pode definir um Messenger dele mesmo para que o serviço possa enviar mensagens de volta.

Essa é simplesmente a forma de comunicação usada em IPC já que o messenger enfilera todas as requisições em uma única thread para que você naõ tenha que fazer o design do serviço para ser thread-safe.

Usando AIDL
AIDL (Android Interface Definition Language) realiza todo o trabalho para decompor objetos em tipos primitivos que o sistema operacional pode entender e ordená-los através dos processos que realizam IPCs. A técnica anterior, usando messenger, é tem a AIDL como estrutura primária. Como mencionado acima, o Messenger cria uma fila de todas as requisições do cliente em uma única thread, para que o serviço receba requisições uma de cada vez. Se, contudo, você quiser que seu serviço manuseie multiploas requisições simultâneamente, então você pode usar AIDL diretamente. Nesse caso, seu serviço deve ser capaz de ser multi-threading e ser construído em modo thread-safe.

Para usar AIDL diretamente você deve criar um arquivo .aidl que define a interface de programação. As ferramentas de SDK do Android usam esse arquivo para gerar uma classe abstrata que implementa a interface e manuseia o IPC que você pode então extender dentro do seu serviço.

Nota: A maioria das aplicações não usam AIDL para criar um bound service, já que ela requer capacidades multithreading e podem resultar uma implementação mais complexa. Como tal, AIDL não é recomendado para a maioria das aplicações e esse documento não discutirá como usá-los em seu serviço. Mas, caso queira saber mais sobre a AIDL, vá até esse link.

Extendendo uma classe Binder

Se o serviço é usado apenas por sua aplicação local e não precisa trabalhar através de diversos processos, então você pode implementar sua própria classe Binder que prove a seu cliente acesso direto para métodos públicos no serviço.

Como fazê-lo:

  • No seu serviço, crie uma instância de Binder que:
    • Contém métodos públicos que o cliente pode chamar ou;
    • Retorna a instância do serviço corrente que tem métodos públicos que o cliente pode chamar ou;
    • Retorna uma instância de uma outra classe hospedada pelo serviço com métodos públicos que o cliente pode chamar.
  • Retorna essa instância de Binder através do onBind()
  • No cliente, recebe o Binder a partir de onServiceConnected() e faz chamadas para o bound service usando os métodos providos.
Por exemplo:

public class LocalService extends Service {
    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();


    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods
            return LocalService.this;
        }
    }


    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}
O LocalBind prove o método getService() para os clientes capturarem uma instância de LocalService. Isso permite ao cliente chamada métodos públicos no serviço. Por exemplo, os clientes podem chamar getRandomNumber() a partir do serviço.

Aqui está uma atividade que faz um bind para o LocalService e chama getRandomNumber() quando um botão é clicado:

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
    /** Called when a button is clicked (the button in the layout file attaches to
      * this method with the android:onClick attribute) */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }
    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }
        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}


O exemplo acima mostra como o cliente faz o bind para o serviço usando uma implementação de ServiceConnection e o callback onServiceConnected.



Usando um Messenger

Se você precisa de um serviço para comunicar com processos remotos, então você pode usar um Messenger para prover uma interface para seu serviço. Essa técnica permite que você realize IPCs sem a necessidade de AIDL.

Aqui está um sumário de como usar um Messenger:

  • O serviço implementa um Handler que recebe um callback para cada chamada a partir de um cliente.
  • O Handler é usado para criar um objeto Messenger (que é uma referência para um Handler)
  • O Messenger cria um IBinder que o serviço retorna para o cliente a partir de onBind()
  • Clientes usam o IBinder para instanciar o Messenger (que referencia para o serviço do Handler), que o cliente usa para enviar objetos de Message para o serviço.
  • O serviço recebe cada Message em seu Handler - especificamente, no método handleMessage.
Dessa maneira, não existem métodos para o cliente chamar no serviço. Ao invés disso, o cliente entrega "mensagens" que o serviço receber em seu Handler.

Exemplo:

public class MessengerService extends Service {
    /** Command to the service to display a message */
    static final int MSG_SAY_HELLO = 1;


    /**
     * Handler of incoming messages from clients.
     */
    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }


    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());


    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    }
}
Note que o handleMessage() no Handler é onde o serviço recebe o Message de entrada e decide o que fazer, baseado no membro what.

Tudo que o cliente precisa fazer é criar um Messenger baseado no IBinder retornado pelo serviço e enviar uma mensagem usando send(). Por exemplo, aqui abaixo está uma atividade simples para o serviço e que entrega a mensagem MSG_SAY_HELLO no serviço.

public class ActivityMessenger extends Activity {
    /** Messenger for communicating with the service. */
    Messenger mService = null;


    /** Flag indicating whether we have called bind on the service. */
    boolean mBound;


    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the object we can use to
            // interact with the service.  We are communicating with the
            // service using a Messenger, so here we get a client-side
            // representation of that from the raw IBinder object.
            mService = new Messenger(service);
            mBound = true;
        }


        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mBound = false;
        }
    };


    public void sayHello(View v) {
        if (!mBound) return;
        // Create and send a message to the service, using a supported 'what' value
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }


    @Override
    protected void onStart() {
        super.onStart();
        // Bind to the service
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    }


    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
}
Note que esse exemplo não mostra como o serviço pode responder ao cliente. Se você quer que o serviço responda então você precisa também criar um Messenger em um cliente. Então quando o cliente recebe o callback onServiceConnected(), ele envia uma mensagem para o serviço que inclui o Messenger do cliente no parâmetro replyTo no método send().

Fazendo o binding para o serviço

Componentes de aplicação podem fazer o bind chamando o bindService(). O sistema Android então chama o onBind() do serviço que retorna um IBinder para a interação com o serviço.

O binding é assincrono. bindService() retorna imediatamente e não retorna o IBinder para o cliente. Para receber o IBinder, o cliente deve criar uma instância de ServiceConnection e passá-lo no bindService(). O ServiceConnection inclui um método callback que o sistema chama para entregar o IBinder.

Nota: Apenas atividades, serviços e content providers podem fazer o bind para o serviço - você não pode fazer o bind para um serviço de um broadcast receiver.

Então, para fazer o bind de um serviço para seu cliente você deve:
  1. Implemente ServiceConnection.
    Sua implementação deve fazer o override de dois métodos callbacks:

    onServiceConnected()
    O sistema o chama para entregar o IBinder retornado pelo método onBind() do serviço.

    onServiceDisconnected()
    O sistema Android o chama quando a conexão para o serviço é inexperadamente perdido, como quando um serviço caiu ou foi morto. Esse método não é chamado quando o cliente termina o bind.
  2. Chame o bindService(), passando a implementação de ServiceConnection.
  3. Quando o sistema chama o método callback onServiceConnected(), você pode começar a fazer chamadas para o serviço, usando os métodos definidos pela interface.
  4. Para disconectar-se do serviço, chame unbindService().
    Quando seu cliente é destruído, ele vai finalizar o bind do serviço mas você deve sempre chamar o método unbindService() quando terminar a interação com o serviço ou quando sua atividade pausa para que o serviço possa finalizar enquanto não está sendo usado.
Por exemplo, o código abaixo conecta o cliente ao serviço criado acima extendendo a classe Binder de maneira que tudo o que é necessário é fazer um cast no IBinder retornado para a classe LocalService e requisitar uma instância de LocalService.
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }
    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};
Com esse ServiceConnection, o cliente pode fazer o bind para um serviço passando-o para o bindService().

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  • O primeiro parâmetro de bindService() é um Intent que explicitamente nomeia o serviço para efetuar o bind.
  • O segundo parâmetro é o objeto de ServiceConnection
  • O terceiro parâmetro é uma flag indicando as opções do binding. Ele deve usualmente ser do tipo BIND_AUTO_CREATE para criar o serviço se ele não já tiver sido criado. Outros possíveis valores são BIND_DEBUG_UNBIND e BIND_NOT_FOREGROUND ou 0 para nada.

1 comentários:

Joel Ducatti disse...

Olá Leonardo,

sou iniciante em Android e estou procurando um método de comunicação entre Androids usando o paradigma cliente servidor através de uma rede. Gostaria saber se posso usar a interface AIDL como ligação entre processos remotos. Ex: Tenho dois androids em uma rede wireless, um deles deve ser o servidor e o outro o cliente. Através da AIDL eu consigo trafegar objetos entre a rede?

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.
Related Posts Plugin for WordPress, Blogger...