2013年12月25日水曜日

BackgroundTransfer を諦めた技術的背景 (その1)


今回、ストアアプリ WiFiSD8 で、所謂ダウンローダ…IriaやIrvineのような、複数アイテムを順次ダウンロードする機能…を実現しようとしました。

が、結果的には
「ストアアプリ特有のApplication Lifecycle Management 環境下ではBackground Transfer機能をもってしても中断・終了への対応は困難であり、ユーザーにアプリを前面に表示し続けてもらわない限り、複数アイテムの順次DLは難しい」
という事になってしまいました。

以下はこの結論に至るまでの逡巡の記録です。
こうすれば出来たのでは、というツッコミは大歓迎です。是非真似させてください。

StoreApp の Application LifeCycle Management について


ストアアプリの終了・中断は基本的にOSにより管理されます。これをApplication Lifecycle Management, ALM と呼びます。
ALM下では、アプリは3つの状態で管理されます。

動作(Running) アプリが前面に表示されている状態。ユーザーと対話し、普通に動作している状態です。

中断(Suspend) アプリが背面に移動すると、OSはアプリを中断します(アプリに拒否権は無い)。アプリが前面に表示されるとすぐに動作状態に復帰します。

終了(Terminated) システムのメモリがきつくなってくる等した場合、OSはアプリを終了します(アプリ側に拒否権は無い)。

アプリが背面に移動すると数秒で中断されます。また、アプリを複数同時起動していると、アプリはそこそこ頻繁に終了されます。

Desktop Appと大きく異なる点として、
  • Appを(いつでも)中断・終了する権利をOSが持っていること
  • Suspend状態があり、極めて頻繁にSuspend状態に入れられる事 またそこそこ頻繁に終了されること
 が挙げられます。

前者により、ストアアプリは何時OSに叩き殺されても構わないようにデータをマメに保存し、後者により、長い時間のかかるタスクは結構困る、という特徴(弱点)を持っています。
反面、Desktopのように長い時間使っているとプロセスがやたら増えてなんとなく不調、DiskやNetworkのような資源をアプリが使い放題、的な状況は起きにくい、という事でもあります。
 

BackgroundTransfer


さて、今回の…いわゆるダウンローダ、複数アイテムを一括で順次ダウンロードするアプリの場合、普通にHttp Clientを使うとアプリ中断でダウンロードも中断してしまうので困ったことになります。

このため、Win8/8.1では「BackgroundTransfer」という機能がOSに用意されています。
これは、OSに「このURLからファイルをDLして下さい」と頼むと、OSがダウンロードを行ってくれる、という機能です。ダウンロードの主体はOSであるため、アプリが中断している間も継続します。

…ですがこの機能、二つ問題があります。

  1. 複数アイテムのDLが使いにくい
  2. アプリが終了されるとバッチDLが終了してしまう

まず「1 複数アイテムのDLが使いにくい」 について説明します。

この機能、APIの設計やサンプルを見るに、大きな1つのファイルをDLするためにデザインされている機能で、複数アイテムを一括でDLすることは(あまり)考えられていないのかな?という気がします(APIが用意されてはいるが、事実上動作として意味が無い)。

BackgroundTransfer が想定しているフローは、

  1. OSにDLアイテムを1個登録
  2. OSがDLを終え、アプリに通知
  3. アプリは次のDLアイテムをOSに登録
であるようです。
ですがこれはバッチダウンローダとしてはNGです。アプリが中断している間に2. に至った場合、次のアイテムを登録する事ができず、バッチプロセスが中断してしまいます。

このため、WiFiSD8 Release 1 では、(多分)想定外の使い方をしていました。

  1. OSにDLアイテムを一気に全部登録
  2. OSはひたすらDL

(想定外と言うのは…本来アイテム登録は await RunAsyncで行い、DLが終了したらawaitから返ってくる、のですが…Release1ではこのCallをasync voidのMethodに押し込み、Callしたら後は知らんぷりでぶん投げるという。酷い。でも、これでもDLの進捗・終了はCallbackで取得可能です。)

この場合、アプリが中断されてもOSは次から次へとDLを繰り返します。
ですがSideEffectがありまして…1の全部登録に結構時間が掛かります。100アイテム登録に一分くらい掛かってしまう。AsyncAwaitのお蔭でアプリのUIが固まる事こそ無いものの、1分はかなりの数のDLが済ませられる時間であり、どうしたものか…という所でした。
 
ですが(この文章ですがだらけですが!)、この想定外の使用をもってしても、
「2 アプリが終了されるとバッチDLが終了してしまう」
これは克服できません。
 正確には、1アイテムのDLはアプリが終了しても継続します。
が、次のアイテムのDLが始まらず、バッチ処理が止まってしまうのです。

ここまでの話をまとめると、

・BackgroundTransfer を使う事で、(開始に時間は掛かるが)アプリ中断でも終了しない一括ダウンロードを実現可能。ただしアプリ終了に対しては無力。

となります。WiFiSD8 Release 1はこの方法を取っていました。
アプリ中断に対しては大変有効な方法なのですが、困るのは…アプリが中断されるか終了されるか、ユーザーから見ると全く予測ができない所です(開発者でも無理ですが)。
そして、アプリ終了をユーザーが防ぐ唯一の方法は、

「アプリを背面に持っていかず、前面に表示し続ける」

これだけです。
・・・あれ?と。ここまで読んで頂いた忍耐深くまた賢明な貴方様なら、議論が循環している事にお気付きかと思います。

「前面に表示し続けるのがアリなのなら、そもそもBackgroundTransfer 使わなくても良くね?HttpClient 使っても同じじゃん!!」

という。
そういう矛盾をWiFiSD8 Release 1 は孕んでいたのでした。
矛盾はありますが、それでも機能を実装することで中断に対応する事が出来、損はしていないはずでした。

ですが、この状況は全く別の要因で大きく変わり、結果的にはBackgroundTransferを使わないRelease 2を作る事になるのでした。
長すぎなので章を分けます。
待て次号。

※ 今回、Background Taskについては15分にCPUTime 2秒ではDLには使えないだろうということで上の考察には入れていません。




2 件のコメント: