Question Télécharger un fichier avec Android, et montrant la progression dans un ProgressDialog


J'essaie d'écrire une application simple qui est mise à jour. Pour cela, j'ai besoin d'une fonction simple qui peut télécharger un fichier et montrer les progrès actuels dans un ProgressDialog. Je sais comment faire ProgressDialog, mais je ne suis pas sûr comment afficher la progression actuelle et comment télécharger le fichier en premier lieu.


914
2018-06-12 10:22


origine


Réponses:


Il y a plusieurs façons de télécharger des fichiers. Après je posterai les manières les plus communes; C'est à vous de décider quelle méthode est la meilleure pour votre application.

1. Utilisation AsyncTask et montre la progression du téléchargement dans une boîte de dialogue

Cette méthode vous permettra d'exécuter certains processus en arrière-plan et de mettre à jour l'interface utilisateur en même temps (dans ce cas, nous mettrons à jour une barre de progression).

Ceci est un exemple de code:

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

le AsyncTask ressemblera à ceci:

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

La méthode ci-dessus (doInBackground) s'exécute toujours sur un thread d'arrière-plan. Vous ne devriez pas faire de tâches d'interface utilisateur là-bas. D'un autre côté, le onProgressUpdate et onPreExecute exécuter sur le thread UI, de sorte que vous pouvez changer la barre de progression:

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

Pour que cela fonctionne, vous avez besoin de l'autorisation WAKE_LOCK.

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

2. Télécharger depuis le service

La grande question ici est: Comment puis-je mettre à jour mon activité à partir d'un service?. Dans l'exemple suivant, nous allons utiliser deux classes dont vous n'êtes peut-être pas conscient: ResultReceiver et IntentService. ResultReceiver est celui qui nous permettra de mettre à jour notre fil d'un service; IntentService est une sous-classe de Service qui engendre un fil pour faire un travail de fond à partir de là (vous devriez savoir qu'un Service fonctionne réellement dans le même thread de votre application; quand vous étend Service, vous devez générer manuellement de nouveaux threads pour exécuter les opérations de blocage de la CPU).

Le service de téléchargement peut ressembler à ceci:

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

Ajoutez le service à votre manifeste:

<service android:name=".DownloadService"/>

Et l'activité ressemblera à ceci:

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

Voici ont été ResultReceiver vient jouer:

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 Utiliser la bibliothèque Groundy

Groundy est une bibliothèque qui vous aide essentiellement à exécuter des morceaux de code dans un service d'arrière-plan, et elle est basée sur ResultReceiver concept montré ci-dessus. Cette bibliothèque est déconseillé en ce moment. C'est comme ça que entier le code ressemblerait à:

L'activité où vous montrez le dialogue ...

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

UNE GroundyTask implémentation utilisée par Groundy pour télécharger le fichier et montrer les progrès:

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

Et ajoutez simplement ceci au manifeste:

<service android:name="com.codeslap.groundy.GroundyService"/>

Ça ne pourrait pas être plus facile je pense. Prenez simplement le dernier pot de Github et vous êtes prêt à partir. Garde en tête que GroundyL'objectif principal est de faire des appels à des API REST externes dans un service d'arrière-plan et de publier facilement les résultats dans l'interface utilisateur. Si vous faites quelque chose comme ça dans votre application, cela pourrait être très utile.

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

3. Utilisation DownloadManager classe (GingerBread et plus récent seulement)

GingerBread a apporté une nouvelle fonctionnalité, DownloadManager, qui vous permet de télécharger des fichiers facilement et de déléguer le travail acharné de gestion des threads, des flux, etc. au système.

Voyons d'abord une méthode d'utilitaire:

/**
 * @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;
}

Le nom de la méthode explique tout cela. Une fois que vous êtes sûr DownloadManager est disponible, vous pouvez faire quelque chose comme ceci:

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

La progression du téléchargement s'affichera dans la barre de notification.

Dernières pensées

Les première et deuxième méthodes ne sont que la pointe de l'iceberg. Il y a beaucoup de choses que vous devez garder à l'esprit si vous voulez que votre application soit robuste. Voici une brève liste:

  • Vous devez vérifier si l'utilisateur dispose d'une connexion Internet
  • Assurez-vous d'avoir les bonnes permissions (INTERNET et WRITE_EXTERNAL_STORAGE) aussi ACCESS_NETWORK_STATE si vous voulez vérifier la disponibilité de l'internet.
  • Assurez-vous que le répertoire où vous allez télécharger des fichiers existe et dispose d'autorisations d'écriture.
  • Si le téléchargement est trop important, vous pouvez mettre en place un moyen de reprendre le téléchargement si les tentatives précédentes ont échoué.
  • Les utilisateurs vous seront reconnaissants si vous leur permettez d'interrompre le téléchargement.

Sauf si vous avez besoin d'un contrôle détaillé du processus de téléchargement, envisagez d'utiliser DownloadManager (3) parce qu'il gère déjà la plupart des éléments énumérés ci-dessus.

Mais considérez aussi que vos besoins peuvent changer. Par exemple, DownloadManager  ne fait pas de mise en cache de réponse. Il va télécharger à l'aveugle le même gros fichier plusieurs fois. Il n'y a pas de moyen facile de réparer après coup. Où si vous commencez avec une base HttpURLConnection (1, 2), alors tout ce dont vous avez besoin est d'ajouter un HttpResponseCache. Ainsi, l'effort initial d'apprentissage des outils standard de base peut être un bon investissement.

Cette classe était obsolète au niveau API 26. ProgressDialog est un modal   dialogue, qui empêche l'utilisateur d'interagir avec l'application. Au lieu   d'utiliser cette classe, vous devez utiliser un indicateur de progression comme   ProgressBar, qui peut être intégré dans l'interface utilisateur de votre application. Alternativement,   vous pouvez utiliser une notification pour informer l'utilisateur de la progression de la tâche. Pour plus de détails Lien


1726
2017-08-29 14:34



N'oubliez pas d'ajouter des permissions à votre fichier manifeste si vous voulez télécharger des choses sur Internet!

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.helloandroid"
    android:versionCode="1"
    android:versionName="1.0">

        <uses-sdk android:minSdkVersion="10" />

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

        <application 
            android:icon="@drawable/icon" 
            android:label="@string/app_name" 
            android:debuggable="true">

        </application>

</manifest>

98
2018-06-12 14:21



Oui, le code ci-dessus fonctionnera. Mais si vous mettez à jour votre progressbar dans onProgressUpdate de Asynctask  et vous appuyez sur le bouton de retour ou terminer votre activité AsyncTask perd sa trace avec votre interface utilisateur. Et lorsque vous revenez à votre activité, même si le téléchargement est en cours en arrière-plan, vous ne verrez aucune mise à jour sur la barre de progression. Bientôt OnResume() essayer de lancer un fil comme runOnUIThread avec une tâche de minuterie qui met à jour votre progressbar avec des valeurs de mise à jour de la AsyncTask fond en cours d'exécution.

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

Ne pas oublier de Détruire le fil quand votre activité n'est pas visible.

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

27
2018-05-20 23:29



J'ai modifié AsyncTask classe pour gérer la création de progressDialog dans le même contexte. Je pense que le code suivant sera plus réutilisable. (il peut être appelé depuis n'importe quelle activité juste passer le contexte, le fichier cible, le message de la boîte de dialogue)

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

15
2018-05-07 15:56



Je vous recommande d'utiliser mon projet NetroidC'est basé sur Volée. J'ai ajouté quelques fonctionnalités telles que le rappel multi-événements, la gestion de téléchargement de fichiers. Cela pourrait être utile.


14
2018-02-10 00:17



Ne pas oublier de remplacer "/ sdcard ..." par nouveau fichier("/ mnt / sdcard / ...") sinon vous obtiendrez un FileNotFoundException


8
2017-08-01 14:55



j'ai trouvé ce blog très utile, Son utilisation loopJ pour télécharger le fichier, il a seulement une fonction simple, sera utile à certains nouveaux gars android.


8
2017-09-19 13:41



Alors que je commençais à apprendre le développement Android, j'avais appris que ProgressDialog est le chemin à parcourir. Il y a le setProgress méthode de ProgressDialog qui peut être appelé pour mettre à jour le niveau de progression au fur et à mesure que le fichier est téléchargé.

Le meilleur que j'ai vu dans de nombreuses applications, c'est qu'ils personnalisent les attributs de cette boîte de dialogue de progression pour donner une meilleure apparence à la boîte de dialogue de progression que la version stock. Bon à garder l'utilisateur engagé avec une animation de grenouille, d'éléphant ou de chats / chiots mignons. Toute animation avec dans le dialogue de progression attire les utilisateurs et ils n'ont pas envie d'attendre longtemps.

Je vais écrire un article de blog sur ProgressDialog et partagez ici bientôt.

Modifier: Afficher la barre de progression lors du téléchargement d'un fichier dans android


6
2017-07-02 17:11



Mon conseil personnel est d'utiliser Dialogue de progression et accumuler avant l'exécution, ou initier à OnPreExecute() , publier des progrès souvent si vous utilisez le style horizontal de la barre de progression de la boîte de dialogue de progression. Le reste est d'optimiser l'algorithme de doInBackground.


5
2018-05-06 19:27