Hay muchas formas de descargar archivos. A continuación publicaré las formas más comunes; depende de usted decidir qué método es mejor para su aplicación.
1. Use AsyncTask
y muestre el progreso de la descarga en un cuadro de diálogo
Este método le permitirá ejecutar algunos procesos en segundo plano y actualizar la IU al mismo tiempo (en este caso, actualizaremos una barra de progreso).
Importaciones:
import android.os.PowerManager;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.net.HttpURLConnection;
Este es un código de ejemplo:
// declare the dialog as a member field of your activity
ProgressDialog mProgressDialog;
// instantiate it within the onCreate method
mProgressDialog = new ProgressDialog(YourActivity.this);
mProgressDialog.setMessage("A message");
mProgressDialog.setIndeterminate(true);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(true);
// execute this when the downloader must be fired
final DownloadTask downloadTask = new DownloadTask(YourActivity.this);
downloadTask.execute("the url to the file you want to download");
mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
downloadTask.cancel(true); //cancel the task
}
});
El AsyncTask
se verá así:
// usually, subclasses of AsyncTask are declared inside the activity class.
// that way, you can easily modify the UI thread from here
private class DownloadTask extends AsyncTask<String, Integer, String> {
private Context context;
private PowerManager.WakeLock mWakeLock;
public DownloadTask(Context context) {
this.context = context;
}
@Override
protected String doInBackground(String... sUrl) {
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
try {
URL url = new URL(sUrl[0]);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
// expect HTTP 200 OK, so we don't mistakenly save error report
// instead of the file
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return "Server returned HTTP " + connection.getResponseCode()
+ " " + connection.getResponseMessage();
}
// this will be useful to display download percentage
// might be -1: server did not report the length
int fileLength = connection.getContentLength();
// download the file
input = connection.getInputStream();
output = new FileOutputStream("/sdcard/file_name.extension");
byte data[] = new byte[4096];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
// allow canceling with back button
if (isCancelled()) {
input.close();
return null;
}
total += count;
// publishing the progress....
if (fileLength > 0) // only if total length is known
publishProgress((int) (total * 100 / fileLength));
output.write(data, 0, count);
}
} catch (Exception e) {
return e.toString();
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
} catch (IOException ignored) {
}
if (connection != null)
connection.disconnect();
}
return null;
}
El método anterior ( doInBackground
) se ejecuta siempre en un hilo de fondo. No deberías hacer ninguna tarea de IU allí. Por otro lado, el onProgressUpdate
y onPreExecute
ejecutar en el hilo de la interfaz de usuario, por lo que puede cambiar la barra de progreso:
@Override
protected void onPreExecute() {
super.onPreExecute();
// take CPU lock to prevent CPU from going off if the user
// presses the power button during download
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
mWakeLock.acquire();
mProgressDialog.show();
}
@Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
// if we get here, length is known, now set indeterminate to false
mProgressDialog.setIndeterminate(false);
mProgressDialog.setMax(100);
mProgressDialog.setProgress(progress[0]);
}
@Override
protected void onPostExecute(String result) {
mWakeLock.release();
mProgressDialog.dismiss();
if (result != null)
Toast.makeText(context,"Download error: "+result, Toast.LENGTH_LONG).show();
else
Toast.makeText(context,"File downloaded", Toast.LENGTH_SHORT).show();
}
Para que esto se ejecute, necesita el permiso WAKE_LOCK.
<uses-permission android:name="android.permission.WAKE_LOCK" />
2. Descargar del servicio
La gran pregunta aquí es: ¿cómo actualizo mi actividad desde un servicio? . En el siguiente ejemplo, vamos a utilizar dos clases que quizás no conozcas: ResultReceiver
y IntentService
. ResultReceiver
es el que nos permitirá actualizar nuestro hilo desde un servicio; IntentService
es una subclase de la Service
cual se genera un hilo para hacer un trabajo de fondo desde allí (debe saber que se Service
ejecuta realmente en el mismo hilo de su aplicación; cuando extiendeService
, debe generar manualmente nuevos subprocesos para ejecutar operaciones de bloqueo de la CPU).
El servicio de descarga puede verse así:
public class DownloadService extends IntentService {
public static final int UPDATE_PROGRESS = 8344;
public DownloadService() {
super("DownloadService");
}
@Override
protected void onHandleIntent(Intent intent) {
String urlToDownload = intent.getStringExtra("url");
ResultReceiver receiver = (ResultReceiver) intent.getParcelableExtra("receiver");
try {
//create url and connect
URL url = new URL(urlToDownload);
URLConnection connection = url.openConnection();
connection.connect();
// this will be useful so that you can show a typical 0-100% progress bar
int fileLength = connection.getContentLength();
// download the file
InputStream input = new BufferedInputStream(connection.getInputStream());
String path = "/sdcard/BarcodeScanner-debug.apk" ;
OutputStream output = new FileOutputStream(path);
byte data[] = new byte[1024];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
total += count;
// publishing the progress....
Bundle resultData = new Bundle();
resultData.putInt("progress" ,(int) (total * 100 / fileLength));
receiver.send(UPDATE_PROGRESS, resultData);
output.write(data, 0, count);
}
// close streams
output.flush();
output.close();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
Bundle resultData = new Bundle();
resultData.putInt("progress" ,100);
receiver.send(UPDATE_PROGRESS, resultData);
}
}
Agregue el servicio a su manifiesto:
<service android:name=".DownloadService"/>
Y la actividad se verá así:
// initialize the progress dialog like in the first example
// this is how you fire the downloader
mProgressDialog.show();
Intent intent = new Intent(this, DownloadService.class);
intent.putExtra("url", "url of the file to download");
intent.putExtra("receiver", new DownloadReceiver(new Handler()));
startService(intent);
Aquí es donde ResultReceiver
viene a jugar:
private class DownloadReceiver extends ResultReceiver{
public DownloadReceiver(Handler handler) {
super(handler);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
if (resultCode == DownloadService.UPDATE_PROGRESS) {
int progress = resultData.getInt("progress"); //get the progress
dialog.setProgress(progress);
if (progress == 100) {
dialog.dismiss();
}
}
}
}
2.1 Usar la biblioteca Groundy
Groundy es una biblioteca que básicamente te ayuda a ejecutar fragmentos de código en un servicio en segundo plano, y se basa en elResultReceiver
concepto que se muestra arriba. Esta biblioteca está en desuso en este momento. Así es como el todo se vería código:
La actividad donde está mostrando el diálogo ...
public class MainActivity extends Activity {
private ProgressDialog mProgressDialog;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String url = ((EditText) findViewById(R.id.edit_url)).getText().toString().trim();
Bundle extras = new Bundler().add(DownloadTask.PARAM_URL, url).build();
Groundy.create(DownloadExample.this, DownloadTask.class)
.receiver(mReceiver)
.params(extras)
.queue();
mProgressDialog = new ProgressDialog(MainActivity.this);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(false);
mProgressDialog.show();
}
});
}
private ResultReceiver mReceiver = new ResultReceiver(new Handler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
switch (resultCode) {
case Groundy.STATUS_PROGRESS:
mProgressDialog.setProgress(resultData.getInt(Groundy.KEY_PROGRESS));
break;
case Groundy.STATUS_FINISHED:
Toast.makeText(DownloadExample.this, R.string.file_downloaded, Toast.LENGTH_LONG);
mProgressDialog.dismiss();
break;
case Groundy.STATUS_ERROR:
Toast.makeText(DownloadExample.this, resultData.getString(Groundy.KEY_ERROR), Toast.LENGTH_LONG).show();
mProgressDialog.dismiss();
break;
}
}
};
}
Una GroundyTask
implementación utilizada por Groundy para descargar el archivo y mostrar el progreso:
public class DownloadTask extends GroundyTask {
public static final String PARAM_URL = "com.groundy.sample.param.url";
@Override
protected boolean doInBackground() {
try {
String url = getParameters().getString(PARAM_URL);
File dest = new File(getContext().getFilesDir(), new File(url).getName());
DownloadUtils.downloadFile(getContext(), url, dest, DownloadUtils.getDownloadListenerForTask(this));
return true;
} catch (Exception pokemon) {
return false;
}
}
}
Y solo agregue esto al manifiesto:
<service android:name="com.codeslap.groundy.GroundyService"/>
No podría ser más fácil, creo. Simplemente tome el último frasco de Github y estará listo para comenzar. Manten eso en mente el propósito principal de Groundy es hacer llamadas a aplicaciones REST externas en un servicio en segundo plano y publicar resultados en la interfaz de usuario con facilidad. Si está haciendo algo así en su aplicación, podría ser realmente útil.
3. Use DownloadManager
class (GingerBread
y más reciente solamente)
GingerBread trajo una nueva característica, DownloadManager
que le permite descargar archivos fácilmente y delegar el arduo trabajo de manejar hilos, transmisiones, etc. al sistema.
Primero, veamos un método de utilidad:
/**
* @param context used to check the device version and DownloadManager information
* @return true if the download manager is available
*/
public static boolean isDownloadManagerAvailable(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
return true;
}
return false;
}
El nombre del método lo explica todo. Una vez que esté seguro de que DownloadManager
está disponible, puede hacer algo como esto:
String url = "url you want to download";
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setDescription("Some descrition");
request.setTitle("Some title");
// in order for this if to run, you must use the android 3.2 to compile your app
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
}
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "name-of-the-file.ext");
// get download service and enqueue file
DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
El progreso de la descarga se mostrará en la barra de notificaciones.
Pensamientos finales
El primer y segundo método son solo la punta del iceberg. Hay muchas cosas que debes tener en cuenta si quieres que tu aplicación sea robusta. Aquí hay una breve lista:
- Debe verificar si el usuario tiene una conexión a Internet disponible
- Asegúrese de tener los permisos correctos (
INTERNET
y WRITE_EXTERNAL_STORAGE
); también ACCESS_NETWORK_STATE
si quieres consultar la disponibilidad de internet.
- Asegúrese de que el directorio donde va a descargar archivos existe y tiene permisos de escritura.
- Si la descarga es demasiado grande, es posible que desee implementar una forma de reanudar la descarga si fallaron los intentos anteriores.
- Los usuarios estarán agradecidos si les permite interrumpir la descarga.
A menos que necesite un control detallado del proceso de descarga, considere usar DownloadManager
(3) porque ya maneja la mayoría de los elementos enumerados anteriormente.
Pero también considere que sus necesidades pueden cambiar. Por ejemplo, DownloadManager
no responde el almacenamiento en caché . Descargará ciegamente el mismo archivo grande varias veces. No hay una manera fácil de arreglarlo después del hecho. Donde si comienzas con un básico HttpURLConnection
(1, 2), entonces todo lo que necesitas es agregar un HttpResponseCache
. Por lo tanto, el esfuerzo inicial de aprender las herramientas básicas y estándar puede ser una buena inversión.
Esta clase fue desaprobada en el nivel 26 de API. ProgressDialog es un diálogo modal que evita que el usuario interactúe con la aplicación. En lugar de usar esta clase, debe usar un indicador de progreso como ProgressBar, que puede integrarse en la interfaz de usuario de su aplicación. Alternativamente, puede usar una notificación para informar al usuario del progreso de la tarea. Para más detalles Enlace