PDM Avanzado Comunicacion Descarga de arquivos

De MediaWiki
Ir a la navegación Ir a la búsqueda

Introdución

Nota: Se utilizades un dispositivo real para facer esta práctica teredes que ter conexión a Internet.

Se utilizades o emulador o computador onde estea correndo debe ter conexión a Internet.


O proceso que temos que seguir para descargar un arquivo é:

  • Identificar o tipo de conexión para saber se estamos conectados a Internet. Neste punto podemos indicar o usuario que o uso da aplicación pode levar un custo se está conectado pola rede móbil.
  • Descargar o arquivo.


Para facer o anterior necesitaremos engadir ó arquivo AndroidManifiest.xml os seguintes permisos:

1 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2 <uses-permission android:name="android.permission.INTERNET" />

Xa que imos acceder a Internet e imos descargar o arquivo na tarxeta SD Externa.

Importante:: Se estades a utilizar unha versión igual ou superior 6.0 do S.O. Android e no targetSDKVersion tes unha versión igual ou superior á 23, o permiso de escritura é considerado un permiso 'perigoso' e teredes que solicitalo tamén por programación, como está explicado neste enlace.

Identificar o tipo de rede

Para obter información sobre a rede necesitamos engadir o seguinte permiso o arquivo de AndroidManifiest.xml:

1 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


Cando estamos conectados cun dispositivo móbil a unha rede temos tres posibilidades:

  • Móbil
  • Ethernet (cable de rede)
  • WIFI


Para obter o tipo de rede ó que estamos conectados deberemos usar un obxecto da clase ConnectivityManager.

Para obtelo, deberemos chamar ó método getSystemService da seguinte forma:

1 ConnectivityManager connMgr = (ConnectivityManager)contexto.getSystemService(Context.CONNECTIVITY_SERVICE);


Unha vez feito isto podemos obter información acerca da rede na que estamos conectados mediante un obxecto da clase NetworkInfo:

1 NetworkInfo networkInfo=null;
2 networkInfo = connMgr.getActiveNetworkInfo();


NetworkInfo informa se estamos conectados e o tipo de rede ó que estamos conectados:

 1                 if (networkInfo != null && networkInfo.isConnected()) {
 2 			switch(networkInfo.getType()){
 3 				case ConnectivityManager.TYPE_MOBILE:
 4 					break;
 5 				case ConnectivityManager.TYPE_ETHERNET:
 6 					// ATENCION API LEVEL 13 PARA ESTA CONSTANTE
 7 					break;
 8 				case ConnectivityManager.TYPE_WIFI:
 9 					// NON ESTEAS MOITO TEMPO CO WIFI POSTO
10 					// MAIS INFORMACION EN http://www.avaate.org/
11 					break;
12 			}
13 		}
14 		else {
15 			// NON TEMOS REDE
16 		}


Nota: Se necesitamos manexar a conexión WIFI podemos facer uso da clase WifiManager: http://developer.android.com/reference/android/net/wifi/WifiManager.html e engadir o permiso android.permission.ACCESS_WIFI_STATE no AndroidManifiest.xml.

Descargar o arquivo

Agora imos resolver o problema de descargar un arquivo dende Internet.

Teremos que utilizar un InputStream para lela e un OutputStream para escribila a disco.

Nota: O manexo de arquivos xa os vimos neste punto: http://wiki.cifprodolfoucha.es/index.php?title=PDM_Avanzado_Datos_Persistentes_Arquivos

O cartafol onde imos gardala será o predeterminado polo S.O. para gardar imaxes.


O proceso será o seguinte:

  • Determinamos a dirección URL para descargar o arquivo:
1 	        private String IMAXE_DESCARGAR="http://www.aspedrinas.com/imagenes/santiago/santiago1.jpg";
2 		URL url=null;
3 		try {
4 			url = new URL(IMAXE_DESCARGAR);
5 		} catch (MalformedURLException e1) {
6 			// TODO Auto-generated catch block
7 			e1.printStackTrace();
8 			return;
9 		}


Se queremos obter só o nome do arquivo da URL podemos poñer este código:

1 		String nomeArquivo = Uri.parse(IMAXE_DESCARGAR).getLastPathSegment();


  • Agora necesitamos ler dende Internet o arquivo a descargar.

A idea é moi sinxela. Temos que facer unha conexión có Servidor. Ó establecer dita conexión podemos especificar unha serie de parámetros coma son:

  • Tempo máximo de lectura (en milisegundos).
  • Tempo máximo para facer a conexión.
  • Método de transmisión (GET ou POST, entre outros).

Todo isto se fai cun obxecto da clase HttpURLConnection:

1 	        HttpURLConnection conn=null;
2 
3 		conn = (HttpURLConnection) url.openConnection();
4 	        conn.setReadTimeout(10000);  	/* milliseconds */
5 	        conn.setConnectTimeout(15000);  /* milliseconds */
6 	        conn.setRequestMethod("POST");
7 	        conn.setDoInput(true);			/* Indicamos que a conexión vai recibir datos */
8 	        
9 	        conn.connect();

Ó facer o intento de conexión o servidor web pode darnos unha resposta de que todo é correcto ou un erro (por exemplo, recursos non atopado, que non deixe descargar,...) Comprobaremos por tanto que non tivemos ningún erro:

1 	        int response = conn.getResponseCode();
2 	        if (response != HttpURLConnection.HTTP_OK){	
3                         // TRATAREMOS O ERRO
4 			return;
5 	        }


Nota: Aínda que dependemos do servidor, podemos obter o tamaño do que queremos descargar da seguinte forma:

1 	int fileLength = conn.getContentLength();	// Non funciona sempre


Isto nos pode servir para modificar unha barra de progreso a indicar canto lle queda por descargar....

En caso de que o servidor non resposnda filelength terá de valor -1.


  • Definimos o OutputStream (para gardar o arquivo lido) e un InputStream para ler o contido de Internet:
1         OutputStream os;
2         InputStream in;
3         ......
4 	os = new FileOutputStream(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES+File.separator+nomeArquivo));
5         in = conn.getInputStream();

Nota: Necesitaremos usar try...catch


  • Agora só queda ler o contido e escribilo ó mesmo tempo como fixemos na unidade de arquivos:
1 	    byte data[] = new byte[1024];	// Buffer a utilizar
2             int count;
3             while ((count = in.read(data)) != -1) {
4                 os.write(data, 0, count);
5             }
6             os.flush();
7             os.close();
8             in.close();
9             conn.disconnect();


  • O PUNTO MÁIS IMPORTATE: Todo o anterior ten que facerse nun fío separado do principal.

Como se vimos na Unidade de Threads e AsynTask o podemos facer utilizando un Thread ou un AsynTask.



Aclaración: Estamos a amosar como descargar un arquivo calquera de Internet e unha vez baixado facer algunha operación sobre el.

Se queremos descargar unha imaxe podemos cargar directamente un Bitmap dende o InputStream desta forma:

1 	        Bitmap bitmap = BitmapFactory.decodeStream(in);

Caso Práctico: Descargar unha imaxe dende Internet

O obxectivo desta práctica é comprobar como podemos descargar un arquivo dende Internet (neste exemplo é unha imaxe).

A dirección URL está posta na propia aplicación do exemplo. O alumno pode cambiala por unha súa pero tendo en conta que hai sitios web que van responder cun erro de conexión (403, forbidden) e outros teñen unha visualización diferente para móbiles que para PC, polo que unha dirección probada dende un PC pode funcionar pero que cando se pon na aplicación móbil pode dar outro erro (307, redirect).

Neste exemplo utilizamos un Thread sen paso de mensaxes.

Isto non será a forma adecuada de implementalo, xa que o lóxico sería premer o botón de Descargar Imaxe e 'informar' a activity cando rematou de descargala.

Unha forma elegante de facelo é utilizar un diálogo de progreso como vimos nas unidades anteriores.

PDM Avanzada DatosPersistentes 20.jpg


Preparación

Engadimos no AndroidManifiest.xml os seguintes permisos:

   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.INTERNET" />
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


  • Lembrar que no caso de utilizar un emulador será necesario que teña a tarxeta externa SD.


Se non o temos creado antes, crearemos un novo paquete de nome: Comunicacion como un subpaquete do teu paquete principal.



  • Dentro do paquete Comunicacion crear unha nova 'Empty Activity' de nome: UD09_01_Internet de tipo Launcher e sen compatibilidade.
Modifica o arquivo AndroidManifiest.xml e engade unha label á activity como xa vimos na creación do proxecto base.




Código da Activity

Código do layout xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:app="http://schemas.android.com/apk/res-auto"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     tools:context=".Comunicacion.UD09_01_Internet">
 8 
 9     <ImageView
10         android:id="@+id/imgvwImaxe_UD09_01_Internet"
11         android:layout_width="0dp"
12         android:layout_height="0dp"
13         android:layout_marginBottom="8dp"
14         android:layout_marginEnd="8dp"
15         android:layout_marginStart="8dp"
16         android:layout_marginTop="8dp"
17         android:scaleType="fitCenter"
18         android:src="@mipmap/ic_launcher_round"
19         app:layout_constraintBottom_toBottomOf="parent"
20         app:layout_constraintEnd_toEndOf="parent"
21         app:layout_constraintStart_toStartOf="parent"
22         app:layout_constraintTop_toTopOf="@+id/guideline5" />
23 
24     <android.support.constraint.Guideline
25         android:id="@+id/guideline5"
26         android:layout_width="wrap_content"
27         android:layout_height="wrap_content"
28         android:orientation="horizontal"
29         app:layout_constraintGuide_percent="0.56" />
30 
31     <Button
32         android:id="@+id/btnDescargar_UD09_01_Internet"
33         android:layout_width="wrap_content"
34         android:layout_height="wrap_content"
35         android:layout_marginEnd="8dp"
36         android:layout_marginStart="8dp"
37         android:layout_marginTop="8dp"
38         android:text="Descargar Imaxe"
39         app:layout_constraintEnd_toEndOf="parent"
40         app:layout_constraintStart_toStartOf="parent"
41         app:layout_constraintTop_toTopOf="parent" />
42 
43     <Button
44         android:id="@+id/btnVisualizar_UD09_01_Internet"
45         android:layout_width="wrap_content"
46         android:layout_height="wrap_content"
47         android:layout_marginEnd="8dp"
48         android:layout_marginStart="8dp"
49         android:layout_marginTop="8dp"
50         android:text="Visualizar Imaxe"
51         app:layout_constraintEnd_toEndOf="parent"
52         app:layout_constraintStart_toStartOf="parent"
53         app:layout_constraintTop_toBottomOf="@+id/btnDescargar_UD09_01_Internet" />
54 </android.support.constraint.ConstraintLayout>



Código da clase UD09_01_Internet
Obxectivo: Descargar unha imaxe de Internet.

  1 package es.cursoandroid.cifprodolfoucha.aprendiendo.Comunicacion;
  2 
  3 import android.Manifest;
  4 import android.app.Activity;
  5 import android.content.Context;
  6 import android.content.pm.PackageManager;
  7 import android.graphics.Bitmap;
  8 import android.graphics.BitmapFactory;
  9 import android.net.ConnectivityManager;
 10 import android.net.NetworkInfo;
 11 import android.net.Uri;
 12 import android.os.Build;
 13 import android.os.Bundle;
 14 import android.os.Environment;
 15 import android.util.Log;
 16 import android.view.View;
 17 import android.widget.Button;
 18 import android.widget.ImageView;
 19 import android.widget.Toast;
 20 
 21 import java.io.File;
 22 import java.io.FileNotFoundException;
 23 import java.io.FileOutputStream;
 24 import java.io.IOException;
 25 import java.io.InputStream;
 26 import java.io.OutputStream;
 27 import java.net.HttpURLConnection;
 28 import java.net.MalformedURLException;
 29 import java.net.URL;
 30 
 31 import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
 32 
 33 public class UD09_01_Internet extends Activity {
 34     public static enum TIPOREDE{MOBIL,ETHERNET,WIFI,SENREDE};
 35     private TIPOREDE conexion;
 36 
 37     private final String IMAXE_DESCARGAR="https://www.aspedrinas.com/imagenes/santiago/santiago1.jpg";
 38     private File rutaArquivo;
 39     private Thread thread;
 40 
 41     // Usado por si necesitamos diferentes permisos, para identificar cual de ellos es
 42     private final int CODIGO_IDENTIFICADOR_PERMISO =1;
 43 
 44     public void pedirPermiso(){
 45         if (Build.VERSION.SDK_INT>=23){
 46             int permiso = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
 47             if (permiso !=PackageManager.PERMISSION_GRANTED) {
 48                 requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODIGO_IDENTIFICADOR_PERMISO);
 49             }
 50         }
 51 
 52     }
 53 
 54     @Override
 55     public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
 56 
 57         Button btnSacarFoto = findViewById(R.id.btnDescargar_UD09_01_Internet);
 58         switch (requestCode) {
 59             case CODIGO_IDENTIFICADOR_PERMISO: {
 60                 // Se o usuario premeou o boton de cancelar o array volve cun null
 61                 if (grantResults.length > 0
 62                         && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 63                     // PERMISO CONCEDIDO
 64                     btnSacarFoto.setEnabled(true);
 65                 } else {
 66                     // PERMISO DENEGADO
 67                     btnSacarFoto.setEnabled(false);
 68                     Toast.makeText(this,"É NECESARIO O PERMISO PARA GARDAR A IMAXE NA SD CARD",Toast.LENGTH_LONG).show();
 69                 }
 70                 return;
 71             }
 72 
 73             // Comprobamos os outros permisos
 74 
 75         }
 76     }
 77 
 78 
 79     private TIPOREDE comprobarRede(){
 80         NetworkInfo networkInfo=null;
 81 
 82         ConnectivityManager connMgr = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
 83         networkInfo = connMgr.getActiveNetworkInfo();
 84 
 85         if (networkInfo != null && networkInfo.isConnected()) {
 86             switch(networkInfo.getType()){
 87                 case ConnectivityManager.TYPE_MOBILE:
 88                     return TIPOREDE.MOBIL;
 89                 case ConnectivityManager.TYPE_ETHERNET:
 90                     // ATENCION API LEVEL 13 PARA ESTA CONSTANTE
 91                     return TIPOREDE.ETHERNET;
 92                 case ConnectivityManager.TYPE_WIFI:
 93                     // NON ESTEAS MOITO TEMPO CO WIFI POSTO
 94                     // MAIS INFORMACION EN http://www.avaate.org/
 95                     return TIPOREDE.WIFI;
 96             }
 97         }
 98         return TIPOREDE.SENREDE;
 99     }
100 
101 
102     private void descargarArquivo() {
103         URL url=null;
104         try {
105             url = new URL(IMAXE_DESCARGAR);
106         } catch (MalformedURLException e1) {
107             // TODO Auto-generated catch block
108             e1.printStackTrace();
109             return;
110         }
111 
112         HttpURLConnection conn=null;
113         String nomeArquivo = Uri.parse(IMAXE_DESCARGAR).getLastPathSegment();
114         rutaArquivo = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),nomeArquivo);
115         try {
116 
117             conn = (HttpURLConnection) url.openConnection();
118             conn.setReadTimeout(10000);     /* milliseconds */
119             conn.setConnectTimeout(15000);  /* milliseconds */
120             conn.setRequestMethod("POST");
121             conn.setDoInput(true);                  /* Indicamos que a conexión vai recibir datos */
122 
123             conn.connect();
124 
125             int response = conn.getResponseCode();
126             if (response ==HttpURLConnection.HTTP_MOVED_TEMP){  // Se dera un código 302, sería necesario volver a descargar cunha uri nova que ven indicada no método getHeaderField("Location")
127                 // url = new URL(conn.getHeaderField("Location"));
128                 // Neste caso habería que refacer o método xa que teríamos que volver a poñer o mesmo código anterior de conexión pero con esta Url nova.
129             }
130             else if (response != HttpURLConnection.HTTP_OK){
131                 // Algo foi mal, deberíamos informar a Activity cunha mensaxe
132                 return;
133             }
134             
135             OutputStream os = new FileOutputStream(rutaArquivo);
136             InputStream in = conn.getInputStream();
137             byte data[] = new byte[1024];   // Buffer a utilizar
138             int count;
139             while ((count = in.read(data)) != -1) {
140                 os.write(data, 0, count);
141             }
142             os.flush();
143             os.close();
144             in.close();
145             conn.disconnect();
146             Log.i("COMUNICACION","ACABO");
147         }
148         catch (FileNotFoundException e) {
149             // TODO Auto-generated catch block
150             Log.e("COMUNICACION",e.getMessage());
151         } catch (IOException e) {
152             // TODO Auto-generated catch block
153             e.printStackTrace();
154             Log.e("COMUNICACION",e.getMessage());
155         }
156 
157     }
158 
159     private void xestionarEventos(){
160         Button btnDescargarImaxe=(Button) findViewById(R.id.btnDescargar_UD09_01_Internet);
161         btnDescargarImaxe.setOnClickListener(new View.OnClickListener() {
162 
163             @Override
164             public void onClick(View v) {
165                 // TODO Auto-generated method stub
166                 thread = new Thread(){
167 
168                     @Override
169                     public void run(){
170                         descargarArquivo();
171                     }
172                 };
173                 thread.start();
174                 Toast.makeText(getApplicationContext(),"A imaxe estase a descargar nun fío separado. Deberíamos enviar unha mensaxe cando remate para saber se foi todo ben",Toast.LENGTH_LONG).show();
175 
176 
177             }
178         });
179         Button btnVisualizarImaxe=(Button) findViewById(R.id.btnVisualizar_UD09_01_Internet);
180         btnVisualizarImaxe.setOnClickListener(new View.OnClickListener() {
181 
182             @Override
183             public void onClick(View v) {
184                 // TODO Auto-generated method stub
185                 if ((thread!=null) && (!thread.isAlive())){
186                     ImageView imgviewImaxe = (ImageView)findViewById(R.id.imgvwImaxe_UD09_01_Internet);
187                     Bitmap bmpImaxe = BitmapFactory.decodeFile(rutaArquivo.getAbsolutePath());
188                     imgviewImaxe.setImageBitmap(bmpImaxe);
189                 }
190 
191             }
192         });
193 
194     }
195 
196     @Override
197     protected void onCreate(Bundle savedInstanceState) {
198         super.onCreate(savedInstanceState);
199         setContentView(R.layout.activity_ud09_01__internet);
200 
201         pedirPermiso();
202 
203         conexion = comprobarRede();
204         if (conexion==TIPOREDE.SENREDE){
205             Toast.makeText(this, "NON SE PODE FACER ESTA PRACTICA SEN CONEXION A INTERNET", Toast.LENGTH_LONG).show();
206             finish();
207         }
208         else{
209             Toast.makeText(this, "Estamos conectados...", Toast.LENGTH_LONG).show();
210         }
211 
212         xestionarEventos();
213     }
214 }
  • Liñas 34,35,79-99: Como vimos na explicación inicial, comprobamos o tipo de conexión do noso dispositivo móbil. O método vai devolver un valor dun tipo ENUM (liña 34).
  • Liñas 102-157: Descargamos o arquivo como vimos na explicación inicial.
  • Liñas 161-178: Xestionamos o evento de Click sobre o botón 'Descargar Imaxe'. Creamos un fío novo de execución e chamamos ó método 'descargarArquivo'.
  • Liñas 180-192: Comprobamos que o fío rematou de executarse e nese caso cargamos a imaxe descargada.
  • Liñas 204-207: En caso de non estar conectado a ningunha rede finalizamos a activity.




-- Ángel D. Fernández González e Carlos Carrión Álvarez -- (2014).