Show Download progress inside activity using DownloadManager
AndroidProgress BarAndroid Problem Overview
I'm trying to reproduce the same progress that DownloadManager shows in Notification bar inside my app, but my progress never is published. I'm trying to update it using runOnUiThread(), but for some reason it's not been updated.
my download:
String urlDownload = "https://dl.dropbox.com/s/ex4clsfmiu142dy/test.zip?token_hash=AAGD-XcBL8C3flflkmxjbzdr7_2W_i6CZ_3rM5zQpUCYaw&dl=1";
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(urlDownload));
request.setDescription("Testando");
request.setTitle("Download");
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "teste.zip");
final DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
final long downloadId = manager.enqueue(request);
final ProgressBar mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
new Thread(new Runnable() {
@Override
public void run() {
boolean downloading = true;
while (downloading) {
DownloadManager.Query q = new DownloadManager.Query();
q.setFilterById(downloadId);
Cursor cursor = manager.query(q);
cursor.moveToFirst();
int bytes_downloaded = cursor.getInt(cursor
.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
int bytes_total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) {
downloading = false;
}
final double dl_progress = (bytes_downloaded / bytes_total) * 100;
runOnUiThread(new Runnable() {
@Override
public void run() {
mProgressBar.setProgress((int) dl_progress);
}
});
Log.d(Constants.MAIN_VIEW_ACTIVITY, statusMessage(cursor));
cursor.close();
}
}
}).start();
my statusMessage method:
private String statusMessage(Cursor c) {
String msg = "???";
switch (c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS))) {
case DownloadManager.STATUS_FAILED:
msg = "Download failed!";
break;
case DownloadManager.STATUS_PAUSED:
msg = "Download paused!";
break;
case DownloadManager.STATUS_PENDING:
msg = "Download pending!";
break;
case DownloadManager.STATUS_RUNNING:
msg = "Download in progress!";
break;
case DownloadManager.STATUS_SUCCESSFUL:
msg = "Download complete!";
break;
default:
msg = "Download is nowhere in sight";
break;
}
return (msg);
}
My log is working perfectly, while my download is running says "Download in progress!" and when it is complete "Download complete!", but the same doesn't occurs on my progress, why? I really need some help, other logics to do that are really appreciated
Android Solutions
Solution 1 - Android
You are dividing two integers:
final double dl_progress = (bytes_downloaded / bytes_total) * 100;
As bytes_downloaded
is less than bytes_total
, (bytes_downloaded / bytes_total)
will be 0, and your progress will therefore always be 0.
Change your calculation to
final int dl_progress = (int) ((bytes_downloaded * 100l) / bytes_total);
to obtain the progress in whole (albeit floored) percentiles.
Solution 2 - Android
Paul's answer is correct but with larger downloads you will hit max int pretty quick and start getting a negative progress. I used this to solve the issue:
final int dl_progress = (int) ((bytes_downloaded * 100l) / bytes_total);
Solution 3 - Android
In case if someone needs implementation of download progress retriever from @Victor Laerte's question in Kotlin with RxJava here you go:
DownloadStateRetriever.kt
class DownloadStateRetriever(private val downloadManager: DownloadManager) {
fun retrieve(id: Long) {
var downloading = AtomicBoolean(true)
val disposable = Observable.fromCallable {
val query = DownloadManager.Query().setFilterById(id)
val cursor = downloadManager.query(query)
cursor.moveToFirst()
val bytesDownloaded = cursor.intValue(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
val bytesTotal = cursor.intValue(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
if (isSuccessful(cursor)) downloading.set(false)
cursor.close()
if (bytesTotal == 0) 0.toInt() else ((bytesDownloaded * 100F) / bytesTotal).toInt()
}
.subscribeOn(Schedulers.newThread())
.delay(1, TimeUnit.SECONDS)
.repeatUntil { !downloading.get() }
.subscribe {
Timber.i("Subscribed to $id. progress: $it")
}
}
private fun isSuccessful(cursor: Cursor) = status(cursor) == DownloadManager.STATUS_SUCCESSFUL
private fun status(cursor: Cursor) = cursor.intValue(DownloadManager.COLUMN_STATUS)
}
I have added extensions to cursor for more code clarity:
CursorExtensions.kt
import android.database.Cursor
fun Cursor.column(which: String) = this.getColumnIndex(which)
fun Cursor.intValue(which: String): Int = this.getInt(column(which))
fun Cursor.floatValue(which: String): Float = this.getFloat(column(which))
fun Cursor.stringValue(which: String): String = this.getString(column(which))
fun Cursor.doubleValue(which: String): Double = this.getDouble(column(which))
Solution 4 - Android
As paul said, you are dividing two integers, with result always <1.
Always cast your number before divide, which calculate and return in float-point.
Don't forget to handle DivByZero.
final int dl_progress = (int) ((double)bytes_downloaded / (double)bytes_total * 100f);
Solution 5 - Android
And in case if someone need to implementation download progress with kotlin and flows - i hope this help someone. This is @rahimli version with adding flows instead of RxJava
Retriever
private fun retrieve(id: Long) = flow {
val downloading = AtomicBoolean(true)
while (downloading.get()) {
val query = DownloadManager.Query().setFilterById(id)
val cursor = downloadManager.query(query)
cursor.moveToFirst()
val bytesDownloaded = cursor.intValue(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
val bytesTotal = cursor.intValue(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
if (isSuccessful(cursor)) downloading.set(false)
cursor.close()
emit(DownloadingState.Downloading(bytesDownloaded, bytesTotal))
if (downloading.get()) delay(1000)
}
}.flowOn(Dispatchers.IO)
Some helpers/extensions
private fun isSuccessful(cursor: Cursor) = status(cursor) == DownloadManager.STATUS_SUCCESSFUL
private fun status(cursor: Cursor) = cursor.intValue(DownloadManager.COLUMN_STATUS)
private fun Cursor.column(which: String) = this.getColumnIndex(which)
private fun Cursor.intValue(which: String): Int = this.getInt(column(which))
sealed class DownloadingState {
data class Downloading(val downloadedBytes: Int, val totalBytes: Int) : DownloadingState() {
val progress = if (totalBytes == 0) 0 else ((downloadedBytes * 100) / totalBytes)
}
object Failure : DownloadingState()
}