1. 程式人生 > >WPF的Image控制元件BitmapImage以及Uri的資源佔用問題

WPF的Image控制元件BitmapImage以及Uri的資源佔用問題

今兒沒幹啥活兒,花了一天時間把這個問題研究了一下。通過BitmapImage的Clone方法,保持使用Uri,同樣可以解決問題。
    場景:
  1. WPF下用Image控制元件展示圖片;
  2. 控制元件的圖片源自然選用BitmapImage; 
  3. BitmapImage通過Uri物件指向磁碟的某個檔案。
  • 正常: Image -<- BitmapImage -<- Uri -<- PNG Image File(@disk)
  • 問題: PNG Image File(@disk) 將不能被其他操作使用,Uri無法釋放資源。

  • 方法1:Image -<- BitmapImage -<- FileStream -<- PNG Image File(@disk)
  • 該方法使用FileStream代替Uri,FileStream是一個實現了IDisposable的檔案處理流,使用後我們可以用它的Close方法或者用using程式碼塊來釋放它。
  • 問題:過早釋放資源將使Image控制元件中的圖片不可見,必須維持一個FileStream的引用,以便隨時釋放。

  • 方法2: Image -<- BitmapImage -<- byte[] -< BinaryReader <-PNG Image File(@disk)
  • 該方法通過BinaryReader將圖片檔案轉化成byte陣列存在記憶體中,這樣我們就可以放棄檔案,因為圖片資料已然進記憶體了。通過byte陣列生成的BitmapImag已跟檔案無關,故原始檔可以在別的操作中繼續使用。
  • 這種方法可以說是一個比較好的解決方案。
  • 方法3:(BitmapImage)bmp.CacheOption = BitmapCacheOption.OnLoad
  • 該方法指定了BitmapImage中圖片的快取方式,不過我感覺OnLoad同樣會有問題。

    行了,說準備下班吧,事來了,除錯的時候發現,該死的其他控制元件也會通過這個Image控制元件拿到圖片,並藉助其Source屬性拿到圖片的來源(比如Uri,位元組陣列),並序列化出來到資料庫以儲存,這是個繪圖板,可以把Image拖進來的那種。
    可想而知,Uri中包含的圖片檔案可以快速使圖片再次被載入,位元組陣列直接報錯,轉換異常。起初百思不得其解,後來看了資料庫,又看了繪圖板的控制元件庫,才發現,看來必須用Uri解決問題,開始思考最初的方法哪裡出了問題。

    我們把最初的方法分開寫,
    Uri uri = new Uri(檔案路徑,列舉.相對還是絕對路徑);
    BitmapImage bmp = new BitmapImage(uri);
    tempImage.Source = bmp; // 控制元件

    下面依次註釋這些語句,再寫個方法去訪問圖片檔案,看看是誰佔用了資源,最終發現其實是控制元件沒有釋放掉資源,Uri只是作為定位物件指引了一個路徑,BitmapImage則做了一層封裝。
好吧,.net的GC是不靠譜的,不要妄圖通過tempImage.Source = null或者System.GC.SuppressFinalize(tempImage)釋放資源,尤其是急的時候。

    想想為什麼,因為Image的源直接維持了對包裝有圖片檔案的BitmapImage的引用,不過很慶幸,BitmapImage提供了Clone方法,可以完成對其的深拷貝,立馬試試:
tempImage.Source = bmp.Clone();

    OK,成功了,圖片顯示正常,其他操作也不報錯,這給了我們新的選擇,因為用Uri引入的圖片可以再其他適當的時候通過Image圖片的Source屬性,再次拿到他的Uri String,然後不難再還原為Uri物件,進一步對檔案進行操作,十分方便。

  • 方法: Image -< BitmapImage.Clone <- BitmapImage -<- Uri -<- PNG Image File(@disk)
  • 優勢:不但解決了資源佔用問題,而且在Image控制元件中留下了檔案的“痕跡”(其實是BitmapImage留下的),以便進一步操作檔案。
  • 驗證:我們選擇一個圖片檔案,然後讓程式碼以該方法指向它,執行程式,待程式顯示圖片後,嘗試刪除這個圖片檔案,成功;若不加Clone方法,則會提示資源已被佔用,說明該方法有效。
  • 注意:這個方法同樣會有一定的時間延遲,因為要從Image.Source與BitmapImage.Clone的“繫結”到本身BitmapImage源物件被GC回收,釋放對檔案的佔用,是需要時間的,不想要等的話方法2位元組陣列很好用的。 
  • 其他:還有更狠的呢,由於我們不能過度(或者說是自由)干涉.net的垃圾收集機制及其垃圾收集器GC,這個BitmapImage什麼時候會被放掉,也不由我們決定,這是一件很頭大的事情,因為放早了同樣會導致Image控制元件裡的圖片不顯示,為啥...你Source指向的記憶體區域都不是bmp了,.net說不能用,必須不顯示啊...妥協方法我在第二天補充了http://blog.sina.com.cn/s/blog_3f2c72ea0100ss69.html

這樣Image的解決方案就比較完整了,我也在關注其他更完美的方法。


    其實我很高興能在實習中實踐自己所學的知識,並加以細化,畢竟.net這東西我接觸也不到一年,用起來也都是小打小鬧,但是這次通過實習新學習WPF真是長進不少。