高校生ブランドがあと数日でなくなりますね
本題
ファイルダウンロードが遅いのでいくつかに分けて並列ダウンロードしたい
ただでさえ速度が遅いんだからさあ
分割ダウンロードの調査
https://developer.mozilla.org/ja/docs/Web/HTTP/Range_requests
指定した範囲(バイト)をリクエストできるらしい
ダウンローダーによくある一時停止からの再開機能はこれを使ってるそう
他のブラウザはしらんけど、Chrome
のvideoタグ
でシークバーを動かすとそのたびにこの部分リクエストを飛ばしてるっぽい
リクエストヘッダに、
と入れると0から50までのデータが返ってくるそう。
ちなみに次のデータ(例えば50バイト分)をリクエストする際は50
ではなく51
からにする必要がある?
name | value |
---|
Range | bytes=51-100 |
https://triple-underscore.github.io/RFC7233-ja.html#p.byte-ranges-specifier
ちなみにステータスコードは206
になる
使うもの
OkHttp
HTTPクライアント。WebAPI叩いたり色んな所で使ってる。
Android 5以上対応
Coroutine
難しい。非同期処理(アプリの画面を止めない)を書くときに幸せになれるやつ。
難しいんで例をあげると
- コールバック地獄を脱却(たとえなので動きませんが)
lifecycleScope.launch
、省略時はUIスレッドになります
そして今回使う予定の、asunc { }
ので、並列にリクエストしたいですよね?そこで使うのがasync { }
です。
また、配列を使うともう少しきれいに書くことができます。
他のプログラミング言語では、関数を作る際にasync
を使うと思うんですが、
KotlinのCoroutine
では呼ぶ際にasync { }
を使うことになります。(のでasync/await
のことは忘れたほうがわかるかも)
本編
話題が逸れ過ぎた。
環境
Kotlinで行きます
ダウンロードしたいファイルのURLを確保しておいてね
仕様
保存先はユーザーに選んでもらう形式を取る。
本当はダウンロードディレクトリに自動で入れたいんだけど、Android 10
以降しか提供されていない
(すいませんこれは嘘でAndroid 9以前でもできるんだけど実装方法が全然違うのでめんどい)
私がやりたいのはファイルダウンロードであって、
AndroidのMediaStore
ではない。やりだしたらもう収集がつかない。
(Androidは生(意味深)ファイルパスでのファイルアクセスが一部を除き禁止されているため)
ダウンロード処理はViewModel
に書くとします
ライブラリ
OkHttp
とCoroutine
(とそれ関係)を入れます。
app/build.gradle
に書き足す。ViewBindingも有効にしてください。
AndroidManifest.xml
インターネット権限が必要です。(よく書き忘れる)
activity_main.xml
開始ボタンとプログレスバーをおきます
MainActivity.kt
とりあえずViewBinding
を使えるようにしておいてください
MainActivityViewModel.kt
を作成して、コピペします。
しばらくはこのクラスにプログラムを書いていきます
ファイルのサイズを取得する
これからファイルをダウンロードするわけですが、分割してダウンロードするにはまず元のファイルサイズを求めておく必要があります。
ここでほしいのはレスポンスヘッダなので、レスポンスボディをリクエストしないHEAD
リクエストを飛ばします。
分割する関数
長ったらしい。
ちなみにこの関数の返り値はこんな感じになります
ファイルをダウンロードする
今回はHEAD
ではなくGET
リクエストですよ!
と、その前にダウンロードした分割ファイルを持っておくわけにもいかないので、一旦ファイルに書き込みます。その保存先フォルダを先に作っておきます
そしたらダウンロードする関数を書きましょう。拡張子にファイルの順番を付けてます
ファイルを結合する関数
結合したファイル(File
)を返してあげてください。後で使う
ユーザーの選んだ保存先に保存する
まだユーザーに保存先を選んでもらう処理は書いてませんが。保存先を選ぶとファイルパス、、、ではなくUri
が返ってくるのでこれを使います。
Activityから呼ぶ関数を書く
start
関数を書いて、Activityからはこの関数を呼ぶことにします
ここまでのコード
MainActivity.kt
Storage Access Frameworkを開いてユーザーに保存先を選んでもらいます
あとViewModel
も使えるようにしておいてね
あとURLにはダウンロードしたいファイルのURLを入れておいてください。
これでボタンを押して、保存先を選ぶとダウンロードが開始されるはずです。
プログレスバーは動きませんが
プログレスバーを動かす
変数を宣言して
start関数を少し書き足す
requestFile
関数に書き足す
最後にMainActivity
これでプログレスバーも動くと思います。
やってること
- ファイルのサイズを求める
- 分割数に合わせてバイト数を決定
- 並列ダウンロード。一旦保存
- 一旦保存したファイルを一つにまとめて保存
- UriからOutputStreamを取得して書き込む
- まとめたファイル、一旦保存したファイルを削除
まーじでAndroidのファイル操作どうにかならねえのこれ
そしてstart
関数を少し書き換える
MainActivity
の方も書き換えます
おわりに
つかれた。ソースコード置いておきますね。
https://github.com/takusan23/OkHttpRangeDownload