Titulo de la entradaTitulo de la entrada

Android: Definiciones Básicas y Desarrollo de Aplicaciones(2)

Figura 6. Ciclo de Vida http://www.androidjavadoc.com/1.0_r1_src/android/app/Activity.html
Los eventos definidos por la clase Activity son los siguientes:
● onCreate(): invocado cuando la actividad es creada por primera vez.
● onStart(): invocado cuando la actividad se hace visible para el usuario.
● onResume(): invocado cuando la actividad comienza a interactuar con el usuario.
● onPause(): invocado cuando la actividad actual se pausa y la actividad anterior se reanuda.
● onStop(): invocado cuando la actividad ya no es visible para el usuario.
● onDestroy(): invocado antes de que el sistema destruya la actividad (ya sea de manera manual o por el sistema para conservar memoria).
● onRestart(): invocado cuando la actividad ha sido detenida y se está reiniciando.
Cada uno de estos eventos es invocado a fin de poder ejecutar la lógica en cuestión en el momento correspondiente. De todas maneras, no siempre es necesario redefinir todos los métodos. De manera simplificada, podría ser suficiente la implementación de los métodos onCreate(), onResume(), onPause(), aunque esto variará según las necesidades en cada caso.
6 - Ejemplo: Desarrollo de una Aplicación Android en Eclipse
6.1 - Creación y configuración inicial del proyecto
A continuación se enumeran los pasos necesarios para crear y configurar un proyecto android en el entorno Eclipse. A modo de ejemplo se crea el proyecto de la aplicación “Hello World” que se implementará mas adelante.


1. Seleccionar “File → New → Project…”. Una vez dentro del menú, elegir la opción
“Android Project” dentro de la carpeta de Android. (Fig. 7)
Figura 7. Creación de un nuevo proyecto Android.
Nota: durante el tiempo de elaboración de este reporte, la imagen y las opciones de selección del tipo de proyecto cambiaron. Es muy probable que durante los siguientes meses vuelvan a cambiar, pero conceptualmente las opciones de creación de proyectos se mantienen básicamente invariantes.
2. Como se observa en la Fig. 8, es necesario completar una serie de datos para la configuración inicial del proyecto (entre paréntesis se indican los valores cargados en el ejemplo, aunque pueden variar según sea necesario):
● Nombre al proyecto (“HelloWorld”).
● Versión de Android a utilizar (2.3.1).
● Nombre de la aplicación (“HelloWorld”).
● Nombre del package. La convención recomendada es utilizar el nombre del dominio en orden inverso, seguido del nombre del proyecto
(“com.programaciondistribuida.HelloWorld”)
● Nombre de la actividad principal (“MainActivity”)
● Versión mínima del API (“9”)
Figura 8. Configuración inicial del proyecto Android.
3. Al presionar sobre el botón “Finish” se creará la estructura de la aplicación.
6.2 - Componentes principales
En este momento ya es posible visualizar la estructura de nuestra aplicación android. La misma cuenta con un conjunto de carpetas y archivos que se detallarán a continuación:
● src: contiene los archivos de código fuente (.java) del proyecto. En nuestro ejemplo existe el archivo “MainActivity.java”.
● gen: contiene el archivo “R.java”, autogenerado por el compilador.
● Android 2.3.1: contiene la librería de android 2.3.1 (“android.jar”).
● assets: contiene todos los “assets” usados por la aplicación, tales como HTML, archivos de texto, bases de datos, etc.
● bin: contiene las clases compiladas (.class) de nuestra aplicación.
● res: contiene los recursos usados por la aplicación. Posee las siguientes subcarpetas:
○ drawable-hdpi, drawable-mdpi, drawable-ldpi: contiene imagenes tales como los iconos de la aplicación en diferentes resoluciones (high, medium, low respectivamente).
○ layout: contiene el archivo “main.xml” en donde se encuentra especificada la interfaz de usuario (UI) en formato XML.
○ values: contiene el archivo “strings.xml”. El mismo sirve para definir todos los strings que contenga nuestra aplicación, para luego ser referenciados. Es recomendable utilizarlo, ya que si en algún momento se necesita traducir la aplicación a otro idioma, solo es necesario reemplazar el archivo strings.xml con otro equivalente en el nuevo idioma.
● AndroidManifest.xml: este es el archivo de manifiesto de nuestra aplicación. En el mismo se especifica los permisos requeridos por la aplicación, su orientación (landscape o portrait), el API mínimo necesario, la clase principal, etc..
● proguard.cfg: archivo de configuración autogenerado por ProGuard para optimizar/
ofuscar el código de la aplicación. No se debe modificar.
● project.properties: archivo de configuración autogenerado por Android Tools. No se debe modificar.
6.3 - Implementación de “Hello World”
Continuando con el proyecto creado anteriormente, se detalla el código a implementar en cada componente para finalizar la aplicación “Hello World”.
● AndroidManifest.xml: En este archivo se indica el package, la versión de la aplicación, su nombre, y la clase encargada de iniciar la aplicación (main launcher). Los valores indicados con arroba referencian a otros archivos (por ejemplo @string/app_name referencia al archivo strings.xml).

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.programaciondistribuida.HelloWorld" android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="9" />
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" >
<activity android:label="@string/app_name" android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

● main.xml: es posible crear la interfaz gráfica desde código, o bien generar un archivo XML donde se defina la misma. Para esta última opción se puede utilizar el Graphical Layout, el cual es una herramienta visual de diseño, parte del ADT. En este ejemplo se definieron dos TextView y un Button (notar que no es
obligatorio el uso de strings.xml).

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout"
android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" >
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" />
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="Esta es la aplicación de prueba Hello World" />
</LinearLayout>

● strings.xml: Este archivo almacenará los textos en formato clave/valor

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World, MainActivity!</string>
<string name="app_name">HelloWorld</string>
</resources>

● MainActivity.java: Ampliaremos el método onCreate() a fin de incorporar dinámicamente un botón, el cual además presentará un mensaje al ser clickeado:

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);

Button aButton = new Button(getBaseContext()); aButton.setText("Y este es un botón de prueba"); aButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(getBaseContext(), "Clicked", Toast.LENGTH_LONG).show();
}
});
LinearLayout layout = (LinearLayout)findViewById(R.id.layout);
layout.addView(aButton);
}
Al ejecutar la aplicación en el emulador veremos una pantalla similar a la de la Fig. 9. La Fig.
10 muestra el resultado de clickear el botón de la aplicación.
Figura 9. Ejecución de la aplicación
Figura 10. Mensaje al clickear el botón
Al ejecutar la aplicación en el emulador veremos una pantalla similar a la de la Fig. 9. La Fig.
10 muestra el resultado de clickear el botón de la aplicación.
6.4 - Instalación Mediante APK
Alternativamente a la ejecución de la aplicación desde Eclipse, es posible realizar una instalación tradicional de ésta mediante el formato de archivo APK.
Primeramente se debe generar el APK definitivo de la aplicación. Desde Eclipse, se debe acceder a “File → Export → Export Android Application”, y completar la información requerida (Fig. 11). Una vez finalizados los pasos de exportación, se contará con el APK listo para ser instalado en el dispositivo.
Para realizar la instalación del APK solo se necesita poder acceder al mismo y descargarlo desde donde esté alojado (web server, mail, dropbox, etc.).

Figura 11. Generación de APK de instalación.
7 - Comunicación entre Dispositivos
7.1 - Sockets en Java
Jave provee las clases principales para la comunicación mediante Sockets:
● TCP
○ java.net.ServerSocket: Esta clase implementa sockets de tipo servidor, el cual espera requests a través de la red.
○ java.net.Socket: Esta clase implementa sockets de tipo cliente, el cual realiza
requests hacia un ServerSocket.
○ java.io.InputStream, java.io.OutputStream: Usadas para el envío de datos entre el cliente y el servidor (y viceversa).
● UDP
○ java.net.DatagramSocket: Esta clase es usada para enviar y recibir datagram packets.. No se garantiza que los paquetes lleguen a destino ni que lleguen en el mismo orden en que fueron enviados.
○ java.net.DatagramPacket: Esta es la clase que se va a enviar y recibir como mensaje.
○ java.net.MulticastSocket: Es un DatagramSocket con capacidades adicionales de multicast. Es útil para enviar y recibir multicast packets.
7.2 - Ejemplo Sockets TCP
El siguiente ejemplo instancia dos threads encargados de realizar una conexión cliente-servidor mediante sockets TCP: Server y Client. El primero quedará en espera hasta que el segundo realice la conexión correspondiente.

import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket;

public class TestTCP {


static final int PORT = 8765;
static final String HOST = "localhost";

public static void main(String[] args)
{
new TestTCP();
}


public TestTCP()
{
Server server = new Server();
Thread serverThread = new Thread(server);


serverThread.start();

Client client = new Client();
Thread clientThread = new Thread(client);
clientThread.start();
}

public class Server implements Runnable {
public void run() {
try {
System.out.println("[Server] Esperando mensaje..."); ServerSocket serverConn = new ServerSocket(PORT); Socket socket = serverConn.accept();
ObjectInputStream fromBuffer = new ObjectInputStream(socket.getInputStream()); String datos = (String)fromBuffer.readObject();
fromBuffer.close(); socket.close(); serverConn.close();
System.out.println("[Server] Recibido: " + datos);
}
catch (Exception e) {
e.printStackTrace();
}
}
}

public class Client implements Runnable {
public void run() {
try {
Socket socket = new Socket(HOST, PORT); ObjectOutputStream toBuffer = new
ObjectOutputStream(socket.getOutputStream()); System.out.println("[Client] Enviando mensaje"); toBuffer.writeObject("MENSAJE"); toBuffer.flush();
toBuffer.close();
socket.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}

7.3 - Ejemplo Sockets UDP
El siguiente ejemplo es similar al anterior, pero utilizando UDP en lugar de TCP. Notar que en este ejemplo se trabaja mediante array de bytes, el envío es no bloqueante, y se está utilizando MulticastSockets a fin de que los hosts destinatarios puedan ser varios en lugar de solo uno.

import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket;

public class TestUDP {

public static final int PORT_UDP = 9998;
public static final String GROUP_IP = "230.0.0.1";

public static void main(String[] args)  {
new TestUDP();
}

public TestUDP()  {
Server server = new Server();
Thread serverThread = new Thread(server);
serverThread.start();

Client client = new Client();
Thread clientThread = new Thread(client);
clientThread.start();
}

public class Client extends Thread {
public void run() {
try
{
// demoramos a fin de que el server esté esperando a recibir
Thread.sleep(1000);
byte[] buf = new byte[256];
buf = "MENSAJE".getBytes();
InetAddress group = InetAddress.getByName(GROUP_IP);
DatagramPacket packet = new DatagramPacket(buf, buf.length, group, PORT_UDP); MulticastSocket socket = new MulticastSocket(PORT_UDP); System.out.println("[Client] Enviando mensaje");
socket.send(packet);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}


public class Server extends Thread {
public void run() {
try
{
MulticastSocket socket = new MulticastSocket(PORT_UDP); InetAddress group = InetAddress.getByName(GROUP_IP);
socket.joinGroup(group);

DatagramPacket packet;
byte[] buf = new byte[256];
packet = new DatagramPacket(buf, buf.length); System.out.println("[Server] Esperando mensaje..."); socket.receive(packet);
String received = new String(packet.getData()); System.out.println("[Server] Recibido: " + received); socket.leaveGroup(group);
socket.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}


}
7.4 - Sockets en Android
La API de Sockets para Android es igual a la de Java, con lo cual se simplifica el desarrollo de aplicaciones heterogéneas, dado que es posible implementar una única librería en común que contenga la funcionalidad de acceso y uso de sockets.
Por ejemplo, en Eclipse es posible crear un proyecto en común que será utilizado tanto por aplicaciones Java tradicionales como por aplicaciones Android. Luego, este proyecto puede ser referenciado desde desde otro proyecto, en la configuración Java Build Path de este último.
7.4.1 Multicasts en Android
Cabe destacar una consideración especial en lo que respecta a recepciones multicast. A menos que se configure explícitamente otra cosa, las mismas son filtradas, básicamente por razones de consumo de energía del equipo:
“Normally the Wifi stack filters out packets not explicitly addressed to this device. Acquring a MulticastLock will cause the stack to receive packets addressed to multicast addresses. Processing these extra packets can cause a noticable battery drain and should be disabled when not needed.”
Fuente: http://developer.android.com/reference/android/net/wifi/WifiManager.MulticastLock.html
Para poder recibir paquetes multicast, es necesario obtener el lock correspondiente. Esta tarea se generalmente se realiza en tres pasos: 1) Instanciar y configurar el multicastLock en el método onCreate(). 2) Requerir en el método onResume(). 3) Liberar en el método
onPause().

public void onCreate(Bundle savedInstanceState) {
...
WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE); WifiManager.MulticastLock multicastLock = wm.createMulticastLock("myDebugTag");
}

protected void onResume() {
super.onResume();
if (multicastLock!=null && !multicastLock.isHeld())
multicastLock.acquire();
}

protected void onPause() {
super.onPause();
if (multicastLock!=null && multicastLock.isHeld())
multicastLock.release();

}

Apéndice A: Práctica
1. Proponer una solución que involucre la instanciación dinámica y carga de datos de un objeto desde una aplicación cliente Android y su posterior envío a un servidor; indicándole a este último el o los métodos que debe ejecutar, para luego retornar el valor resultante.
2. Implementar mediante sockets TCP un “control remoto IP” para dispositivo móvil encargado de comandar un servicio (corriendo por ejemplo en un equipo desktop) como el de reproducción de música (acciones de reproducción, pausa, etc.) o navegación de fotografías (siguiente, anterior, etc.).
3. Desarrollar utilizando sockets UDP un “notificador de hosts conectados en una LAN”, teniendo en cuenta que no será necesario contar con un servidor central para esta tarea, sino que cada dispositivo móvil deberá mantener actualizada su tabla de hosts conectados.