Windows Phone実用開発テクニック(28):画像キャッシュ


以前の記事では、Windows Phoneでの画像処理に関する知識をいくつか紹介しましたが、Windows Phoneでの画像編集、Windows Phoneでの画像処理のテクニック、Windows PhoneでのGIF画像表示、保存画像、ロード画像など、Windows Phoneの開発において画像処理が大きな割合を占めていることがわかります.今日は簡単な画像キャッシュメカニズムを紹介します.
David Ansonは、UIスレッドに影響を与えることなく画像をダウンロードするためのLowProfileImageLoaderを発表した(Mangoでは画像処理をUIスレッドから抽出処理しているので、この影響はないので、この記事を参照してください).
LowProfileImageLoaderの考え方は、画像のUriを1つのキューに入れ、このキューを順に巡って画像リソースを要求し、ダウンロードした後にUIにストリームを返すように通知し、新しいUIが挿入されるたびに作業スレッドを起動し、作業スレッドを1回に複数のUriを同時に処理できるように設定することで、デフォルトは5つです.
LowProfileImageLoaderの考え方を理解した後、私たちはLowProfileImageLoaderに基づいて簡単な画像キャッシュをカスタマイズしました.つまり、もし私たちがこの画像をダウンロードしたことがあれば、私たちは画像をローカルに保存して、次回プログラムを起動する時、ローカルがこの画像をキャッシュしたことがあるかどうかを判断して、もしこの画像をキャッシュしたら、ローカルから画像を読み取って戻ります.画像がキャッシュされていない場合は、ダウンロード後にUIに通知し、画像をローカルに保存します.
スレッドが動作するマスターメソッドWorkerThreadProcの要求ネットワークの前に判断を追加し、そのピクチャがキャッシュされているか否かを判断する
if (pendingRequest.Uri.IsAbsoluteUri)
{
    //load from isolated storage if has been cached     if (IsImageCached(pendingRequest.Uri))
    {
        if (null!=LoadCachedImage(pendingRequest.Uri))
        {
            pendingCompletions.Enqueue(new PendingCompletion(pendingRequest.Image, pendingRequest.Uri, LoadCachedImage(pendingRequest.Uri)));
        }
    }
    else     {
        // Download from network         var webRequest = HttpWebRequest.CreateHttp(pendingRequest.Uri);
        webRequest.AllowReadStreamBuffering = true; // Don't want to block this thread or the UI thread on network access         webRequest.BeginGetResponse(HandleGetResponseResult, new ResponseState(webRequest, pendingRequest.Image, pendingRequest.Uri));
    }                        
}

キャッシュされている場合は、完了キューに直接プッシュします.つまり、UIにコールバックする準備ができています.
コールバックに処理を追加し、キャッシュされていない場合は、ピクチャをキャッシュする必要があります.
// Decode the image and set the source var pendingCompletion = pendingCompletions.Dequeue();
if (GetUriSource(pendingCompletion.Image) == pendingCompletion.Uri)
{
    //if has been cached,do not cache     if (!IsImageCached(pendingCompletion.Uri))
    {
        CacheImage(pendingCompletion.Stream, pendingCompletion.Uri);
    }
    try     {
        ImageSource bitmap;
        var bitmapImage = new BitmapImage();
        bitmapImage.SetSource(pendingCompletion.Stream);
        bitmap = bitmapImage;
        pendingCompletion.Image.Source = bitmap;
    }
    catch(Exception ex)
    {
        // Ignore image decode exceptions (ex: invalid image)     }
}

次の方法は、画像がキャッシュされているかどうかを判断することです.
private static bool IsImageCached(Uri u)
{
    string filePath = Path.Combine(Constants.CACHE_DIR_IMAGES, GetParsePath(u.ToString()));
    using (var store=IsolatedStorageFile.GetUserStoreForApplication())
    {
        if (store.FileExists(filePath))
        {
            return true;
        }
    }
    return false;
}

一般的な画像のuriはhttp://tなので、画像のuriを解析する問題に関連しています.jpgなど、不要なトラブルを避けるためにuriを相応のトランスコードする必要があります.
private static string GetParsePath(string url)
{
    return url.Replace("://", "").Replace("/","_");
}

次の方法は、キャッシュからピクチャストリームを読み出す方法です.
private static Stream LoadCachedImage(Uri u)
{
    string filePath = Path.Combine(Constants.CACHE_DIR_IMAGES, GetParsePath(u.ToString()));
    using (var store = IsolatedStorageFile.GetUserStoreForApplication())
    {
        if (!store.FileExists(filePath))
        {
            return null;
        }
        return store.OpenFile(filePath, FileMode.Open, FileAccess.Read);
    }
}

画像をキャッシュする方法:
private static bool CacheImage(Stream source,Uri u)
{
    string filePath = Path.Combine(Constants.CACHE_DIR_IMAGES, GetParsePath(u.ToString()));
    using (var store = IsolatedStorageFile.GetUserStoreForApplication())
    {
        try         {
            if (!store.DirectoryExists(Constants.CACHE_DIR_IMAGES))
            {
                store.CreateDirectory(Constants.CACHE_DIR_IMAGES);
            }
            using (var stream = store.OpenFile(filePath, FileMode.OpenOrCreate, FileAccess.Write))
            {
                byte[] bytes = new byte[source.Length];
                source.Read(bytes, 0, (int)source.Length);
                stream.Write(bytes, 0, (int)source.Length);
            }
            return true;
        }
        catch (Exception)
        {
            return false;
            throw;
        }
    }
}

呼び出し方法はこうです
<Image delay:LowProfileImageLoader.UriSource="{Binding logo}" Grid.Column="0" Height="100" Width="100" /> 

XAMLのイメージに上記のコードを追加すれば、画像がダウンロードされると、次回使用するためにローカルにキャッシュされます.
もちろん、依存プロパティを追加して、キャッシュを現在起動しているかどうかを判断することができます.もう一つ考えなければならないのは、いつピクチャキャッシュを削除するか、それはあなたのappによって決まります!
修正したLowProfileImageLoaderはここで見つけることができます.Hope that helps.