Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
283 views
in Technique[技术] by (71.8m points)

Download multiple files with a progress bar in ListView Android

I want to create a ListView that allows the user to download many files and show a progress bar status in each ListView item. It looks like this:

enter image description here

The download ListView has some rules:

  • Each download task displays in one ListView item with a progress bar, percentage and current status (downloading, waiting, finished).
  • Allow a maximum of 5 download tasks, other tasks have to wait
  • Users can cancel the download task, and remove this task from the ListView
  • The ListView is in the download activity. Users can change to another activity. When they leave, continue downloading in the background. When they come back, display the current progress and status of the download tasks.
  • Keep the completed download tasks until users want to remove them.

I tried to use the ThreadPoolExcutor in a Service. For each download task, I can get the percentage complete, but I don't know how to broadcast them to an adapter to display progress. And I don't know how to keep all the tasks running in the background and then post progress when the activity containing the ListView is active and keep the completed tasks
It would be great if there were any library or example which can solve my problems. Thanks in advance! P/S: I have already searched similar questions, but I can't find the solution for this one, so I had to create my own question in detail. So please don't mark it as duplicate. Thanks.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is a working sample, take a look.

Launch the app, press back button and then again come back to test the case of launching another Activity and coming back.

Make sure to get PARTIAL_WAKE_LOCK for your IntentService to ensure that CPU keeps running.

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...