Download Multiple Files with a Progress Bar in Listview Android

Download multiple files with a progress bar in ListView Android

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

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 layout:

<?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 just contains a 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" />

Note: Make sure to register DownloadingService in AndroidManifest.xml like this:

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

UPDATE:

  • Cancel support added

Android Multiple download pause resume in listview with progress update

Finally I found the answer which was much simpler than I thought, here it is as follows

  1. Create a service having Asynctask for downloading and hashtable of values(url, Asynctask)
  2. Pass the value(url, Asynctask) when a list item is clicked and check whether that hashtable contain the value already if yes cancel that Asynctask task if no add it to hashtable and start Asynctask
  3. now for updating progress in my adapter I ran a thread which iterate over hashtable and passes the value using BroadcastListener.
  4. And in activity intercept the broadcast and depending on the ListItem visible update the progress

PS: If anybody needs some code I can provide basic code of the description explained above

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

private static final long INTERVAL_BROADCAST = 800;
private long mLastUpdate = 0;
private Hashtable<String, DownloadFile> downloadTable;

private LocalBroadcastManager broadcastManager;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
MessageEntity entityRecieved = (MessageEntity) intent.getSerializableExtra("ENTITY");
queueDownload(entityRecieved);
return super.onStartCommand(intent, flags, startId);
}

private void queueDownload(MessageEntity entityRecieved){
if (downloadTable.containsKey(entityRecieved.getPacketID())) {
DownloadFile downloadFile = downloadTable.get(entityRecieved.getPacketID());
if (downloadFile.isCancelled()) {
downloadFile = new DownloadFile(entityRecieved);
downloadTable.put(entityRecieved.getPacketID(), downloadFile);
startDownloadFileTask(downloadFile);
} else {
downloadFile.cancel(true);
downloadTable.remove(entityRecieved.getPacketID());
}

} else {
DownloadFile downloadFile = new DownloadFile(entityRecieved);
downloadTable.put(entityRecieved.getPacketID(), downloadFile);
startDownloadFileTask(downloadFile);
}
}

@Override
public void onCreate() {
super.onCreate();
downloadTable = new Hashtable<String, DownloadFile>();
broadcastManager = LocalBroadcastManager.getInstance(this);
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
void startDownloadFileTask(DownloadFile asyncTask) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask.execute();
}

private void publishCurrentProgressOneShot(boolean forced) {
if (forced || System.currentTimeMillis() - mLastUpdate > INTERVAL_BROADCAST) {
mLastUpdate = System.currentTimeMillis();
int[] progresses = new int[downloadTable.size()];
String[] packetIds = new String[downloadTable.size()];
int index = 0;
Enumeration<String> enumKey = downloadTable.keys();
while (enumKey.hasMoreElements()) {
String key = enumKey.nextElement();
int val = downloadTable.get(key).progress;
progresses[index] = val;
packetIds[index++] = key;
}
Intent i = new Intent();
i.setAction(PROGRESS_UPDATE_ACTION);
i.putExtra("packetIds", packetIds);
i.putExtra("progress", progresses);
mBroadcastManager.sendBroadcast(i);
}

class DownloadFile extends AsyncTask<Void, Integer, Void> {
private MessageEntity entity;
private File file;
private int progress;

public DownloadFile(MessageEntity entity) {
this.entity = entity;
}

@Override
protected Void doInBackground(Void... arg0) {
String filename = entity.getMediaURL().substring(entity.getMediaURL().lastIndexOf('/') + 1);
file = new File(FileUtil.getAppStorageDir().getPath(), filename);
downloadFile(entity.getMediaURL(), file);
return null;
}

public String downloadFile(String download_file_path, File file) {
int downloadedSize = 0;
int totalSize = 0;
try {
// download the file here
while ((bufferLength = inputStream.read(buffer)) > 0 && !isCancelled()) {

progress = percentage;
publishCurrentProgressOneShot(true);
}

} catch (final Exception e) {
return null;
}

return file.getPath();
}

}

Download multiple files with one progressbar java / Android

I think you have 2 options:

The fake progress bar approach

You know in advance how many files you need to download, you can set the ProgressDialog total amount to the number of files to download. This works pretty well with files which are small and similar in dimensions and gives the user a good feedback about what's going on.

// you can modify the max value of a ProgressDialog, we modify it
// to prevent unnecessary rounding math.
// In the configuration set the max value of the ProgressDialog to an int with
pDialog.setMax(urls.length);

for (int i = 0; i < urls.length; i++) {
// launch HTTP request and save the file
//...
// your code
//...

//advance one step each completed download
publishProgress();
}

/**
* Updating progress bar
*/
protected void onProgressUpdate(Integer... progress) {
pDialog.incrementProgressBy(1);
}

The real progress bar approach

You need to know in advance the total length of all the files you need to download. For example, you could create a separate REST API to call before everything else which give you the total length in bytes before you start to download each separate files. In this way, you can periodically update the total ProgressDialog length accordingly to the total bytes you have already downloaded.



Related Topics



Leave a reply



Submit