一つ前の投稿で、時間のかかるデータダウンロード一発こっきりのケースを説明しましたが、その続きです。
Twitterでは、膨大な量の項目を”カーソル”を利用してダウンロードしていくAPIを持っています。
var friendsUri = new Uri("http://api.twitter.com/1/statuses/followers/" + targetUsername + ".xml?cursor=" + cursor); var twitter = new WebClient(); twitter.DownloadStringCompleted += new DownloadStringCompletedEventHandler(twitter_FollowersDownloadCompleted); twitter.DownloadStringAsync(friendsUri);
前回の投稿でも似たようなコードが出てきましたが、DownloadStringAsync()メソッドの引数のUriにカーソルの値を追加すると、最大100項目ずつダウンロードが可能です。cursorを”-1”と指定すると最初の100個が取得できます。そして、イベントハンドラのコードで、e.ResultをXElementでParseした結果に対し、
string cursor = message.Descendants("next_cursor").First().Value;
で、次に続く百項目を取り出す為のカーソルを取得できます。この値を使って逐次非同期ダウンロードを繰り返していくと、全ての項目がきちんと(Twitter APIは単位時間当たりの使用制限があるのでそれを超えなければの話ですが)ダウンロードできます。次に続く項目はない場合、カーソルは"0”になります。
更に、毎度ダウンロードが終わった時点で、各時点のカーソル値とひも付けてダウンロードした内容をファイル化しておけば、逐次ダウンロードの途中で、アプリが終了され、再開した時にも、保存したファイルを辿っていって、ファイル化されていないところからダウンロードを始めれば、処理に無駄がありません。まとめたコードを以下に紹介します。
string targetUsername; // 調査対象のユーザーアカウント名は事前に代入しておく
GetFollowersData(string cursor) // OnNavigatedToや、ボタン、タップのハンドラーで、cursorを""にしてコール { string fileName = "followers" + targetUsername + cursor + ".xml"; var storage = IsolatedStorageFile.GetUserStorageForApplication(); if (storage.FileExists(fileName)) { // ファイルが存在する場合は、ファイルからデータを組み立てる using (var stream = StreamReader(storage.OpenFile(fileName, FileMode.Open)) { string content = stream.ReadToEnd(); string next_cursor = ParseContent(content); if (next_cursor.CompareTo("0") != 0) { GetFollowersData(next_cursonr); } } } else { // ファイルが無い場合は、ダウンロードを試みる GetFollowersFromDownload(""); } }
string currentCursor; // ダウンロード処理は非同期なので現在のカーソル値を保持するメンバー変数が必要
void GetFollowersFromDownload(string cursor) // 非同期ダウンロードの開始用メソッド { currentCursor = cursor; if (string.IsNullOrWhiteSpace(cursor)) { currentCursor = "-1"; } var friendsUri = new Uri("http://api.twitter.com/1/statuses/followers/" + targetUsername + ".xml?cursor=" + currentCursor); var twitter = new WebClient(); twitter.DownloadStringCompleted += new DownloadStringCompletedEventHandler(twitter_FollowersDownloadCompleted); twitter.DownloadStringAsync(friendsUri); }
void twitter_FollowersDownloadCompleted(object sender, DownloadStringCompletedEventArgs e) { try { cursor = ParseContent(e.Result); var storage = IsolatedStorageFile.GetUserStoreForApplication(); using (var writer = new StreamWriter(storage.CreateFile("Followers" + targetUsername + currentCursor + ".xml"))) { writer.Write(e.Result); } if (cursor.CompareTo("0") != 0) { // まだ終わりじゃない。涙で交わした約束・・・もとい、まだ続くので新しいカーソルで非同期ダウンロードを実行 GetFollowersFromDownload(cursor); } } catch (Exception ex) { MessageBox.Show( "ダウンロードに失敗しました。 : " + ex.Message); } }
string ParseContent(string eResult) // ファイルの内容とダウンロードの内容は一緒なので解析コードは同一のものを使う { XElement message = XElement.Parse(eResult); var query = ... foreach (var item in query) { ... } return message.Descendants("next_cursor").First().Value; }
これで凡その骨格は出来上がり。保持データをクリアして、最新のデータを取得したい場合は、
var storage = IsolatedStorageFile.GetUserStoreForApplication(); foreach (var fileName in storage.GetFileNames("Followers" + targetUsername + "*" + ".xml")) { storage.DeleteFile(fileName); }
で、保持しているファイルを全て削除してから、GetFollowersData("")をコールしてあげるよろし。
こうしておけば、非同期ダウンロード待ちの間に、アプリケーションの終了や、ページの遷移が起こっても、ダウンロード出来ているデータは再利用し、未ダウンロードの時点から処理を再開することが可能です。
以上、紹介してきたことはとどのつまり、アプリケーションの状態保存を適切な時点で適宜行うことの一例だと思ってください。Metro Styleのアプリケーションはバックに回ると処理が中断されるスタイルなので、Windows Phoneだけでなく、いつ中断されても、再開時、適切なポイントで動き出せるよう、必要十分なデータを最小回数で保持することを心がけましょう。…なんだか、組込み機器のアプリと一緒な気がするのは気のせいか?…