11.2.4 大數據量網絡圖片列表的異步加載和內存優化
虛擬化技術可以讓Windows Phone上的大數據量列表不必擔心會一次性加載所有的數據,保證了UI的流程性。對於虛擬化的技術,我們不僅僅只是依賴其來給列表加載數據,還可以利用虛擬化的特性去做更多的事情。虛擬化技術有一個很重要的特性就是,它可以准確地判斷出哪些列表項處於手機屏幕中,可以動態地去更新這些數據。基於這樣的特性,我們可以給列表的功能做更多的優化。
那么下面我們基於一個例子來講解利用虛擬化技術去做列表的性能優化。有這么一個需求,需要實現一個圖片的列表,圖片都是來自網絡的,然后數據集合也很大。做這個網絡圖片列表功能時會面臨着兩個問題,一個是圖片的加載會比較耗時,兩外一個是當不斷地滑動會讓數據集合加載的圖片占用的內存會越來越高。
對於第一個問題,可以采用異步加載的方式來解決,這樣列表加載完之后,圖片再顯示出來,列表首次加載的速度會很快。那么我們可以通過后台線程調用網絡請求下載圖片,下載完圖片之后再觸發UI線程把圖片顯示出來。
第二個問題是要解決內存的問題,那么可以使用弱引用類型(WeakReference類)來存儲圖片的數據。弱引用就是不保證不被垃圾回收器回收的對象,它擁有比較短暫的生命周期,在垃圾回收器掃描它所管轄的內存區域過程中,一旦發現了只具有弱引用的對象,就會回收它的內存,不過一般情況下,垃圾回收器的線程優先級很低,也就不會很快發現那些只有弱引用的對象。當內存的使用會影響到程序的流暢運行的時候,垃圾回收器,就會按照優先次序把存在時間長的弱引用對象回收,從而釋放內存。所以弱引用特別適合在當前這種情況下,占用大量內存,但通過垃圾回收功能回收以后很容易重新創建的圖片對象。圖片下載完之后會存放在弱引用對象里面,當檢查到數據被回收的時候,再進行異步加載,當然你也可以把圖片用獨立存儲存起來,這樣也就免去了再次請求網絡的操作。
下面我們來實現網絡圖片列表的異步加載和內存優化的示例:
代碼清單11-8:網絡圖片列表(源代碼:第11章\Examples_11_8)
(1)創建數據實體類Data類,在Data類里面封裝異步加載圖片和弱引用的邏輯。
Data.cs文件主要代碼 ------------------------------------------------------------------------------------------------------------------ // Data類從INotifyPropertyChanged派生,要實現綁定屬性改變的事件,用於圖片異步請求完成之后可以更新到UI上 public class Data: INotifyPropertyChanged { // 圖片名字屬性 public string Name { get; set; } // 當前的頁面對象,用於觸發UI線程 public Page Page { get; set; } // 圖片的網絡地址 private Uri imageUri; public Uri ImageUri { get { return imageUri; } set { if (imageUri == value) { return; } imageUri = value; bitmapImage = null; } } // 若引用對象,用於存儲下載好的圖片對象 WeakReference bitmapImage; // ImageSource屬性用於綁定到列表的Image控件上 public ImageSource ImageSource { get { if (bitmapImage != null) { // 如果弱引用沒有沒回收,則取弱引用的值 if (bitmapImage.IsAlive) return (ImageSource)bitmapImage.Target; else Debug.WriteLine("數據已經被回收"); } // 弱引用已經被回收那么則通過圖片網絡地址進行異步下載 if (imageUri != null) { Task.Factory.StartNew(() =>{ DownloadImage(imageUri);}); } return null; } } // 下載圖片的方法 void DownloadImage(object state) { HttpWebRequest request = WebRequest.CreateHttp(state as Uri); request.BeginGetResponse(DownloadImageComplete, request); } // 完成圖片下載的回調方法 async void DownloadImageComplete(IAsyncResult result) { HttpWebRequest request = result.AsyncState as HttpWebRequest; HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result); // 讀取網絡的數據 Stream stream = response.GetResponseStream(); int length = int.Parse(response.Headers["Content-Length"]); // 注意需要把數據流重新復制一份,否則會出現跨線程錯誤 // 網絡下載到的圖片數據流,屬於后台線程的對象,不能在UI上使用 Stream streamForUI = new MemoryStream(length); byte[] buffer = new byte[length]; int read=0; do { read = stream.Read(buffer, 0, length); streamForUI.Write(buffer, 0, read); } while (read == length); streamForUI.Seek(0, SeekOrigin.Begin); // 觸發UI線程處理位圖和UI更新 await Page.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { BitmapImage bm = new BitmapImage(); bm.SetSource(streamForUI.AsRandomAccessStream()); // 把圖片位圖對象存放到若引用對象里面 if (bitmapImage == null) bitmapImage = new WeakReference(bm); else bitmapImage.Target = bm; //觸發UI綁定屬性的改變 OnPropertyChanged("ImageSource"); } ); } // 屬性改變事件 async void OnPropertyChanged(string property) { var hander = PropertyChanged; if (hander != null) await Page.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { hander(this, new PropertyChangedEventArgs(property)); }); } public event PropertyChangedEventHandler PropertyChanged; }
(2)使用ListView控件綁定到數據Data對象的數據集合。
MainPage.xaml文件主要代碼 ------------------------------------------------------------------------------------------------------------------ <ListView x:Name="listView"> <ListView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Name}" Height="80"></TextBlock> <Image Source="{Binding ImageSource}" Width="200" Height="200"></Image> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView>
MainPage.xaml.cs文件主要代碼 ------------------------------------------------------------------------------------------------------------------ public MainPage() { InitializeComponent(); // 創建一個有1000個Data對象的數據集合 List<Data> Items = new List<Data>(); for (int i = 0; i < 1000; i++) { // 在網絡地址后面加上index=i是為了保證每個網絡地址的不一樣 // 這樣就不會產生網絡數據緩存,更加接近真實的網絡圖片列表 Items.Add(new Data { Name = "Test" + i, Page = this, ImageUri = new Uri("http://pic002.cnblogs.com/images/2012/152755/2012120917494440.png?index=" + i) }); } listView.ItemsSource=Items; }

本文來源於《深入理解Windows Phone 8.1 UI控件編程》
源代碼下載:http://vdisk.weibo.com/s/zt_pyrfNHoezI
歡迎關注我的微博@WP林政
WP8.1技術交流群:372552293
