アンドロイド - DownloadManagerにファイルダウンロードを受ける方法

By JS | Last updated: January 13, 2019

DownloadManagerは、HTTPファイルをダウンロード受ける役立つシステムサービスです。アプリは、保存する場所のURIとダウンロードすることがHTTP URIをDownloadManagerに教えてやるだけの世話を受けています。 また、DownloadManager内部のバックグラウンドサービスでダウンを受けるためのアプリでスレッドを生成する必要はありません。

ダウンロードマネージャーは、ノーフィケーションにダウンロードの状態を示し、完了したら、ブロードキャストで完了したことを示します。また、リアルタイムにダウンロードの状態をチェックすることもできます。

利点を整理すると、次のとおりです。

  • アプリからダウンロードするためのバックグラウンドスレッドを作成する必要がありません。
  • ノフィケーションを別々に設定する必要がありません。
  • ダウンロードが完了すると、ブロードキャストでお知らせします。
  • 不安定なネットワークの状態の例外処理がされています。ダウンロードに失敗した場合、再試行することができます。

どのようにダウンロードマネージャを使用するかの例を介して説明します。

完成された例では、GitHubにあります。

UIコンポーネント

ダウンロード要求、状態確認、取り消し処理をするためにボタンを3つ作成しました。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <Button
            android:id="@+id/downloadBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Download"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

    <Button
            android:id="@+id/statusBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Status"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/downloadBtn"/>

    <Button
            android:id="@+id/cancelBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Cancel"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/statusBtn"/>

</androidx.constraintlayout.widget.ConstraintLayout>

アプリを起動してみると、3つのボタンを持っているアクティビティが見えます。

android downloadmanager

権限

インターネットからファイルをダウンロードするため、 AndroidManifest.xmlにインターネットパーミッションを必ず追加する必要があります。

<uses-permission android:name="android.permission.INTERNET"/>

のダウンロード要求(Request)

ダウンロードボタンを押すと、DownloadManagerにファイルのダウンロードを要求するように実装しました。

downloadBtn.setOnClickListener {
    downloadImage()
}

ダウンロード要求は DownloadManager.Requestオブジェクトを作成し、DownloadManager.enqueueを利用して、追加のみしてくれればされます。 Requestオブジェクトには通知などの設定やダウンロードの制限を設定することができます。

ダウンロードマネージャQueueにRequestが追加されると、ダウンロードマネージャのバックグラウンドで世話ファイルをダウンロードします。 アプリとは別にバックグラウンドスレッドを作成する必要がありません。

enqueueはdownloadIdを返しますよ。このIdはダウンロード状態を知っているかの結果を確認するために必要です。

private fun downloadImage() {
    val file = File(getExternalFilesDir(null), "dev_submit.mp4")
    val youtubeUrl = "https://r2---sn-oguelney.googlevideo.com/videoplayback?expire=1547361698&ratebypass=yes&ipbits=0&txp=5431432&fvip=2&sparams=clen,dur,ei,expire,gir,id,ip,ipbits,itag,lmt,mime,mip,mm,mn,ms,mv,pl,ratebypass,requiressl,source,usequic&dur=2487.205&source=youtube&id=o-AFU5WYmppmSyvhuN-vYHnA9zb_qazPL5JANaBNepI9ZF&requiressl=yes&lmt=1541762056298111&itag=18&ip=52.78.151.237&clen=106473489&signature=02FE1AD7C3C0FDFB173383A9D48A1374FDDE9470.31A7D7D3F98045946B0DD3C07436A241B223B84D&ei=Qok6XOqPGI3DqQHxlL_4CA&pl=24&key=cms1&c=WEB&gir=yes&mime=video%2Fmp4&redirect_counter=1&cm2rm=sn-oguy67l&req_id=471900e67ee3a3ee&cms_redirect=yes&mip=182.228.195.55&mm=34&mn=sn-oguelney&ms=ltu&mt=1547339611&mv=u&usequic=no"

    val request = DownloadManager.Request(Uri.parse(youtubeUrl))
        .setTitle("Downloading a video")
        .setDescription("Downloading Dev Summit")
        .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
        .setDestinationUri(Uri.fromFile(file))
        .setRequiresCharging(false)
        .setAllowedOverMetered(true)
        .setAllowedOverRoaming(true)

    downloadId = downloadManager.enqueue(request)
    Log.d(TAG, "path : " + file.path)
}

Requestに設定する項目は次のとおりです。

  • DownloadManager.Request: Requestオブジェクトを生成し、引数としてダウンロードするファイルのURIを渡します。
  • setTitle: ノーフィケーションに見えるタイトルです。
  • setDescription: ノーフィケーションに見える記述です。
  • setNotificationVisibility: VISIBILITY_VISIBLEに設定されると、ノーフィケーションに示します。
  • setDestinationUri: ファイルが保存される位置のURIです。
  • setRequiresCharging: Trueに設定すると、端末が充電中にのみダウンロードします。
  • setAllowedOverMetered: Trueに設定すると、モバイルネットワークが接続されても、ダウンロードします。
  • setAllowedOverRoaming: Trueに設定すると、ローミングネットワークが接続されても、ダウンロードします。

setNotificationVisibilityに設定するためのオプションとして、以下のようなものがあります。

  • VISIBILITY_VISIBLE: ダウンロードが進行中である場合にのみ、ノーを示し、完了すると、表示されません。
  • VISIBILITY_VISIBLE_NOTIFY_COMPLETED: ダウンロード進行中と完了したときに、すべてノーを示しています。
  • VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION: ダウンロードが完了した場合にのみノーを示しています。
  • VISIBILITY_HIDDEN: ノーを示しません。

ダウンロードが進行すると、ノーフィケーションで進行状況をお知らせします。この部分は、DownloadManagerに実装されていて別々に実装する必要がありません。 android downloadmanager

ダウンロードの状態を確認(Status)

状態ボタンを押すと、ダウンロードの状態をトーストに出力するようにしました。

statusBtn.setOnClickListener {
    val status = getStatus(downloadId)
    Toast.makeText(this, status, Toast.LENGTH_SHORT).show()
}

ダウンロードIDがあれば、DownloadManagerに照会することができます。 DownloadManager.Queryオブジェクトを作成し、DownloadManager.query APIにクエリをします。 戻り値としてcursorが戻され、特定のカラムを参照して、ダウンロードの状態を取得することができます。

private fun getStatus(id: Long): String {
    val query: DownloadManager.Query = DownloadManager.Query()
    query.setFilterById(id)
    var cursor = downloadManager.query(query)
    if (!cursor.moveToFirst()) {
        Log.e(TAG, "Empty row")
        return "Wrong downloadId"
    }

    var columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
    var status = cursor.getInt(columnIndex)
    var columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON)
    var reason = cursor.getInt(columnReason)
    var statusText: String

    when (status) {
        DownloadManager.STATUS_SUCCESSFUL -> statusText = "Successful"
        DownloadManager.STATUS_FAILED -> {
            statusText = "Failed: $reason"
        }
        DownloadManager.STATUS_PENDING -> statusText = "Pending"
        DownloadManager.STATUS_RUNNING -> statusText = "Running"
        DownloadManager.STATUS_PAUSED-> {
            statusText = "Paused: $reason"
        }
        else -> statusText = "Unknown"
    }

    return statusText
}

ダウンロードの状態と意味は以下の通りです。

  • STATUS_SUCCESSFUL: ダウンロードが正常に完了した
  • STATUS_FAILED: ダウンロードが失敗されました
  • STATUS_RUNNING: 現在のダウンロードが進行中
  • STATUS_PAUSED: ダウンロードが停止され、続いたり、再取得を待っている状態

ダウンロードの状態が STATUS_FAILEDSTATUS_PAUSEDあるとき、このようにされた理由(Reason)を知ることができます。

Reasonの種類と、以下のようなものがあります。

  • PAUSED_WAITING_TO_RETRY
  • PAUSED_WAITING_FOR_NETWORK
  • PAUSED_QUEUED_FOR_WIFI
  • PAUSED_UNKNOWN
  • ERROR_FILE_ERROR
  • ERROR_UNHANDLED_HTTP_CODE
  • ERROR_HTTP_DATA_ERROR
  • ERROR_TOO_MANY_REDIRECTS
  • ERROR_TOO_MANY_REDIRECTS
  • ERROR_INSUFFICIENT_SPACE
  • ERROR_DEVICE_NOT_FOUND
  • ERROR_CANNOT_RESUME
  • ERROR_FILE_ALREADY_EXISTS
  • ERROR_UNKNOWN

アプリを起動し、ダウンロードボタンを押した後Statusボタンを押すと、Runningを出力します。

android downloadmanager

ダウンロードをキャンセル

ダウンロード要求されたことについてのキャンセルもできます。 DownloadManager.remove APIに引数としてdownloadIdを渡します。

[キャンセル]ボタンを押すと、要求されたIdに対してキャンセルするように実装しました。

cancelBtn.setOnClickListener {
    if (downloadId != -1L) {
        downloadManager.remove(downloadId)
    }
}

ダウンロード結果の受信、ノークリックイベント受信

ダウンロード要求が完了すると、ブロードキャストでの結果を提供してくれます。ダウンロードが成功するか、または失敗しても結果を提供します。

ユーザーがノーティをクリックしたときにもノーフィケーションを送信します。 ダウンロードがキャンセルされた場合は、再度ダウンロードするUIを表示したり、ダウンロードしたファイルの詳細情報を表示するように実装することができます。

まずDownloadManagerにダウンロードを要求する前に、ブロードキャストを登録する必要があります。 DownloadManagerは次二つのIntentを渡すことができます。

  • ACTION_DOWNLOAD_COMPLETE: ダウンロード要求が完了(成功または失敗)されると、このインテントを渡されます。
  • ACTION_NOTIFICATION_CLICKED: ユーザーがノーフィケーションをクリックすると、このインテントが配信されます。

動的にブロードキャストレシーバを登録しました。

val intentFilter = IntentFilter()
intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
intentFilter.addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED)
registerReceiver(onDownloadComplete, intentFilter)

ブロードキャストレシーバは、以下のように実装することができます。

private val onDownloadComplete = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.action)) {
            if (downloadId == id) {
                val query: DownloadManager.Query = DownloadManager.Query()
                query.setFilterById(id)
                var cursor = downloadManager.query(query)
                if (!cursor.moveToFirst()) {
                    return
                }

                var columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
                var status = cursor.getInt(columnIndex)
                if (status == DownloadManager.STATUS_SUCCESSFUL) {
                    Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show()
                } else if (status == DownloadManager.STATUS_FAILED) {
                    Toast.makeText(context, "Download failed", Toast.LENGTH_SHORT).show()
                }
            }
        } else if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(intent.action)) {
            Toast.makeText(context, "Notification clicked", Toast.LENGTH_SHORT).show()
        }
    }
}

インテントは、複数のIDに対する応答が来ることができますので、downloadIdをチェックする必要があります。 ダウンロードが失敗した場合でも、インテントが配信されるので、状態をチェックする必要があります。

参考

Related Posts

codechachaCopyright ©2019 codechacha