Bienvenidos sean a este post, en el caso de hoy veremos como se puede usar al servicio de otra forma y esta es para permitir una comunicacion entre aplicaciones, esto es necesario para cuando una aplicacion quiere enviar informacion a otra pero no puede porque cada aplicacion tiene un espacio de memoria asignado y no compartido. Android propone como solucion un mecanismo de comunicacion entre procesos que se basa en un lenguaje de especificacion de interfaces, AIDL (Android Interface Definition Language). Las AIDL se publica por medio de servicios, gracias a esto un proceso en Android puede llamar a un metodo en un objeto de un proceso diferente al suyo, se trata de algo similar a COM pero de forma mas ligera.

Vamos a ver un ejemplo para entenderlo, para ello crearemos un nuevo proyecto en Android Studio con las siguientes caracteristicas:

  • Application Name: Servicio Remoto
  • Domain Name: example.org
  • SDK Minimum (Phone and Tablet): 14
  • Add an activity: Empty Activity

Una vez generada nuestra nueva app, procederemos a modificar el archivo activity_main.xml donde lo reemplazaremos con el siguiente codigo:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Servicio de reproduccion de música" />
    <Button
        android:id="@+id/boton_conectar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Conectar Servicio" />
    <Button
        android:id="@+id/boton_desconectar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Desconectar Servicio" />
    <Button
        android:id="@+id/boton_reproducir"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Reproducir" />
    <Button
        android:id="@+id/boton_avanzar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Avanzar" />    
</LinearLayout>

En este caso se creara un layout donde tendremos cuatro botones para conectarnos a un servicio, para desconectarnos, para reproducir y para avanzar, nuestro siguiente paso sera agregar un nuevo recurso. Para ello debemos crear su carpeta contenedora, para eso debemos ir a la carpeta res donde haremos click con el boton derecho y seleccionar New -> Android Resource Directory, en Directory name modifiquenlo a raw, luego en Resource type seleccionen raw, el resto debe estar como aparece, pulsen Ok para crear el nuevo recurso. Con nuestro nuevo recurso contenedor tendremos dos opciones, toman un archivo de musica en formato MP3 de ustedes y lo renombran a audio.mp3 o descargan el siguiente archivo:

Con el archivo, ya sea que eligieron uno o descargaron el archivo antes sugerido van a la carpeta donde esta y presionen Ctrl+C o click con el boton derecho y seleccionen Copiar, vuelven a Android Studio y van a la nueva carpeta que creamos anteriormente, raw, y presionamos Ctrl+V o click con el boton derecho y seleccionen Paste, aparecera un nuevo cuadro donde deberan dejar todo como aparece y pulsen Ok para copiar el nuevo recurso.

Nota: Despues de hecho esto, vayan a File -> Sync Project with Gradle Files para sincronizar al nuevo recurso.

Con esto basico ya creado vamos a crear una interfaz de tipo AIDL, su sintaxis es similar a Java (o C++) pero solo nos permitira identificar unicamente una interfaz de un objeto pero no su implementacion, una interfaz esta formada por una secuencia de metodos, cada una con su serie de parametros y un valor devuelto, los parametros y los valores devueltos han de tener un tipo y los tipos permitidos son:

  • Tipos primitivos: int, short, byte, char, float, double, long, boolean
  • Una de estas clases: String, CharSequence, List, Map
  • Una interfaz escrita en AIDL
  • Una subclase de Parcelable

Retomando a nuestro ejemplo primero debemos crear una interfaz AIDL, para ello debemos hacer click con el boton derecho sobre org.example.servicioremoto y seleccionar New -> AIDL -> AIDL File como se ve en la siguiente imagen

Una vez seleccionada nos aparecera el siguiente cuadro

En el unico campo disponible modifiquen el valor por IServicioMusica y luego presionen Finish para crear la interfaz, esta nos quedara asi

Con esto terminado pasemos a modificar a la interfaz con el siguiente codigo:

package org.example.servicioremoto;

interface IServicioMusica {
    String reproducir(in String mensaje);
    void setPosicion(int ms);
    int getPosicion();
}

Este codigo es muy similar a los de Java pero vean un detalle en el metodo reproducir, cuando no es un tipo primitivo debemos informarle que funcion va a tener el atributo, si es de entrada (in), salida (out) o ambas (inout), en este caso es de entrada. Luego tendremos un metodo para ubicar nuestra posicion y otra para obtenerla pero como pueden ver no son metodos en si sino prototipos, es decir simples declaraciones de los metodos pero sin definiciones ni acciones, con esto modificado solo debemos hacer una cosa mas, es ir a Build -> Rebuild project como se ve en la siguiente imagen

Esto volvera a regenerar el proyecto y a su vez nos generara una clase de Java relacionada al archivo AIDL creado recientemente, una vez creado si lo buscan lo pueden encontrar sino se los muestro en esta imagen

En este archivo que se genero no debemos modificar nada porque sera el encargado de sobreescribir muchos metodos utiles para nosotros entre ellos Stub pero eso lo veremos un poco mas adelante, siguiendo con nuestro proyecto para ello pasaremos a crear una nueva clase llamada ServicioRemoto la cual sera heredera de Service y nos permitira implementar onBind() para ello vamos al contenedor de las clases Java y hacen click con el boton derecho y seleccionan New -> Java class, cuando aparezca el nuevo cuadro modifican el campo Name con ServicioRemoto y el resto queda como aparece, pulsen Ok y procedera a generar la nueva clase, a esta clase la modificaremos con el siguiente codigo:

package org.example.servicioremoto;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.os.RemoteException;

public class ServicioRemoto extends Service {

    MediaPlayer reproductor;

    @Override
    public void onCreate(){
        super.onCreate();
        reproductor = MediaPlayer.create(ServicioRemoto.this,R.raw.audio);
    }

    private final IServicioMusica.Stub binder = new IServicioMusica.Stub() {
        @Override
        public String reproducir(String mensaje) throws RemoteException {
            reproductor.start();
            return mensaje;
        }

        @Override
        public void setPosicion(int ms) throws RemoteException {
            reproductor.seekTo(ms);
        }

        @Override
        public int getPosicion() throws RemoteException {
            return reproductor.getCurrentPosition();
        }
    };

    @Override
    public IBinder onBind(Intent intento){
        return this.binder;
    }
}

Con esta clase implementaremos la interfaz antes creada, en este caso primero crearemos un objeto llamado reproductor de la clase MediaPlayer, en el metodo onCreate() crearemos un reproductor para asignarle el recurso audio.mp3, si comienzan a tipear el siguiente metodo y seleccionen a Stub debera generar automaticamente el resto de los metodos pero con instrucciones diferentes, modifiquenla hasta que queden como se ve en el codigo, estas se encargaran de reproducir, ir a una posicion y conseguir la misma, y el ultimo metodo se encarga de devolver lo creado anteriormente, cuando implementes los metodos de una interfaz AIDL has de tener en cuenta lo siguiente:

  • Si generas una excepcion desde estos metodos, esta no pasara la aplicacion que hizo la llamada
  • Las llamadas son sincronas, por lo tanto has de tener cuidado cuando se haga una llamada que tarde cierto tiempo en responder y por esto nunca la haga desde el hilo principal de la aplicacion. Si el hilo principal queda bloqueado demasiado tiempo, el sistema notificara que la aplicacion “no responde”, para evitar esto crea un hilo secundario desde donde se haga la llamada
  • Solo es posible declarar metodos, no puedes declarar campos estaticos en una interfaz AIDL

Con esto implementado ahora debemos llamar a la interfaz remota, para ello modificaremos la clase MainActivity con el siguiente codigo:

package org.example.servicioremoto;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

    private IServicioMusica servicio;

    private ServiceConnection conexion = new ServiceConnection() {

        public void onServiceConnected(ComponentName className,
                                       IBinder iservicio) {
            servicio = IServicioMusica.Stub.asInterface(iservicio);
            Toast.makeText(MainActivity.this,
                    "Conectado a Servicio", Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            servicio = null;
            Toast.makeText(MainActivity.this,
                    "Se ha perdido la conexion con el servicio",
                    Toast.LENGTH_SHORT).show();
        }
    };

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

        Button botonConectar = (Button) findViewById(R.id.boton_conectar);
        botonConectar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MainActivity.this.bindService(new Intent(
                        MainActivity.this,ServicioRemoto.class),
                        conexion, Context.BIND_AUTO_CREATE);
            }
        });

        Button botonReproducir = (Button) findViewById(R.id.boton_reproducir);
        botonReproducir.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try{
                    servicio.reproducir("Titulo");
                }
                catch(Exception e){
                    Toast.makeText(MainActivity.this,e.toString(),
                            Toast.LENGTH_SHORT).show();
                }
            }
        });

        Button botonAvanzar = (Button) findViewById(R.id.boton_avanzar);
        botonAvanzar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try{
                    servicio.setPosicion(servicio.getPosicion()+1000);
                }
                catch (Exception e){
                    Toast.makeText(MainActivity.this,e.toString(),
                            Toast.LENGTH_SHORT).show();
                }
            }
        });

        Button botonDesconectar = (Button) findViewById(R.id.boton_desconectar);
        botonDesconectar.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try{
                    MainActivity.this.unbindService(conexion);
                }
                catch (Exception e){
                    Toast.makeText(MainActivity.this,e.toString(),
                            Toast.LENGTH_SHORT).show();
                }
                servicio = null;
            }
        });
    }
}

Veamos las partes mas importantes de este codigo, primero tendremos esta linea:

private IServicioMusica servicio;

Esta linea se encargara de crear un objeto del tipo IServicioMusica la cual nos permitira hacer llamadas al objeto remoto, pasemos al siguiente bloque:

private ServiceConnection conexion = new ServiceConnection() {
     @Override
     public void onServiceConnected(ComponentName className,
                                    IBinder iservicio) {
         servicio = IServicioMusica.Stub.asInterface(iservicio);
         Toast.makeText(MainActivity.this,
                 "Conectado a Servicio", Toast.LENGTH_SHORT).show();
     }

@Override 
public void onServiceDisconnected(ComponentName className) {
     servicio = null;
     Toast.makeText(MainActivity.this,
             "Se ha perdido la conexion con el servicio",
             Toast.LENGTH_SHORT).show();
     }
};

Implementa la interfaz ServiceConnection, los escuchadores (listeners) de esta interfaz nos permite controlar cuando se produce una conexion (onServiceConnected), en este caso utilizara a servicio, el objeto creado anteriormente, la cual iniciara y nos mostrara un mensaje en pantalla, y tambien cuando se produzca una desconexion del servicio (onServiceDisconnected) lo cual seteara a servicio como null y nos mostrara un mensaje en pantalla. Nuestro siguiente bloque importante sera:

Button botonConectar = (Button) findViewById(R.id.boton_conectar);
botonConectar.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View v) {
         MainActivity.this.bindService(new Intent(
                 MainActivity.this,ServicioRemoto.class),
                 conexion, Context.BIND_AUTO_CREATE);
     }
});

En este bloque tenemos el tipo vinculo que creamos entre un elemento y el codigo Java, luego le agregaremos la posibilidad de poder “escuchar” al evento click en el mismo pero la linea que mas nos importa esta dentro del metodo onClick(), para conectarte con el servicio se llama al metodo bindService() pasa el servicio que creamos en el bloque anterior. Nuestro siguientes bloques seran:

Button botonReproducir = (Button) findViewById(R.id.boton_reproducir);
botonReproducir.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
       try{
            servicio.reproducir("Titulo");
        }
        catch(Exception e){
            Toast.makeText(MainActivity.this,e.toString(),
                    Toast.LENGTH_SHORT).show();
        }
    }
});

Button botonAvanzar = (Button) findViewById(R.id.boton_avanzar);
botonAvanzar.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        try{
            servicio.setPosicion(servicio.getPosicion()+1000);
        }
        catch (Exception e){
            Toast.makeText(MainActivity.this,e.toString(),
                    Toast.LENGTH_SHORT).show();
        }
    }
});

Estos dos son los vinculos entre un elemento del layout y del codigo Java, el primero sera para el boton reproducir y el segundo para el boton de avanzar, observen como nos da la posibilidad de poder llamar a los metodos del objeto servicio, en el primero utilizara el metodo reproducir() y donde le enviara texto como mensaje, en el segundo bloque utilizara setPosicion() y para informar a cual posicion utiliza el metodo getPosicion() y le suma 1000 ms (1s), en ambos casos utiizamos el metodo try y catch para ante algun error nos muestra el mismo por medio de un mensaje Toast y por ultimo veamos este bloque:

Button botonDesconectar = (Button) findViewById(R.id.boton_desconectar);
botonDesconectar.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
       try{
           MainActivity.this.unbindService(conexion);
        }
        catch (Exception e){
            Toast.makeText(MainActivity.this,e.toString(),
                    Toast.LENGTH_SHORT).show();
        }
        servicio = null;
    }
});

Este elemento sera el encargado de desconectarnos del servicio, para ello utilizaremos el metodo unbindService() a la cual le pasaremos el objeto conexion, si es con exito nuestro siguiente paso sera setear a servicio como null en caso contrario nos mostrara un mensaje de error por medio de Toast, esto gracias al try y catch. Nuestra ultima modificacion sera en AndroidManifest.xml donde agregaremos la siguiente modificacion dentro del bloque application:

        <service android:name=".ServicioRemoto" android:process=":remote">
            <intent-filter>
                <action
                    android:name="org.example.servicioremoto.IServicioMusica" />
            </intent-filter>
        </service>

 En este bloque definiremos un nuevo servicio, el cual estara relacionado a la clase ServicioRemoto pero agregamos una nueva opcion llamada process con la opcion :remote, la cual nos permitira que el servicio corra en su hilo propio. Dentro de intent-filter tendremos una opcion llamada action la cual nos permitira crear mas de una llamada a la interfaz de AIDL, esto es utilizada para cuando necesitamos hacer mas de una invocacion a dichas interfaces, ahora si lo compilamos y probamos obtendremos una app como se ve en el siguiente video

En el video podemos ver como si no nos conectamos con el servicio no podremos reproducir la musica, una vez conectado podemos reproducir y avanzar y si lo desconectamos la musica seguira sonando, no mostro el mensaje de desconexion y no se porque 😒, pero no podremos usar ninguna accion.

En resumen, hoy hemos visto como crear una interfaz AIDL, para que sirven, como implementarla, como crear un servicio remoto para poder conectarnos y utilizarla, espero les haya sido util sigueme en Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios