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つのボタンを持っているアクティビティが見えます。
権限
インターネットからファイルをダウンロードするため、 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に実装されていて別々に実装する必要がありません。
ダウンロードの状態を確認(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_FAILED
、STATUS_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を出力します。
ダウンロードをキャンセル
ダウンロード要求されたことについてのキャンセルもできます。 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
- エラー解決:android gradle plugin requires java 11 to run. you are currently using java 1.8.
- Android - コルーチンとRetrofitによる非同期通信の例
- Android - コルーチンでURL画像を読み込む
- Android - 振動、Vibrator、VibrationEffectの例
- Some problems were found with the configuration of task
- Query method parameters should either be a type that can be converted into a database column or a List
- UbuntuでAndroid 12オープンソースをダウンロードしてビルド
- Android - ViewModelを生成する方法
- Android - Transformations.map(), switchMap() の違い
- Android-Transformations.distinctUntilChanged()소개
- Android - TabLayoutの実装方法(+ ViewPager2)
- Android - 携帯電話の電話番号を取得する方法
- Android 12 - Splash Screens
- Android 12 - インクリメンタルインストール
- Android - adbコマンドでbugreportログファイルの抽出
- Android - adbコマンドでAppデータを削除する
- Android - adbコマンドでアプリ無効化、有効化
- Android - adbコマンドで特定のパッケージのPIDを検索
- Android - adbコマンドでパーミッションGrantまたはRevoke
- Android - adbコマンドでapkのインストール、削除、
- Android - adbコマンドで特定のパッケージのプロセスの終了
- Android - adb push、pullでファイルのコピー、ダウンロード
- Android - adbコマンドでscreen capture保存
- Android - adbコマンドでSystemアプリの削除、インストール
- Android - adbコマンドでsettings value確認、変更、
- Android 12 - IntentFilterのexported明示的な宣言
- Android - adbコマンドで工場出荷時の(Factory reset)
- Android - adb logcatコマンドでログ出力
- Android - adbコマンドでメモリダンプ(dump-heap)
- Android - adbコマンドでApp強制終了(force-stop)
- Android - adbコマンドでServiceの実行、終了
- Android - adbコマンドでBroadcast配信
- Android - adbコマンドでActivity実行
- Android - PackageManagerにPackage情報を取得する
- Android - ACTION_BOOT_COMPLETEDイベント受信