ページ遷移を考慮した、時間がかかるデータダウンロード処理 – Windows Phone 7+Twitterを例に


Windows Phone 7では下に並んだ3つのボタンでいつでもページ遷移ができます。Twitterなどネットからデータをダウンロードするのに時間がかかる場合、当然、ダウンロード処理中にページ遷移が起こり得ます。TwitterのREST APIでデータをロードする場合、WebClientというクラスを使います。例えば、あるユーザーアカウントのフォロワーのリストを取得する場合には、

            var friendsUri = new Uri(“http://api.twitter.com/1/statuses/followers/” + targetUsername + “.xml” );
            var twitter = new WebClient();
            twitter.DownloadStringCompleted +=
                    new DownloadStringCompletedEventHandler(twitter_FollowersDownloadCompleted);
            twitter.DownloadStringAsync(friendsUri);

というコードでWebClientクラスのDownloadStringCompletedイベントにハンドラを登録し、DownloadStringAsync()メソッドでデータロードを開始して、データロードが終わったらtwitter_FollowersDownloadComplatedメソッドがコールされるというように、非同期APIを使います。ちなみにハンドラ側では、

        void twitter_FollowersDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            XElement message = XElement.Parse(e.Result);
            var query = from m in message.Descendants(“user”)
                        select new TwitterUser
                        {
                            Id = m.Element(“id”).Value,
                            UserName = m.Element(“name”).Value,
                            ScreenName = m.Element(“screen_name”).Value,
                            Description = m.Element(“description”).Value,
                            ImageProfile = new Uri(m.Element(“profile_image_url”).Value)
                        };
            foreach (var tu in query)
            {

こんな感じで書いてやると、送られてきたXMLファイルの内容からフォローしているユーザーの情報を、予めアプリで定義しておいた、TwitterUserクラスのオブジェクトを作成し、foreachループで個々のフォローワーデータを格納したリストを作ることが出来ます。
DownloadStringAsync()メソッドをコールしてから、ハンドラがコールされる間は、アプリで表示しているGUIも操作可能だし、別のロジックの処理も可能だし、勿論ハードキーでライブタイルのトップページに戻ることも出来るし、一つ前のページに戻ることも出来ます。

ページ遷移が起きない限りでは、ダウンロードが終わってこのハンドラがコールされてイベント引数eのResultプロパティにTwitterからのXMLデータが格納されて取り出し可能なのですが、ページが変わって、アプリケーションがバックグラウンドになると、e.Resultにアクセスした段階でExceptionが発生してしまいます。このままだと、アプリケーションの動作的にもNGだし、Marketplaceでの審査も通りません。上に紹介したコードは、例えばフライトモードの時のようにネットワークに端末が繋がっていない場合にも、同じようにExceptionが発生するので、イベントハンドラに登録したメソッドの引数、eのResultプロパティにアクセスする処理を、try { } catch (Exception ex) { MessageBox.show(ex.Message); } などと、try~catchで囲む必要があります。

※ ネットワークアクセスは大抵何らかのExceptionを発生する可能性があるので、サンプルコードなどでtry catchがなくても、必ず、try catchで囲もうね~

最低限、ページ遷移によるダウンロードの中断は、以上の処理で異常処理終了は防げます。ネットワークからのデータダウンロードは、ページ遷移による失敗よりもむしろ、ネットワークが繋がっていないとか、サーバーが何らかの事情で動いていないといった事態が多いはずなので、またこういう状況の場合直ぐに再チャレンジしても失敗することも多いはずなので、Exceptionをcatchして正常処理(アプリ的には異常事態を正常に捕獲したので正常)した後のUIの待ち状態では、ユーザーが再度好きな時点で、再ロードの開始が出来るような指示を行えるボタンなどのUIを用意しておくとよいでしょう。

次に、データが全て受信できた状態を想像してください。当然受信したデータを下にした処理結果が何らかの形でページ上に表示されているはずです。一旦アプリを閉じて、ライブタイルからアプリを起動すると、単にメモリ上にダウンロードして解釈したデータを保持しているだけではアプリが終了した時点でメモリ上のデータが消えてしまいます。Twitterのフォロワー情報の場合、よっぽどの人気ユーザーアカウントでない限り、あっという間にフォロワーが劇的に増減することはまれです。一度ダウンロードしたデータは暫く変わることがないようなケースでは、毎度毎度データをネットからダウンロードを行うのは非効率なので、アプリケーションが終了状態になってもデータを保持できる領域に記憶しておいて、それを使いまわし、ユーザーがデータを更新したい時にだけ新しくデータをネットワークからダウンロードしてくるというつくりの方が、使い勝手がよいでしょう。サーバー上のデータは変わっていなくても、何らかの原因でダウンロードが失敗する恐れもありますよね?

そんな時には、IsolatedStorageに受信したデータを保存しておくのが一番手軽で確実な方法だと思います。ハンドラの引数eのResultプロパティにXML形式のテキストが入っているので、ハンドラの中で、

            string fileName = “followers” + targetUsername + “.xml”;
            var storage = IsolatedStorageFile.GetUserStoreForApplication();
            using (var writer = new StreamWriter(storage.CreateFile(fileName)))
            {
                writer.Write(e.Result);
            }

というコードでアプリ用のローカルストレージに受信データがファイルに保存されます。
そして、WebClientを使ってダウンロードを開始するコードを、

            string fileName = “followers” + targetUsername + “.xml”;
            var storage = IsolatedStorageFile.GetUserStoreForApplication();
            if (!storage.FileExists(fileName))
            {
                var friendsUri = new Uri(“http://api.twitter.com/1/statuses/followers/” + targetUsername + “.xml” );
                var twitter = new WebClient();
                twitter.DownloadStringCompleted +=
                         new DownloadStringCompletedEventHandler(twitter_FollowersDownloadCompleted);
                twitter.DownloadStringAsync(friendsUri);
            }
            else
            {
                using (var stream = new System.IO.StreamReader(
                              storage.OpenFile(friendsFilePrefix + fileCursor + fileExt, System.IO.FileMode.Open)))
                {
                    string eResult = stream.ReadToEnd();
                    XElement element = XElement.Parse(eResult);
                    ・・・ ネットワークからダウンロードした時の処理と同じ

 こんな風に書き換えてやれば、ファイルが無いときだけダウンロードを試みる処理になるわけです。

そして、ユーザーの指示で再ロードを開始する処理は、

            string fileName = “followers” + targetUsername + “.xml”;
            var storage = IsolatedStorageFile.GetUserStoreForApplication();
            if (!storage.FileExists(fileName))
            {
                 storage.DeleteFile(fileName);
            }

このコードを実行して、一つ前のコードを実行してやればファイルがなくなっているので、同じコードでダウンロード開始が可能です。

さて、実際にTwitterのREST APIを使って実行してみると、フォロワー数が多いとかなり時間がかかるし、Twitter APIの仕様上、全てのデータ情報を漏れなく取得することをの保障されていないので、実用レベルでは動かない場合が多いです。
Twitterで全てのフォロワー情報を確実に取得するには、”カーソル”という概念を利用して一定数を複数に分けて逐次ダウンロードするという処理が必要になります。この詳細は次のブログに続く。

あ、忘れた。紹介しているコードは色々な名前空間のクラスを使っているので、

using System.IO;
using System.IO.IsoratedStorage;
using System.Net;
using System.Xml;

を宣言しておいてね。

Comments (0)

Skip to main content