web-dev-qa-db-fra.com

Téléchargez plusieurs fichiers avec une barre de progression dans ListView Android

Je veux créer un ListView qui permet à l'utilisateur de télécharger de nombreux fichiers et de montrer un état de la barre de progression dans chaque élément ListView. Cela ressemble à ceci:

enter image description here

Le téléchargement ListView a quelques règles:

  • Chaque tâche de téléchargement s'affiche dans un élément ListView item avec une barre de progression, pourcentage et état actuel (téléchargement, attente, fini).
  • Prévoyez un maximum de 5 tâches de téléchargement, d'autres tâches doivent attendre
  • Les utilisateurs peuvent annuler la tâche de téléchargement et supprimer cette tâche du ListView
  • Le ListView est dans l'activité de téléchargement. Les utilisateurs peuvent passer à une autre activité. Quand ils partent, continuez à télécharger en arrière-plan. Lorsqu'ils reviennent, affichent les progrès et l'état actuels des tâches de téléchargement.
  • Conservez les tâches de téléchargement terminées jusqu'à ce que les utilisateurs souhaitent les supprimer.

J'ai essayé d'utiliser le ThreadPoolExcutor dans un Service. Pour chaque tâche de téléchargement, je peux obtenir le pourcentage complet, mais je ne sais pas comment les diffuser à un adaptateur pour afficher les progrès. Et je ne sais pas comment conserver toutes les tâches en cours d'exécution et publier ensuite des progrès lorsque l'activité contenant le ListView est active et conserver les tâches remplies
[.____] Ce serait génial s'il y avait une bibliothèque ou un exemple qui peut résoudre mes problèmes. Merci d'avance! P/S : J'ai déjà recherché des questions similaires, mais je ne trouve pas la solution pour celle-ci, alors j'ai dû créer ma propre question en détail. Alors s'il vous plaît ne le marquez pas comme duplicate. Merci.

24
R4j

Ceci est un échantillon travail, jetez un coup d'œil.

Lancez l'application, appuyez sur la touche Back, puis revenez pour tester le cas du lancement d'un autre Activity et de revenir.

Assurez-vous d'obtenir PARTIAL_WAKE_LOCK Pour votre IntentService pour vous assurer que la CPU continue de fonctionner.

import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Collection;
import Java.util.List;
import Java.util.Random;
import Java.util.concurrent.Callable;
import Java.util.concurrent.CompletionService;
import Java.util.concurrent.ExecutionException;
import Java.util.concurrent.ExecutorCompletionService;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;

import Android.app.Activity;
import Android.app.IntentService;
import Android.content.BroadcastReceiver;
import Android.content.Context;
import Android.content.Intent;
import Android.content.IntentFilter;
import Android.os.Bundle;
import Android.os.Parcel;
import Android.os.Parcelable;
import Android.support.v4.content.LocalBroadcastManager;
import Android.view.View;
import Android.view.ViewGroup;
import Android.widget.ArrayAdapter;
import Android.widget.ListView;
import Android.widget.ProgressBar;
import Android.widget.TextView;

public class MainActivity extends Activity {
    public static final String ID = "id";
    private ListView mListView;
    private ArrayAdapter<File> mAdapter;
    private boolean mReceiversRegistered;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ListView listView = mListView = (ListView) findViewById(R.id.list);
        long id = 0;
        File[] files = {getFile(id++),
                getFile(id++), getFile(id++), getFile(id++),
                getFile(id++), getFile(id++), getFile(id++),
                getFile(id++), getFile(id++), getFile(id++),
                getFile(id++), getFile(id++), getFile(id++),
                getFile(id++), getFile(id++), getFile(id++),
                getFile(id++), getFile(id++), getFile(id++),
                getFile(id++), getFile(id++), getFile(id++),
                getFile(id++), getFile(id++), getFile(id++),
                getFile(id++), getFile(id++), getFile(id)};
        listView.setAdapter(mAdapter = new ArrayAdapter<File>(this,
                R.layout.row, R.id.textView, files) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View v = super.getView(position, convertView, parent);
                updateRow(getItem(position), v);
                return v;
            }
        });

        if (savedInstanceState == null) {
            Intent intent = new Intent(this, DownloadingService.class);
            intent.putParcelableArrayListExtra("files", new ArrayList<File>(Arrays.asList(files)));
            startService(intent);
        }

        registerReceiver();
    }

    private File getFile(long id) {
        return new File(id, "https://someurl/" + id);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver();
    }

    private void registerReceiver() {
        unregisterReceiver();
        IntentFilter intentToReceiveFilter = new IntentFilter();
        intentToReceiveFilter
                .addAction(DownloadingService.PROGRESS_UPDATE_ACTION);
        LocalBroadcastManager.getInstance(this).registerReceiver(
                mDownloadingProgressReceiver, intentToReceiveFilter);
        mReceiversRegistered = true;
    }

    private void unregisterReceiver() {
        if (mReceiversRegistered) {
            LocalBroadcastManager.getInstance(this).unregisterReceiver(
                    mDownloadingProgressReceiver);
            mReceiversRegistered = false;
        }
    }

    private void updateRow(final File file, View v) {
        ProgressBar bar = (ProgressBar) v.findViewById(R.id.progressBar);
        bar.setProgress(file.progress);
        TextView tv = (TextView) v.findViewById(R.id.textView);
        tv.setText(file.toString());
        v.findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent();
                i.setAction(DownloadingService.ACTION_CANCEL_DOWNLOAD);
                i.putExtra(ID, file.getId());
                LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcast(i);
            }
        });
    }

    // don't call notifyDatasetChanged() too frequently, have a look at
    // following url http://stackoverflow.com/a/19090832/1112882
    protected void onProgressUpdate(int position, int progress) {
        final ListView listView = mListView;
        int first = listView.getFirstVisiblePosition();
        int last = listView.getLastVisiblePosition();
        mAdapter.getItem(position).progress = progress > 100 ? 100 : progress;
        if (position < first || position > last) {
            // just update your data set, UI will be updated automatically in next
            // getView() call
        } else {
            View convertView = mListView.getChildAt(position - first);
            // this is the convertView that you previously returned in getView
            // just fix it (for example:)
            updateRow(mAdapter.getItem(position), convertView);
        }
    }

    protected void onProgressUpdateOneShot(int[] positions, int[] progresses) {
        for (int i = 0; i < positions.length; i++) {
            int position = positions[i];
            int progress = progresses[i];
            onProgressUpdate(position, progress);
        }
    }

    private final BroadcastReceiver mDownloadingProgressReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(
                    DownloadingService.PROGRESS_UPDATE_ACTION)) {
                final boolean oneShot = intent
                        .getBooleanExtra("oneshot", false);
                if (oneShot) {
                    final int[] progresses = intent
                            .getIntArrayExtra("progress");
                    final int[] positions = intent.getIntArrayExtra("position");
                    onProgressUpdateOneShot(positions, progresses);
                } else {
                    final int progress = intent.getIntExtra("progress", -1);
                    final int position = intent.getIntExtra("position", -1);
                    if (position == -1) {
                        return;
                    }
                    onProgressUpdate(position, progress);
                }
            }
        }
    };

    public static class DownloadingService extends IntentService {
        public static String PROGRESS_UPDATE_ACTION = DownloadingService.class
                .getName() + ".progress_update";

        private static final String ACTION_CANCEL_DOWNLOAD = DownloadingService.class
                .getName() + "action_cancel_download";

        private boolean mIsAlreadyRunning;
        private boolean mReceiversRegistered;

        private ExecutorService mExec;
        private CompletionService<NoResultType> mEcs;
        private LocalBroadcastManager mBroadcastManager;
        private List<DownloadTask> mTasks;

        private static final long INTERVAL_BROADCAST = 800;
        private long mLastUpdate = 0;

        public DownloadingService() {
            super("DownloadingService");
            mExec = Executors.newFixedThreadPool( /* only 5 at a time */5);
            mEcs = new ExecutorCompletionService<NoResultType>(mExec);
            mBroadcastManager = LocalBroadcastManager.getInstance(this);
            mTasks = new ArrayList<MainActivity.DownloadingService.DownloadTask>();
        }

        @Override
        public void onCreate() {
            super.onCreate();
            registerReceiver();
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            unregisterReceiver();
        }

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if (mIsAlreadyRunning) {
                publishCurrentProgressOneShot(true);
            }
            return super.onStartCommand(intent, flags, startId);
        }

        @Override
        protected void onHandleIntent(Intent intent) {
            if (mIsAlreadyRunning) {
                return;
            }
            mIsAlreadyRunning = true;

            ArrayList<File> files = intent.getParcelableArrayListExtra("files");
            final Collection<DownloadTask> tasks = mTasks;
            int index = 0;
            for (File file : files) {
                DownloadTask yt1 = new DownloadTask(index++, file);
                tasks.add(yt1);
            }

            for (DownloadTask t : tasks) {
                mEcs.submit(t);
            }
            // wait for finish
            int n = tasks.size();
            for (int i = 0; i < n; ++i) {
                NoResultType r;
                try {
                    r = mEcs.take().get();
                    if (r != null) {
                        // use you result here
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
            // send a last broadcast
            publishCurrentProgressOneShot(true);
            mExec.shutdown();
        }

        private void publishCurrentProgressOneShot(boolean forced) {
            if (forced
                    || System.currentTimeMillis() - mLastUpdate > INTERVAL_BROADCAST) {
                mLastUpdate = System.currentTimeMillis();
                final List<DownloadTask> tasks = mTasks;
                int[] positions = new int[tasks.size()];
                int[] progresses = new int[tasks.size()];
                for (int i = 0; i < tasks.size(); i++) {
                    DownloadTask t = tasks.get(i);
                    positions[i] = t.mPosition;
                    progresses[i] = t.mProgress;
                }
                publishProgress(positions, progresses);
            }
        }

        private void publishCurrentProgressOneShot() {
            publishCurrentProgressOneShot(false);
        }

        private synchronized void publishProgress(int[] positions,
                                                  int[] progresses) {
            Intent i = new Intent();
            i.setAction(PROGRESS_UPDATE_ACTION);
            i.putExtra("position", positions);
            i.putExtra("progress", progresses);
            i.putExtra("oneshot", true);
            mBroadcastManager.sendBroadcast(i);
        }

        // following methods can also be used but will cause lots of broadcasts
        private void publishCurrentProgress() {
            final Collection<DownloadTask> tasks = mTasks;
            for (DownloadTask t : tasks) {
                publishProgress(t.mPosition, t.mProgress);
            }
        }

        private synchronized void publishProgress(int position, int progress) {
            Intent i = new Intent();
            i.setAction(PROGRESS_UPDATE_ACTION);
            i.putExtra("progress", progress);
            i.putExtra("position", position);
            mBroadcastManager.sendBroadcast(i);
        }

        class DownloadTask implements Callable<NoResultType> {
            private int mPosition;
            private int mProgress;
            private boolean mCancelled;
            private final File mFile;
            private Random mRand = new Random();

            public DownloadTask(int position, File file) {
                mPosition = position;
                mFile = file;
            }

            @Override
            public NoResultType call() throws Exception {
                while (mProgress < 100 && !mCancelled) {
                    mProgress += mRand.nextInt(5);
                    Thread.sleep(mRand.nextInt(500));

                    // publish progress
                    publishCurrentProgressOneShot();

                    // we can also call publishProgress(int position, int
                    // progress) instead, which will work fine but avoid broadcasts
                    // by aggregating them

                    // publishProgress(mPosition,mProgress);
                }
                return new NoResultType();
            }

            public int getProgress() {
                return mProgress;
            }

            public int getPosition() {
                return mPosition;
            }

            public void cancel() {
                mCancelled = true;
            }
        }


        private void registerReceiver() {
            unregisterReceiver();
            IntentFilter filter = new IntentFilter();
            filter.addAction(DownloadingService.ACTION_CANCEL_DOWNLOAD);
            LocalBroadcastManager.getInstance(this).registerReceiver(
                    mCommunicationReceiver, filter);
            mReceiversRegistered = true;
        }

        private void unregisterReceiver() {
            if (mReceiversRegistered) {
                LocalBroadcastManager.getInstance(this).unregisterReceiver(
                        mCommunicationReceiver);
                mReceiversRegistered = false;
            }
        }

        private final BroadcastReceiver mCommunicationReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals(
                        DownloadingService.ACTION_CANCEL_DOWNLOAD)) {
                    final long id = intent.getLongExtra(ID, -1);
                    if (id != -1) {
                        for (DownloadTask task : mTasks) {
                            if (task.mFile.getId() == id) {
                                task.cancel();
                                break;
                            }
                        }
                    }
                }
            }
        };

        class NoResultType {
        }
    }

    public static class File implements Parcelable {
        private final long id;
        private final String url;
        private int progress;

        public File(long id, String url) {
            this.id = id;
            this.url = url;
        }

        public long getId() {
            return id;
        }

        public String getUrl() {
            return url;
        }

        @Override
        public String toString() {
            return url + " " + progress + " %";
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeLong(this.id);
            dest.writeString(this.url);
            dest.writeInt(this.progress);
        }

        private File(Parcel in) {
            this.id = in.readLong();
            this.url = in.readString();
            this.progress = in.readInt();
        }

        public static final Parcelable.Creator<File> CREATOR = new Parcelable.Creator<File>() {
            public File createFromParcel(Parcel source) {
                return new File(source);
            }

            public File[] newArray(int size) {
                return new File[size];
            }
        };
    }
}

row.xml disposition:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:orientation="vertical">

    <TextView
        Android:id="@+id/textView"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:text="Title" />

    <ProgressBar
        Android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:max="100" />

    <Button
        Android:id="@+id/cancel"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_gravity="right"
        Android:text="Cancel" />

</LinearLayout>

activity_main.xml contient juste un ListView:

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:id="@+id/list"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent" />

Remarque : Assurez-vous de vous inscrire DownloadingService in androidmanifest.xml Comme ceci:

<service Android:name=".MainActivity$DownloadingService" />

Mise à jour :

  • Annuler le support ajouté
60
M-WaJeEh

IMO - 4 choses que vous voudriez mettre en œuvre:

  • Auditeur/wrapper pour httpclient.get.exec () - Donc, vous savez combien d'octets reçus sur chaque écriture ()

    Diffusé/recevoir pour auditeur que vous mentionnez peut réellement être redondant

    Async httpclient - non bloquage est un must

    Piscine/mise en file d'attente pour les demandes

pour le écouteur Vous pouvez voir le modèle d'observateur et commuter le "téléchargement" vers le bas.

comme vous avez déjà un modèle d'observateur, vous devriez pouvoir l'adapter à votre exigence d'architecture. Lorsque l'auditeur réille sur les E/S dans le get.Exec (), vous avez juste besoin et une interface qui vous permet de rappeler sur l'activité/fragment qui a l'interface utilisateur et l'adaptateur de votre liste afin qu'il puisse être informé de la Changement de comptage-octets-lecture-on-http-get. Le rappel d'E/S devra faire référence à la saisie de la liste correcte dans l'adaptateur ou fournir une autre carte d'identité afin qu'elle puisse être liée à un get particulier. J'ai utilisé des gestionnaires et des arguments dans ObtenirMessageHandler () À cette fin. Lorsque le gestionnaire fournit et ID à un get spécifique ... Ensuite, l'auditeur aura une référence au même ID ou à l'ARC lorsqu'il fait son rappel pour compter des octets d'E/S.

pour les articles 3 et 4, il y a beaucoup de choses là-bas. Apache Native Apache HTTPCLIENTS DANS DES POWERS/FILES. Android Volley offre cela aussi. Plus de détails ICI sur la mécanique des gestionnaires et "ObtenirMessage" pour les rappels.

2
Robert Rowntree