Este artículo explicará de qué forma se puede enviar y recibir datos hacia un servidor desde una aplicación Android, con el propósito de introducir a los desarrolladores a la creación de servicios en la nube desde fuentes de datos externas. En primera instancia se expondrán conocimientos introductorios sobre el protocolo HTTP, el cual habilita el intercambio de información. Luego verás a través de un ejemplo práctico como leer y postear comentarios en un servidor externo. Y Adicionalmente se integrarán las tareas asíncronas para asegurar el correcto funcionamiento del hilo principal.
Finalmente se concluye con aspectos adicionales del mecanismo usado para la conexión y observaciones que pueden ayudarte en la conexión de las aplicaciones Android.
- Introducción al funcionamiento del protocolo HTTP
- Ejemplo: Aplicación Monstalkers para lectura y posteo de Comentarios
- Usar el cliente HttpURLConnection
- Comprobar si la conexión a la red es posible
- Abrir la conexión hacia el servidor
- Obtener datos con el método GET
- Postear información con el método POST
- Subir un archivo hacia un servidor con el método POST
- Establecer peticiones HTTP en segundo plano usando AsyncTask
- Otros métodos para peticiones HTTP en Android
- Conclusiones y Recomendaciones
CONTENIDO
Introducción al funcionamiento del protocolo HTTP
Las conexiones que haremos desde nuestras aplicaciones Android hacia los servidores web siguen un estándar internacional llamado Hipertext Transfer Protocol HTTP. Este protocolo consiste en reglas sencillas de transferencia de recursos o archivos entre equipos interconectados a una red.
Al equipo que hace la petición para enviar u obtener datos se le llama Cliente y al que contiene el recurso o el espacio para almacenar es llamado Servidor. La comunicación se establece a través de una petición de envío, la cual contiene los datos del cliente, como el sistema operativo que usa, el navegador web desde donde se hace la petición, la ubicación del archivo solicitado (URL), etc.
Una petición puede tener múltiples objetivos dependiendo del método que se elija. Los tipos de peticiones más comunes son el Retorno de datos y la Publicación de datos. Técnicamente se les conoce como los métodos GET
y POST
.
La búsqueda de una página web a través de la URL es un buen ejemplo de una petición GET
, donde el cliente especifica la URL y el servidor retorna en la información HTML necesaria para que el navegador realice su respectivo parsing.
El ejemplo más popular del método POST
se refleja en el envío de información desde un formulario hacia la base de datos del servidor. Aquí hacemos lo contrario, dictaminamos los datos y el servidor los recibe para almacenarlos y darles persistencia.
Cada vez que entras a hermosa programación desde tu navegador la comunicación HTTP sería similar a esta:
GET /index.html
Host: www.hermosaprogramación.com
User Agent: Mozilla/4.0 (Compatible; MSIE 7.0; Windows NT 6.0)
Accept: */*
Dicha petición es recibida por el Servidor, quién arroja la respuesta dependiendo del estado del recurso solicitado, que en este caso es el archivo HTML que representa el Home de Hermosa Programación.
HTTP/1.0 200 OK
Date: Fri, 27 Dec 2014 23:59:59 GMT
Content-Type: text/html
Content-Length: 1467
<html>
<body>
<h1>Hermosa Programación</h1>
(El contenido restante)
.
.
.
</body>
</html>
Como se nota, el estado de respuesta es 200 OK
, un código que significa que todo marcha sobre ruedas con este recurso. En seguida se ubican metadatos asociados a la fecha de consulta, el tipo de contenido enviado, su tamaño y al final el contenido HTML. Con esta información, tu navegador web ya puede implementar la lógica necesaria para mostrar la página web.
Ahora veamos el esquema de una petición POST
, cuyo objetivo es enviar los datos Nombre
y Precio
hacia la base de datos del servidor:
POST /data/Insertar-Productos.php HTTP/1.0
From: frog@jmarshall.com
User-Agent: HTTPTool/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 25
Nombre=Motorola&Precio;=2000
En este caso se indica la ruta de ubicación donde se publicaran los resultados, una cuenta de correo como el origen de la petición, el tipo de contenido estándar para los formularios, el tamaño de los datos y finalmente dos pares clave-valor encriptados con UTF-8.
Si deseas que tus aplicaciones tengan acceso total a la conexión de tu dispositivo Android es necesario que incluyas los siguientes permisos en tu archivo AndroidManifiest.xml
:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Ejemplo: Aplicación Monstalkers para lectura y posteo de Comentarios
Monstalkers es una aplicación de ejemplo creada para mostrar el uso del cliente HttpURLConnection
. Su objetivo es mostrar como cargar comentarios almacenados en un servidor externos a través del parsing de un arreglo JSON. Adicionalmente permitirá al usuario crear y enviar su propio comentario al servidor. Puedes descargar el código aquí:
En cuanto a diseño, Monstalkers se compone de dos actividades. La primera es la actividad principal llamada MainActivity
, donde se muestra un ListView
que despliega todos los comentarios por orden cronólogico.Su Action Bar tiene dos botones de acción, los cuales permiten añadir un nuevo comentario y refrescar la vista respectivamente.
Cuando se presiona el action button para añadir, inmediatamente se despliega la actividad FormActivity
, la cual contiene un sencillo campo de edición para digitar el comentario. El usuario decidirá el fin del comentario presionando el botón Enviar o Cancelar.
Usar el cliente HttpURLConnection
La clase HttpUrlConnection
del paquete java.net.*
permite a nuestros dispositivos android asumir las características de un cliente HTTP ligero. Su funcionamiento está condicionado a las versiones superiores a Gingerbread. Para versiones anteriores debes usar el cliente HttpClient
de Apache. Con esta clase podremos recibir y enviar información a través de la web, lo que potenciará nuestras aplicaciones Android. A continuación se muestran los pasos que se deben realizar para establecer una conexión existosa.
Comprobar si la conexión a la red es posible
Antes de iniciar el cliente Http debes comprobar si la conexión del dispositivo está habilitada, ya que puede ser posible que el Wi-fi no esté disponible o simplemente la conexión de red está fuera del rango. Para comprobar el estado de conexión usaremos los métodos getActiveNetworkInfo()
e isConnected()
:
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null & networkInfo.isConnected()) {
// Operaciones http
} else {
// Mostrar errores
}
Se ha usado la clase ConnectivityManager
para obtener las características actuales de la conexión. Esta información la guardamos en un elemento del tipo NetworkInfo
con el método getActiveNetworkInfo()
. Luego comprobamos si se retornó algún dato y si además el dispositivo está conectado con isConnected()
.
Abrir la conexión hacia el servidor
El primer paso para iniciar la comunicación es abrir la conexión hacia el recurso alojado en el servidor. Para ello se usa el método openConnection()
de la clase URL
. El resultado que se obtenga debe ser casteado a HttpUrlConnection
para que el cliente sea instanciado:
URL url = new URL("http://monstalkers.hostoi.com/data/get_all_comments.php");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
Obtener datos con el método GET
Si deseas descargar datos desde la URL especificada simplemente usas el método getIntpuStream()
para obtener el flujo de datos asociado al recurso que se encuentra en esa dirección:
URL url = new URL("http://monstalkers.hostoi.com/data/get_all_comments.php");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
// Acciones a realizar con el flujo de datos
finally {
urlConnection.disconnect();
}
Es importante que al finalizar tus operaciones de conexión liberes la memoria asociada a la instancia de la conexión realizada. Para ello usa el método disconnect()
, el cual pone a disposición de nuevo una futura reconexión.
Recuerda que un tipo InputStream
debe ser decodificado para interpretar su contenido, ya sea texto plano, imagen, JSON, audio, etc. Dependiendo del objetivo así mismo debes usar los métodos y técnicas correspondientes.
Postear información con el método POST
Si deseas publicar información en un servidor debes abrir la conexión al igual que con GET
. Luego se indica a la conexión que se permite el envío de datos hacia el servidor con el método setDoOutput()
.
Seguidamente se declaran los datos que se enviarán al destino, para los cuales debes declarar el tamaño que ocuparán para ser transmitidos por el flujo. Si su tamaño es fijo, entonces usa el método setFixedLengthStreamingMode()
, quién recibe como parámetro la cantidad de bytes. Si el tamaño es incierto (normalmente esta situación se da en transmisiones Streaming), entonces usa setChunkedStreamingMode()
.
Reuniendo todas estas características, Monstalkers hace su publicación de comentarios de la siguiente forma:
// Obtener la conexión
HttpURLConnection con = null;
try {
// Construir los datos a enviar
String data = "body=" + URLEncoder.encode(comment,"UTF-8");
con = (HttpURLConnection)url.openConnection();
// Activar método POST
con.setDoOutput(true);
// Tamaño previamente conocido
con.setFixedLengthStreamingMode(data.getBytes().length);
// Establecer application/x-www-form-urlencoded debido a la simplicidad de los datos
con.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
OutputStream out = new BufferedOutputStream(con.getOutputStream());
out.write(data.getBytes());
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(con!=null)
con.disconnect();
}
El código anterior muestra como se envía el cuerpo de un comentario digitado por el usuario en la actividad ActivityForm
. En primera instancia se construye una cadena llamada data
, donde guardaremos el par clave-valor del comentario con el estándar de formularios "clave= valor". Para ello usamos la clave "body"
como representación del cuerpo del comentario como tal, cuyo valor se encuentra almacenado en la variable comment
.
Luego se encriptó el parámetro con el método estático encode
de la clase URLEncoder
. Esta clase permite ajustar la cadena al sistema UTF-8 para que sea compatible con el tipo de contenido application/x-www-form-urlencoded
, de acuerdo a los estándares de envío de información para el método POST
establecido por la W3C.
Aunque en este ejemplo se usa un solo par clave-valor, a menudo necesitarás enviar múltiples pares. Para ello solo debes concatenarlos con el carácter '&'
de acuerdo al estándar.
El siguiente paso fue declarar el envío de datos con setDoOutput()
, seguido se estableció el valor en bytes de nuestros datos y además se añadió el tipo de contenido con setRequestProperty()
. Este método te permite añadir cabeceras a la petición que envíes.
Finalmente se obtiene acceso al sistema de archivos del servidor con getOutputStream()
, el cual retorna en un flujo de datos en donde escribirás los datos de los comentarios a través de los métodos flush()
y write()
.
Subir un archivo hacia un servidor con el método POST
En la sección anterior se usó el tipo de contenido application/x-www-form-urlencoded
para enviar datos atómicos hacia el servidor, pero...¿que hacer para subir un archivo?
Una de las formas es usar el tipo de contenido multipart/form-data
del estandar W3C para enviar datos binarios de gran tamaño. Pero la verdad me resulta complicado preparar todas las sentencias de texto requeridas para dirigir el flujo de información. En su lugar puedes publicar el archivo directamente como bytes puros a través de getOutputStream()
de la siguiente forma:
File file = new File("ruta_del_archivo");
URL url = new URL("http://www.tuservidor.com/");
HttpURLConnection con = null;
try{
con = (HttpURLConnection)url.openConnection();
// Activar método POST
con.setDoOutput(true);
// Tamaño desconocido
con.setFixedLengthStreamingMode(0);
OutputStream out = con.getOutputStream();
// Usas tu método ingeniado para convertir el archivo a bytes
out.write(convertfileToBytes(file));
out.flush();
out.close();
}finally{
if(con!=null)
con.disconnect();
}
Establecer peticiones HTTP en segundo plano usando AsyncTask
El tiempo que tarda la transmisión de datos en la red depende de muchos factores, como el tamaño de la información a intercambiar, los tiempos de latencia, la congestión del servidor o incluso la ejecución de múltiples tareas distintas en el sistema operativo. Cualquiera de estas situaciones puede prolongar el tiempo de una petición indefinidamente.
Pero como tú ya sabes, las tareas asíncronas nos auxilian en estas situaciones. Solo debemos crear una nueva instancia de la clase AsyncTask
e incluir la petición al servidor en el método doInBackground()
y luego actualizar los resultados visuales en onPostExecute()
.
Lee también Uso de Hilos y tareas asíncronas (AsyncTask) en Android
Por ejemplo, la obtención de todos los comentarios que se han guardado en el servidor de monstalker ha sido encapsulada en una clase llamada GetCommentsTask
:
public class GetCommentsTask extends AsyncTask<URL, Void, List<String>> {
@Override
protected List doInBackground(URL... urls) {
List comments = null;
try {
// Establecer la conexión
con = (HttpURLConnection)urls[0].openConnection();
// Obtener el estado del recurso
int statusCode = con.getResponseCode();
if(statusCode!=200) {
comments = new ArrayList
();
comments.add("El recurso no está disponible");
return comments;
}
else{
/*
Parsear el flujo con formato JSON a una lista de Strings
que permitan crean un adaptador
*/
InputStream in = new BufferedInputStream(con.getInputStream());
JSONCommentsParser parser = new JSONCommentsParser();
comments = parser.readJsonStream(in);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
con.disconnect();
}
return comments;
}
@Override
protected void onPostExecute(List s) {
/* Se crea un adaptador con el el resultado del parsing
que se realizó al arreglo JSON
*/
ArrayAdapter adapter = new ArrayAdapter(
getBaseContext(),
android.R.layout.simple_list_item_1,
s);
// Relacionar adaptador a la lista
comments.setAdapter(adapter);
}
}
La secuencia de la tarea tendría la siguiente estructura:
- Abrir la conexión hacia la URL en
doInBackground()
. - Obtener el estado del recurso con
getResponseCode()
. Si la respuesta no es 200 OK, entonces preparar un valor de retorno que indique el error sucedido. - En caso de éxito, entonces se obtiene el flujo de datos con
getInputStream()
- Parsear o formatear dependiendo del flujo. En este caso particular la información viene como un arreglo de objetos JSON. Para traducirlo en información legible se ha implementado la clase
JSONCommentsParser
, la cual usa la claseandroid.util.JsonReader
para leer los bytes y recorrer la estructura, de tal forma que al final se retornen los datos que interesan (una lista de comentarios). - Finalmente se realizan las acciones necesarias en
onPostExecute()
. MonsTalkers implementa crea un adaptador nuevo basado en el parámetro recibido y luego lo asocia alListView
.
Si deseas formatear un flujo a Bitmap
puedes usar el método estático decodeStream()
de la clase BitmapFactory
, el cual recibe como parámetro un tipo InputStream
. Si en lugar de un bitmap, tienes texto plano, entonces puedes usar un objeto InputStreamReader
, el cual puede convertir bytes a caracteres (char
) y finalmente convertir a String
.
Otros métodos para peticiones HTTP en Android
El método por defecto asociado a un objeto HttpURLConnection
es GET
. Cuando abres la conexión es sencillo obtener el flujo asociado a la URL con tan solo usar getInputStream()
. El método POST
tampoco es complicado de emplear, ya que solo se activa con setDoOutput()
para estructurar los datos a enviar y escribir sobre el flujo de salida.
Pero en el caso de otros métodos como OPTIONS
, HEAD
, PUT
, DELETE
o TRACE
debes usar el método setRequestMethod()
para hacer efectiva la petición. No obstante este también se puede usar para especificar los métodos GET
y POST
.
con.setRequestMethod("GET");
Otra característica adicional de la clase HttpURLConnection
es la capacidad de establecer los tiempos de caducidad de conexión y tiempos de caducidad de lectura a través de los métodos setConnectTimeout()
y setReadTimeout()
:
// Expirar a los 10 segundos si la conexión no se establece
con.setConnecTimeout(10000)
// Esperar solo 15 segundos para que finalice la lectura
con.setReadTimeout(15000)
Conclusiones y Recomendaciones
El cliente HttpURLConnection
nos permite usar métodos de petición de forma eficaz y amigable. Solo se indica la ubicación del recurso a través de una URL, luego defines tu propósito. Si el objetivo es obtener información, entonces asegúrate de crear un método que parsee la información con el formato correcto.
Por el contrario si se enviarán datos, entonces procura constituir pares clave-valor siguiendo el estándar. Adicionalmente recuerda usar tareas asíncronas ejecutar tus peticiones, de lo contrario el framework de Android te arrojará una excepción perteneciente al modo restringido, donde se te explica que no es permitido ejecutar peticiones en el hilo principal.
Aunque este cliente es una alternativa congruente, Google recomienda usar la librería HTTP Volley. Esta herramienta permite transmitir datos a través de la red en un alto nivel, debido a que establece una capa superior para evitar manejar el flujo de datos directamente, relaciona la fase de parsing con facilidad y optimiza el rendimiento de las operaciones. Pero este será un tema para otro artículo.
James Revelo Urrea - Desarrollador independiente http://www.hermosaprogramacion.com