Esistono molti modi per scaricare i file. Di seguito posterò i modi più comuni; sta a te decidere quale metodo è meglio per la tua app.
1. Utilizzare AsyncTask
e mostrare l'avanzamento del download in una finestra di dialogo
Questo metodo ti permetterà di eseguire alcuni processi in background e aggiornare contemporaneamente l'interfaccia utente (in questo caso, aggiorneremo una barra di avanzamento).
importazioni:
import android.os.PowerManager;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.net.HttpURLConnection;
Questo è un codice di esempio:
// 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
}
});
Il AsyncTask
sarà simile a questa:
// 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;
}
Il metodo above ( doInBackground
) viene sempre eseguito su un thread in background. Non dovresti svolgere alcuna attività UI lì. D'altra parte, il onProgressUpdate
ed onPreExecute
eseguire sul thread dell'interfaccia utente, quindi lì è possibile modificare la barra di avanzamento:
@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();
}
Affinché ciò avvenga, è necessaria l'autorizzazione WAKE_LOCK.
<uses-permission android:name="android.permission.WAKE_LOCK" />
2. Scarica dal servizio
La grande domanda qui è: come posso aggiornare la mia attività da un servizio? . Nel prossimo esempio useremo due classi di cui potresti non essere a conoscenza: ResultReceiver
e IntentService
. ResultReceiver
è quello che ci permetterà di aggiornare il nostro thread da un servizio; IntentService
è una sottoclasse di Service
cui genera un thread per eseguire il lavoro in background da lì (dovresti sapere che Service
viene effettivamente eseguito nello stesso thread della tua app; quando si estende Service
, devi generare manualmente nuovi thread per eseguire operazioni di blocco della CPU).
Il servizio di download può essere simile al seguente:
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);
}
}
Aggiungi il servizio al tuo manifest:
<service android:name=".DownloadService"/>
E l'attività sarà simile a questa:
// 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);
Ecco dove ResultReceiver
arriva il gioco:
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 Utilizzare la libreria Groundy
Groundy è una libreria che sostanzialmente ti aiuta a eseguire pezzi di codice in un servizio in background e si basa sulResultReceiver
concetto mostrato sopra. Questa libreria è obsoleta al momento. Ecco comeapparirebbel' intero codice:
L'attività in cui stai mostrando la finestra di dialogo ...
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;
}
}
};
}
GroundyTask
Un'implementazione usata da Groundy per scaricare il file e mostrare lo stato di avanzamento:
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;
}
}
}
E aggiungi questo al manifest:
<service android:name="com.codeslap.groundy.GroundyService"/>
Non potrebbe essere più facile, credo. Basta prendere l'ultimo barattolo di Github e sei pronto per partire. Tieni presente che lo scopo principale di Groundy è quello di effettuare chiamate alle API REST esterne in un servizio in background e pubblicare facilmente i risultati nell'interfaccia utente. Se stai facendo qualcosa del genere nella tua app, potrebbe essere davvero utile.
3. Usa DownloadManager
classe ( GingerBread
e solo più recenti)
GingerBread ha introdotto una nuova funzionalità, DownloadManager
che consente di scaricare facilmente i file e delegare il duro lavoro di gestione di thread, flussi, ecc. Al sistema.
Innanzitutto, vediamo un metodo di utilità:
/**
* @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;
}
Il nome del metodo spiega tutto. Una volta che sei sicuro che DownloadManager
sia disponibile, puoi fare qualcosa del genere:
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);
L'avanzamento del download verrà visualizzato nella barra delle notifiche.
Pensieri finali
Il primo e il secondo metodo sono solo la punta dell'iceberg. Ci sono molte cose che devi tenere a mente se vuoi che la tua app sia solida. Ecco un breve elenco:
- È necessario verificare se l'utente ha una connessione Internet disponibile
- Assicurati di avere le autorizzazioni giuste (
INTERNET
e WRITE_EXTERNAL_STORAGE
); anche ACCESS_NETWORK_STATE
se si desidera verificare la disponibilità di Internet.
- Assicurati che la directory in cui stai per scaricare i file esista e abbia i permessi di scrittura.
- Se il download è troppo grande, potresti voler implementare un modo per riprendere il download se tentativi precedenti non sono riusciti.
- Gli utenti saranno grati se permetti loro di interrompere il download.
A meno che non sia necessario il controllo dettagliato del processo di download, prendere in considerazione l'utilizzo di DownloadManager
(3) perché gestisce già la maggior parte degli elementi sopra elencati.
Ma considera anche che le tue esigenze possono cambiare. Ad esempio, DownloadManager
nessuna cache di risposta . Scarica ciecamente lo stesso file di grandi dimensioni più volte. Non c'è modo semplice per risolverlo dopo il fatto. Dove se inizi con un basic HttpURLConnection
(1, 2), allora tutto ciò che ti serve è aggiungere un HttpResponseCache
. Quindi lo sforzo iniziale di apprendere gli strumenti standard di base può essere un buon investimento.
Questa classe è stata deprecata a livello di API 26. ProgressDialog è una finestra di dialogo modale che impedisce all'utente di interagire con l'app. Invece di utilizzare questa classe, è necessario utilizzare un indicatore di avanzamento come ProgressBar, che può essere incorporato nell'interfaccia utente dell'app. In alternativa, è possibile utilizzare una notifica per informare l'utente sullo stato di avanzamento dell'attività. Per maggiori dettagli Link