Mostrar o andamento do download dentro da atividade usando o DownloadManager

89

Estou tentando reproduzir o mesmo progresso que o DownloadManager mostra na barra de notificação dentro do meu aplicativo, mas meu progresso nunca é publicado. Estou tentando atualizá-lo usando runOnUiThread (), mas por algum motivo ele não foi atualizado.

meu 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();

meu método statusMessage:

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

Meu log está funcionando perfeitamente, enquanto meu download está em execução diz "Download em andamento!" e quando estiver completo "Download concluído!", mas o mesmo não ocorre no meu progresso, por quê? Eu realmente preciso de alguma ajuda, outras lógicas para fazer que são muito apreciadas

Victor Laerte
fonte
Será que seu arquivo é simplesmente muito pequeno e o download é concluído antes da publicação do andamento? O que a consulta de download retorna em sua tarefa? Se a tarefa só for executada após algum período de tempo, você pode ter alguma outra operação de longa duração em seu thread principal.
Paul Lammertsma
Eu atualizei o código, você pode dar uma olhada agora? E sobre o tamanho do arquivo não ser muito pequeno, posso ver o andamento do download na barra de notificação
Victor Laerte

Respostas:

62

Você está dividindo dois inteiros:

final double dl_progress = (bytes_downloaded / bytes_total) * 100;

Se bytes_downloadedfor menor que bytes_total, (bytes_downloaded / bytes_total)será 0 e seu progresso, portanto, sempre será 0.

Altere o seu cálculo para

final int dl_progress = (int) ((bytes_downloaded * 100l) / bytes_total);

para obter o progresso em percentis inteiros (embora com base).

Paul Lammertsma
fonte
@AZ_ Obrigado por sua contribuição. Eu sugiro que você adicione sua própria resposta com a solução mais elaborada.
Paul Lammertsma
Tudo bem, não quero colocar outra resposta de resposta já aceita porque vai ser difícil para os usuários. Você pode optar por não aceitar minha edição :)
AZ_
1
Se sua atividade terminar e você quiser cancelar o download, você obterá um division by zeroerro fatal. É por isso que eu fiz assim final int dl_progress = ( bytes_total > 0 ? (int) ((bytes_downloaded * 100L) / bytes_total) : 0 );
KaHa6uc
17

A resposta de Paul está correta, mas com downloads maiores você atingirá o máximo int bem rápido e começará a ter um progresso negativo. Usei isso para resolver o problema:

final int dl_progress = (int) ((bytes_downloaded * 100l) / bytes_total);
JustinMorris
fonte
Você está certo; Modifiquei minha resposta para garantir que outros não cometam o mesmo erro.
Paul Lammertsma
4

Como paul disse, você está dividindo dois inteiros, com resultado sempre <1.

Sempre lance seu número antes de dividir, que calcula e retorna em ponto flutuante.

Não se esqueça de lidar com DivByZero.

final int dl_progress = (int) ((double)bytes_downloaded / (double)bytes_total * 100f);
Dennis C
fonte
4

Caso alguém precise da implementação do recuperador de progresso de download da pergunta de @Victor Laerte em Kotlin com RxJava, aqui vai:

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

Eu adicionei extensões ao cursor para mais clareza de código:

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))
Rahimli
fonte