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
- Create a
service
havingAsynctask
for downloading andhashtable
of values(url, Asynctask) - 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
- now for updating progress in my
adapter
I ran a thread which iterate overhashtable
and passes the value usingBroadcastListener
. - And in activity intercept the
broadcast
and depending on theListItem
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
Java.Lang.Illegalstateexception: Fragment Not Attached to Activity
Fcm (Firebase Cloud Messaging) Push Notification with ASP.NET
Android Navigation Architecture Component - Get Current Visible Fragment
Adding Tab Inside Fragment in Android
Cordova Plugin Development - Adding Aar
Android Open Camera from Button
How to Open Private Files Saved to the Internal Storage Using Intent.Action_View
Disable Home Button in Android Toddler App
Save Sensitive Data in React Native
Error: Execution Failed for Task ':App:Clean'. Unable to Delete File
Rawquery(Query, Selectionargs)
How to Get Add to Home Screen Pop Up on Site Open in Mobile Browser
Extended Surfaceview's Ondraw() Method Never Called
Dynamic Resource Loading Android