Upgrade to Pro — share decks privately, control downloads, hide ads and more …

大きい画像をたくさんアップロードするために

 大きい画像をたくさんアップロードするために

2018/12/11に開催されたOtemachi.apk #1にて発表した「大きい画像をたくさんアップロードするために」のスライドです。

0f50b010cc99988fba8a73008b21f353?s=128

Yoshihiro WADA

December 11, 2018
Tweet

Transcript

  1. 大きい画像をたくさんアップロードするために Cyber Agent Inc. Yoshihiro Wada a.k.a. @e10dokup 2018/12/11 Otemachi.apk

  2. 自己紹介 • Yoshihiro Wada a.k.a. @e10dokup • 大体左のアイコンで息をしています • CyberAgant

    Inc. / Ameba • Amebaブログやってます • We’re Hiring • カメラ沼 • 財布の透過率が高いのが最近の悩み
  3. 今回のお話 • 大きい画像をいっぱいアップロードしたい • スマホで撮った写真とか • デジタルカメラやFlashAirからWi-Fiで持ってきた写真とか • 別に画像に限った話ではない •

    立ちはだかる壁 • 通信量 • モバイルネットワークでやると無限にギガが減る • メモリ • 展開の仕方によってはOOMでドゥンとクラッシュしてしまう
  4. 普通に考えると… • そもそも大きい画像を扱わないようにする • スマートフォンの小さい画面に2000万画素オーバーの大きさのサイズはオーバースペック • 500万画素くらいの大きさならまだわかるレベルになる • 「Android 画像

    リサイズ」で検索すると出てくるようなお話 • UI上に扱う画像を表示する際はもちろんリサイズは大事 • 今回本当にやりたいこと • UI上の表示ではなく、Google Photosのようなアルバムサービスへのアップロード • できるなら撮影した画質そのままで扱いたい • 圧縮しようのないデータのアップロード
  5. というわけでやっていきましょう

  6. まず一連の流れを把握 • 扱う画像・ファイルを取得する • ストレージだったり、FlashAirだったり、LAN内のNASだったり… • この時点で端末メモリ上には配置される • 端末メモリ上に配置したファイルをアップロード •

    ファイルアップロードが完了するまでメモリ上には当然残る • 失敗したら…の処理も存在する • これがファイル1つに対しての処理の流れになる • 複数存在している場合は当然複数回繰り返す、並列に走らせるといった対応が必要
  7. だいたいこんな感じ…? ファイル取得 アップロード ファイル取得 アップロー とか ファイル取得 アップロード ファイル取得 アップロード

    時系列
  8. 要するに、同時に走る処理の数を いい感じに制限してあげればいい

  9. やってみる • ファイル一覧がListで存在 • 画像取得、アップロードを行う処理を作る • 具体的な処理についてはキリが無くなるので省略で…

  10. Rxだとこんな感じ Observable.fromIterable(fileList) .concatMap { // ϑΝΠϧऔಘॲཧ } .concatMap { //

    Ξοϓϩʔυॲཧ } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { // ௨৴׬ྃ࣌ॲཧ }
  11. Rxだとこんな感じ Observable.fromIterable(fileList) .concatMap { // ϑΝΠϧऔಘॲཧ } .concatMap { //

    Ξοϓϩʔυॲཧ } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { // ௨৴׬ྃ࣌ॲཧ } ファイルリストをObservableに
  12. Rxだとこんな感じ Observable.fromIterable(fileList) .concatMap { // ϑΝΠϧऔಘॲཧ } .concatMap { //

    Ξοϓϩʔυॲཧ } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { // ௨৴׬ྃ࣌ॲཧ } 流れてくるストリームをファイル取得に使う。 (ストレージアクセスなりネットワークアクセスなり)
  13. Rxだとこんな感じ Observable.fromIterable(fileList) .concatMap { // ϑΝΠϧऔಘॲཧ } .concatMap { //

    Ξοϓϩʔυॲཧ } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { // ௨৴׬ྃ࣌ॲཧ } 流れてきたファイルをアップロード
  14. えっ、これで大丈夫なの? • fromIterableでファイルリストの中身を一つずつストリームに流している • flatMapではなくconcatMapでストリーム順序を優先するようにした • 50MBのファイルがストリームに流れると流石にOOMが出るので注意 • Android Profilerでメモリアロケーションを見てるとGCがだいぶ愉快

  15. という感じなんですが 手元では問題無く動いているように見えるだけで 自信がないので詳しい人に助けてほしい…

  16. • ファイル一つに対しての処理は以下の感じ • ファイルを取得する • その結果をawaitして、アップロードにつなげる • この処理自体もawaitして、終わり次第次のファイルについて同様の処理をする…
 といった再帰的な実行をすることになりそう たぶんCoroutinesだと

    ファイル取得 アップロード await await 全部終了 まだ次がある
  17. アクセシビリティに気をつける • ファイルサイズが大きいぶん、当然アップロードまでにかかる処理が長い • 画面でダイアログ出してぐるぐる…なんてやってるとキリがない • アップロード処理をフォアグラウンドサービスに移行させる • アップロード中であることの通知をしっかりさせる •

    巨大なアップロードになるのでモバイルネットワーク通信時にアップロードさせない • NetworkInfoのTypeがTYPE_WIFIになっている時以外はブロックする…といった処理も
 検討する
  18. フォアグラウンドサービスでアップロードさせる • サービス起動時にフォアグラウンドで通知させることが必要 • Android O + API Level 26から

    • Context.startForegroundService() で起動 • サービス起動から5秒以内に Service.startForeground() しないとクラッシュする • RemoteServiceException • アップロードする観点では起動直後に通知させるで良さそう • 処理が終了したら stopSelf(), stopForeground() で止めないと通知が残り続ける • ちゃんと止めよう
  19. サービス起動 fun start(context: Context) { val intent = Intent(context,ForegroundService::class.java) if

    (ifVersionLaterThanO()) { // check SDK_INT context.startForegroundService(intent) } else { context.startService(intent) } }
  20. 起動直後に通知して処理を実行 override fun onCreate() { super.onCreate() // Ξοϓϩʔυ௨஌༻ʹnotificationΛ࡞͓ͬͯ͘ startForeground(NOTIFICATION_ID, notification)

    uploadItems() { // Ξοϓϩʔυ׬ྃͰαʔϏεΛऴྃ͢Δ stopSelf() } }
  21. おわり