Tratar con las conexiones de red es un punto crucial en el desarrollo de Android. La descarga y el análisis de datos del formato original es una tarea común para las aplicaciones conectadas a la red y generalmente consta de dos pasos. El primer paso es conectar la aplicación a datos de origen remotos. Esta operación debe realizarse en un subproceso separado para evitar cualquier retraso en la comunicación que pueda causar una experiencia de usuario débil. El siguiente paso es leer los datos de la fuente, generalmente mediante Java Streams, y convertirlos del formato original a objetos Java. En este tutorial, aprenderá a descargar y analizar datos de OpenWeatherMap.org, un servicio en línea que almacena información sobre el clima mundial y la ofrece a los desarrolladores como un servicio web en formato JSON, XML y HTML.
El siguiente ejemplo mostrará cómo interactuar con este servicio aprovechando dos impresionantes bibliotecas de Java: GSON (para el análisis de JSON) y Picasso (para la descarga eficiente de imágenes).
De los datos al diseño
Para descargar datos sobre el tiempo actual, debe conectarse a OpenWeatherMap.org mediante la dirección http : http://api.openweathermap.org/data/2.5/weather . Al contactar la URL, se concatenará con una cadena de consulta que contiene un parámetro que indica el nombre de la ciudad que le interesa.
Por ejemplo, si quisiera saber el clima actual en Londres, enviaría una solicitud GET al dirección http://api.openweathermap.org/data/2.5/weather?q=London .
En el ejemplo, descargaremos datos sobre algunas ciudades importantes. Realizaremos una solicitud HTTP diferente para cada ciudad y todas las solicitudes tendrán el formato mencionado anteriormente.
La información recopilada se mostrará en un ListView como puede ver en la siguiente imagen:
Toda la información (temperatura, ícono a la izquierda y descripción del clima) se proporciona desde OpenWeatherMap.org en un formato JSON como este:
{ "coord":{ "lon":-0.13, "lat": 51,51 }, "sistema":{ "tipo 1, "id":5091, "mensaje":0.0192, "país":"GB", "amanecer": 1425623634, "puesta de sol": 1425664170 }, "clima":[ { "identificación": 800, "principal":"Borrar", "description":"El cielo está despejado", "icono":"01d" } ], "base":"estaciones cmc", "principal":{ "temp":283.2, "presión": 1030, "humedad": 57, "temp_min":280.93, "temp_max":285.37 }, "viento":{ "velocidad": 5.7, "grados":210 }, "nubes":{ "todos":0 }, "dt":1425664129, "id":2643743, "nombre":"Londres", "bacalao":200 }
Las bibliotecas: GSON y Picasso
GSON es una biblioteca de Java que admite la conversión de objetos JSON a Java y viceversa. Los desarrolladores pueden beneficiarse de GSON cuando tienen que convertir un archivo JSON con una estructura compleja. GSON puede hacerlo automáticamente usando la reflexión de Java. Puede manejar tipos de datos simples, objetos y colecciones comunes de Java.
Picasso es una biblioteca creada y mantenida por Square Open Source ( http://square.github.io/ ). Es útil para obtener imágenes de Internet, almacenarlas en caché para optimizar el uso de la red y vincularlas a un widget de ImageView.
Ambas bibliotecas se pueden incluir en proyectos de Android usando Maven o descargándolas como un archivo JAR desde los siguientes enlaces:
Picasso: http://square.github.io/picasso/#download
GSON:http://code.google.com/p/google-gson/
El código
En primer lugar, la estructura de cada fila en la interfaz de usuario está diseñada con el siguiente diseño:
fila.xml
<?versión xml="1.0" codificación="utf-8"?> <RelativeLayout xmlns_android="http://schemas.android.com/apk/res/android" android_layout_width="match_parent" android_layout_height="wrap_content" android_fondo="#CCFFDD" android_relleno="5dp" > <ImagenVista Android_id="@+id/img" android_layout_width="wrap_content" android_layout_height="wrap_content" android_layout_centerVertical="verdadero" /> <Vista de texto android_id="@+id/ciudad" android_layout_width="wrap_content" android_layout_height="wrap_content" android_layout_alignTop="@id/img" android_layout_marginLeft="10dp" android_layout_toRightOf="@+id/img" android_textSize="20sp" android_textStyle="negrita|cursiva" /> <Vista de texto android_id="@+id/descripción" android_layout_width="wrap_content" android_layout_height="wrap_content" android_layout_alignLeft="@id/ciudad" android_layout_below="@id/ciudad" android_textSize="18sp" /> <Vista de texto android_id="@+id/temperatura" android_layout_width="wrap_content" android_layout_height="wrap_content" android_layout_alignParentRight="verdadero" android_layout_centerVertical="verdadero" android_textSize="25sp" /> </RelativeLayout>
Producción :
Corresponde al archivo /res/layout/row.xml.
El siguiente código es la clase MainActivity:
MainActivity.java
paquete com.androidbegin.weather; importar java.io.IOException; importar java.io.InputStreamReader; importar java.io.Reader; importar java.net.MalformedURLException; importar java.net.URL; importar com.google.gson.Gson; importar android.app.ListActivity; importar android.os.AsyncTask; importar android.os.Bundle; clase pública MainActivity extiende ListActivity { // Dirección base para descarga de información Cadena estática final privada BASE_ADDR = "http://api.openweathermap.org/data/2.5/weather"; // La siguiente matriz contiene los nombres de las ciudades que nos interesan Private String[] ciudades = new String[] { "Tokio", "Londres", "Moscú", "Ottawa", "Madrid", "Lisboa", "Zúrich" }; // Referencia al objeto Adaptador. WeatherAdapter es una clase personalizada, // definido en un archivo separado adaptador WeatherAdapter privado; /* * Mezclar el objeto String que contiene el nombre de la ciudad con la base * dirección, podemos generar la dirección HTTP completa para contactar. su formato * debería ser así: * http://api.openweathermap.org/data/2.5/weather?q=London Este es el * Propósito del método getDataAddress. */ cadena privada getDataAddress(String ciudad) { return BASE_ADDR + "?q=" + ciudad; } @Anular Vacío protegido onCreate (Paquete de estado de instancia guardado) { super.onCreate(estadoDeInstanciaGuardado); // El objeto adaptador es instanciado adaptador = new WeatherAdapter(esto); // El adaptador está vinculado a ListView setListAdapter(adaptador); // Solicitamos información por separado vía HTTP para cada ciudad en el // matriz para (Cadena c : ciudades) cargarJson(c); } // Este método realiza la solicitud remota y usa GSON para analizar datos JSON protegido vacío loadJson (Cadena seleccionada) { // Ampliamos la clase AsyncTask e instanciamos un objeto directamente. new AsyncTask<Cadena, Vacío, Datos>() { // El método doInBackground contiene las operaciones más lentas que queremos // para actuar en un hilo separado @Anular datos protegidos doInBackground(String... params) { // La matriz Params contiene solo un objeto y su valor es el // cadena que representa el nombre de la ciudad String ciudadseleccionada = params[0]; // objeto de URL que usaremos para conectarnos al host remoto URL URL; intentar { // Se crea el objeto URL. El parámetro de entrada que pasamos es el // URL de contacto url = nueva URL(getDataAddress(selectedCity)); // Después de la conexión, la URL proporciona la transmisión al control remoto // datos. El objeto lector se puede usar para leerlos. Reader dataInput = new InputStreamReader(url.openStream()); // GSON necesita un flujo para leer datos. Pasamos dos parámetros // al método fromJson: el flujo a leer y la clase de datos // estructura para análisis automático Datos datos = new Gson().fromJson(dataInput, Data.class); devolver datos; } captura (Excepción URL mal formada e1) { devolver nulo; } captura (IOException e1) { devolver nulo; } } // El método onPostExecute recibe el valor de retorno de // hacerEnFondo. Recuerda que onPostExecute funciona en el principal // hilo de la aplicación vacío protegido en PostExecute (resultado de datos) { si (resultado! = nulo) { // Si no es nulo, el objeto de datos se pasa al adaptador adaptador.add(resultado); } }; // Se invoca el método de ejecución para activar el fondo // operación }.ejecutar(seleccionado); } }
MainActivity es una subclase de ListActivity, por lo que ya incluye un ListView. En el método onCreate inicializaremos el adaptador, que es una clase personalizada llamada WeatherAdapter.
El método llamado loadJson realiza la interacción de red real. Invoca doInBackground de un objeto derivado de AsyncTask que abre una conexión a la dirección HTTP que proporcionará datos y enviará el flujo de entrada a GSON como un objeto Reader.
Aquí es cuando GSON descarga y convierte datos JSON en un objeto de datos. La clase de datos se define en el proyecto y sus propiedades reflejan las de JSON. GSON solo convertirá automáticamente las propiedades para las que puede encontrar una clave correspondiente en el archivo JSON:
Datos.java
paquete com.androidbegin.weather; importar java.util.List; // GSON convertirá los datos JSON en un objeto de datos datos de clase { // Dirección base para descarga de icono Cadena estática final privada ICON_ADDR = "http://openweathermap.org/img/w/"; clase estática Tiempo { Descripción de la cadena; icono de cadena; } clase estática principal { temperatura de flotación; } List<Tiempo> tiempo; Principal Principal; Nombre de cadena; // Un método que convierte la temperatura de grados Kelvin a Celsius Cadena getTemperatureInCelsius() { temperatura flotante = temperatura principal - 273.15f; devuelve String.format("%.2f", temp); } // getIconAddress concatena la dirección base y el código específico para // el icono cadena pública getIconAddress() { return ICON_ADDR + tiempo.get(0).icono + ".png"; } public String getDescripción() { if (clima != nulo && clima.tamaño() > 0) return tiempo.get(0).descripción; devolver nulo; } }
La clase WeatherAdapter amplía BaseAdapter y anula cuatro métodos: getCount, getItem, getItemId y getView.
WeatherAdapter.java
paquete com.androidbegin.weather; importar java.util.ArrayList; importar com.squareup.picasso.Picasso; importar android.content.Context; importar android.view.LayoutInflater; importar android.view.View; importar android.view.ViewGroup; importar android.widget.BaseAdapter; importar android.widget.ImageView; importar android.widget.TextView; clase pública WeatherAdapter extiende BaseAdapter { // Datos de origen: cada uno de los objetos de datos contiene información sobre una ciudad private ArrayList<Data> ciudades = new ArrayList<Data>(); contexto de contexto privado; // Un objeto ViewHolder almacena cada una de las referencias de widgets en el diseño. // Evita un uso frecuente de findViewById clase estática privada ViewHolder { ImageView imgv; TextView ciudad; Descripción de Vista de texto; Temperatura de TextView; } WeatherAdapter público (contexto contextual) { este.contexto = contexto; } // Se agrega un objeto de datos a List<Data>. // La invocación de notificarDataSetChanged implica la actualización de ListView anular agregar (datos c) { ciudades.add(c); notificarDataSetChanged(); } // getCount devuelve el número de elementos en la estructura de datos @Anular public int getCount() { return ciudades.tamaño(); } // getItem devuelve el elemento almacenado en una posición específica de los datos // estructura @Anular Objeto público getItem(int pos) { return ciudades.get(pos); } /* * getItemId realiza el mismo trabajo que getItem pero devuelve la identificación del objeto * en lugar de la referencia al objeto */ @Anular getItemId largo público (int pos) { volver pos; } /* * getView crea un objeto View que muestra información almacenada en un Data * objeto en una posición específica en el ArrayList */ @Anular Vista pública getView(int pos, View v, ViewGroup vg) { ViewHolder vh = null; // Si v es nulo, se instancia una nueva Vista si (v == nulo) { // El LayoutInflater crea la Vista utilizando un diseño XML como modelo v = LayoutInflater.from(context).inflate(R.layout.row, null); // Las referencias a los widgets internos se almacenan en un ViewHolder vh = nuevo titular de vista (); vh.imgv = (ImageView) v.findViewById(R.id.img); vh.descripción = (TextView) v.findViewById(R.id.descripción); vh.city = (TextView) v.findViewById(R.id.city); vh.temperature = (TextView) v.findViewById(R.id.temperature); v.setTag(vh); } más // v no es nulo, por lo que no necesitamos invocar LayoutInflater vh = (ViewHolder) v.getTag(); // Recuperamos el objeto en una posición específica Datos res = (Datos) getItem(pos); // La biblioteca de Picasso descarga el icono y lo establece como fuente de ImageView Picasso.with(contexto).load(res.getIconAddress()).into(vh.imgv); // Otras vistas de texto se llenan con información de objetos de datos vh.descripción.setText(res.getDescripción()); vh.city.setText(res.nombre); vh.temperature.setText(res.getTemperatureInCelsius() + "°C"); volver v; } }
En particular, se llama al método getView cuando un objeto de datos tiene que convertirse en una vista. En el código getView se crea la estructura de la Vista inflando el diseño del archivo row.xml. Después de eso, creamos un objeto ViewHolder cuyo propósito es almacenar las referencias de las vistas en el diseño, TextViews y ImageView. Luego, el ViewHolder se vincula a la Vista que lo usará invocando el método setTag .
En el método getView, podemos ver la biblioteca de Picasso en funcionamiento. Esta herramienta ya incluye todo lo que necesitamos para obtener imágenes. La operación se realizará de forma asíncrona y Picasso se hará cargo de todas las tareas relacionadas con la descarga y el almacenamiento en caché de las imágenes.
Además, la sintaxis para la invocación de Picasso es muy sencilla y requiere una sola línea de código:
Picasso.with(contexto).load(res.getIconAddress()).into(vh.imgv);
Una vez descargado, el ícono se mostrará en ImageView al que hace referencia vh.imgv.
Finalmente, en su AndroidManifest.xml , debemos declarar los permisos para permitir que la aplicación se conecte a Internet. Abra su AndroidManifest.xml y pegue el siguiente código.
Manifiesto de Android. xml
<?versión xml="1.0" codificación="utf-8"?> <manifiesto xmlns_android="http://schemas.android.com/apk/res/android" paquete="com.androidbegin.weather" android_versionCode="1" android_versionName="1.0" > <usos-sdk android_minSdkVersion="14" android_targetSdkVersion="21" /> <usos-permiso android_name="android.permiso.INTERNET" /> <aplicación Android: permitir copia de seguridad = "verdadero" android_icono="@dibujable/ic_launcher" android_etiqueta="@cadena/nombre_aplicación" android_tema="@estilo/AppTheme" > <actividad android_nombre=".Actividad principal" android_etiqueta="@cadena/nombre_aplicación" > <intent-filter> <acción android_name="android.intent.action.MAIN" /> <categoría android_name="android.intent.category.LAUNCHER" /> </intent-filter> </actividad> </aplicación> </manifiesto>
Producción: