studio - java android download file from url




Scarica un file con Android e mostra i progressi in una ProgressDialog (8)

Sto cercando di scrivere una semplice applicazione che viene aggiornata. Per questo ho bisogno di una semplice funzione che può scaricare un file e mostrare l'avanzamento corrente in una ProgressDialog . So come eseguire ProgressDialog , ma non sono sicuro di come visualizzare l'avanzamento corrente e come scaricare il file in primo luogo.


Esistono molti modi per scaricare i file. Di seguito pubblicherò i modi più comuni; spetta a te decidere quale metodo è migliore per la tua app.

1. Usa AsyncTask e mostra l'avanzamento del download in una finestra di dialogo

Questo metodo ti consentirà di eseguire alcuni processi in background e aggiornare l'interfaccia utente contemporaneamente (in questo caso aggiorneremo una barra di avanzamento).

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);
    }
});

AsyncTask sarà simile a questo:

// 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 sopra ( doInBackground ) viene eseguito sempre su un thread in background. Non dovresti svolgere alcuna attività di interfaccia utente lì. D'altra parte, onProgressUpdate e onPreExecute eseguiti sul thread dell'interfaccia utente, quindi è 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();
    }

Perché ciò avvenga, è necessario il permesso 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 che genera una discussione per eseguire il lavoro in background da lì (dovresti sapere che un Service viene eseguito nello stesso thread della tua app, quando estendi il Service , devi generare manualmente nuovi thread per eseguire le operazioni di blocco della CPU) .

Il servizio di download può assomigliare a questo:

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 {
            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());
            OutputStream output = new FileOutputStream("/sdcard/BarcodeScanner-debug.apk");

            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);
            }

            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 che arriva ResultReceiver a giocare:

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");
            mProgressDialog.setProgress(progress);
            if (progress == 100) {
                mProgressDialog.dismiss();
            }
        }
    }
}

2.1 Usa la libreria di Groundy

Groundy è una libreria che fondamentalmente aiuta a eseguire pezzi di codice in un servizio in background, ed è basata sul concetto di ResultReceiver mostrato sopra. Al momento questa libreria è deprecata . Ecco come apparirà l' intero codice:

L'attività in cui viene visualizzata 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 utilizzata 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, penso. Prendi l'ultimo barattolo di Github e sei pronto per andare. Tieni presente che lo scopo principale di Groundy è quello di effettuare chiamate agli apis REST esterni 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.

2.2 Utilizzare https://github.com/koush/ion

3. Utilizzare la classe DownloadManager (solo GingerBread e GingerBread più recenti)

GingerBread ha portato 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 è 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);

Il progresso del download verrà mostrato nella barra di notifica.

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 una breve lista:

  • È necessario verificare se l'utente ha una connessione Internet disponibile
  • Assicurati di avere i permessi giusti ( INTERNET e WRITE_EXTERNAL_STORAGE ); anche ACCESS_NETWORK_STATE se si desidera verificare la disponibilità di Internet.
  • Assicurati che la directory dove hai intenzione di 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 i precedenti tentativi non sono riusciti.
  • Gli utenti saranno grati se permetteranno loro di interrompere il download.

A meno che non sia necessario il controllo dettagliato del processo di download, è consigliabile utilizzare DownloadManager (3) perché gestisce già la maggior parte degli elementi sopra elencati.

Ma considera anche che le tue esigenze potrebbero cambiare. Ad esempio, DownloadManager non esegue alcuna memorizzazione nella cache di risposta . Scaricherà alla cieca lo stesso grande file più volte. Non c'è un modo semplice per risolverlo dopo il fatto. Dove se inizi con HttpURLConnection (1, 2) di base, tutto ciò che ti serve è aggiungere una HttpResponseCache . Quindi lo sforzo iniziale di apprendere gli strumenti standard di base può essere un buon investimento.

Questa classe è stata dichiarata obsoleta al livello API 26. ProgressDialog è una finestra di dialogo modale che impedisce all'utente di interagire con l'app. Invece di utilizzare questa classe, dovresti utilizzare un indicatore di avanzamento come ProgressBar, che può essere incorporato nell'interfaccia utente della tua app. In alternativa, è possibile utilizzare una notifica per informare l'utente dell'avanzamento dell'attività. Per maggiori dettagli Link


Ho modificato la classe AsyncTask per gestire la creazione di progressDialog nello stesso contesto. Penso che il seguente codice sarà più riutilizzabile. (può essere chiamato da qualsiasi attività basta passare contesto, file di destinazione, messaggio di dialogo)

public static class DownloadTask extends AsyncTask<String, Integer, String> {
    private ProgressDialog mPDialog;
    private Context mContext;
    private PowerManager.WakeLock mWakeLock;
    private File mTargetFile;
    //Constructor parameters :
    // @context (current Activity)
    // @targetFile (File object to write,it will be overwritten if exist)
    // @dialogMessage (message of the ProgresDialog)
    public DownloadTask(Context context,File targetFile,String dialogMessage) {
        this.mContext = context;
        this.mTargetFile = targetFile;
        mPDialog = new ProgressDialog(context);

        mPDialog.setMessage(dialogMessage);
        mPDialog.setIndeterminate(true);
        mPDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mPDialog.setCancelable(true);
        // reference to instance to use inside listener
        final DownloadTask me = this;
        mPDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                me.cancel(true);
            }
        });
        Log.i("DownloadTask","Constructor done");
    }

    @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();
            }
            Log.i("DownloadTask","Response " + connection.getResponseCode());

            // 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(mTargetFile,false);

            byte data[] = new byte[4096];
            long total = 0;
            int count;
            while ((count = input.read(data)) != -1) {
                // allow canceling with back button
                if (isCancelled()) {
                    Log.i("DownloadTask","Cancelled");
                    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;
    }
    @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) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                getClass().getName());
        mWakeLock.acquire();

        mPDialog.show();

    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        super.onProgressUpdate(progress);
        // if we get here, length is known, now set indeterminate to false
        mPDialog.setIndeterminate(false);
        mPDialog.setMax(100);
        mPDialog.setProgress(progress[0]);

    }

    @Override
    protected void onPostExecute(String result) {
        Log.i("DownloadTask", "Work Done! PostExecute");
        mWakeLock.release();
        mPDialog.dismiss();
        if (result != null)
            Toast.makeText(mContext,"Download error: "+result, Toast.LENGTH_LONG).show();
        else
            Toast.makeText(mContext,"File Downloaded", Toast.LENGTH_SHORT).show();
    }
}

Il mio consiglio personale è quello di utilizzare la finestra di dialogo Avanzamento e creare prima dell'esecuzione, oppure iniziare su OnPreExecute() , pubblicare spesso i progressi se si utilizza lo stile orizzontale della barra di avanzamento della finestra di dialogo di avanzamento. La parte rimanente è ottimizzare l'algoritmo di doInBackground .


Mentre stavo iniziando ad imparare lo sviluppo di Android, avevo imparato che ProgressDialog è la strada da percorrere. Esiste il metodo setProgress di ProgressDialog che può essere richiamato per aggiornare il livello di avanzamento man mano che il file viene scaricato.

Il meglio che ho visto in molte app è che personalizzano gli attributi di questa finestra di avanzamento per dare un aspetto migliore alla finestra di avanzamento rispetto alla versione originale. Buono a mantenere l'utente impegnato con alcune animazioni di come rana, elefante o simpatici gatti / cuccioli. Qualsiasi animazione con nella finestra di dialogo di avanzamento attira gli utenti e non hanno voglia di essere tenuti ad aspettare a lungo.

Scriverò un post sul blog su ProgressDialog e lo condividerò presto.

Modifica: mostra la barra di avanzamento durante il download di un file in Android


Non dimenticare di sostituire "/ sdcard ..." con il nuovo file ("/ mnt / sdcard / ...") altrimenti otterrai un FileNotFoundException


Sì, il codice sopra funzionerà. Ma se si aggiorna la onProgressUpdate di progressbar in onProgressUpdate di Asynctask e si preme il pulsante indietro o si termina l'attività AsyncTask perde la sua traccia con l'interfaccia utente. E quando si torna alla propria attività, anche se il download è in esecuzione in sullo sfondo non vedrai alcun aggiornamento sulla barra di avanzamento. Quindi su OnResume() prova ad eseguire un thread come runOnUIThread con un'attività timer che aggiorna la AsyncTask progressbar con i valori che aggiornano dallo sfondo di AsyncTask .

private void updateProgressBar(){
    Runnable runnable = new updateProgress();
    background = new Thread(runnable);
    background.start();
}

public class updateProgress implements Runnable {
    public void run() {
        while(Thread.currentThread()==background)
            //while (!Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(1000); 
                Message msg = new Message();
                progress = getProgressPercentage();        
                handler.sendMessage(msg);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (Exception e) {
        }
    }
}

private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        progress.setProgress(msg.what);
    }
};

Non dimenticare di distruggere il filo quando la tua attività non è visibile.

private void destroyRunningThreads() {
    if (background != null) {
        background.interrupt();
        background=null;
    }
}

Ti consiglierei di usare il mio Progetto Netroid , basato su Volley . Ho aggiunto alcune funzionalità come callback multi-evento, gestione del download dei file. Questo potrebbe essere di aiuto.


Usa la libreria di query Android, davvero molto interessante. Puoi cambiarlo per usare ProgressDialog come puoi vedere in altri esempi, questo mostrerà la vista del progresso dal tuo layout e lo nasconderà dopo il completamento.

File target = new File(new File(Environment.getExternalStorageDirectory(), "ApplicationName"), "tmp.pdf");
new AQuery(this).progress(R.id.progress_view).download(_competition.qualificationScoreCardsPdf(), target, new AjaxCallback<File>() {
    public void callback(String url, File file, AjaxStatus status) {
        if (file != null) {
            // do something with file  
        } 
    }
});




android-asynctask