Windows Phone 性能優化(二)

 

這篇文章的 demo 是在 (一)的基礎上進行的調整,邏輯基本類似。本文只列和 上一篇出不一樣的代碼html

 

爲了實現自定義的虛擬化,把上一篇文章的 ListBox 換成 ScrollViewer + ItemsControl,這樣組合在實際的項目算法

中又是仍是會用到的,好比,若是咱們須要對 ScrollViewer 進行不少的控制,好比獲取它的「滑動」事件,ScrollViewerapp

中在放置其它控件,或者直接定製它的樣式等等(固然能夠經過 VisualTreeHelper 也能夠獲取 ListBox 中的 ScrollViewer)。性能

 

ListBox (繼承自 ItemsControl)內部的實現就是封裝了 ScrollViewer + ItemsControl 控件,在本 demo 中,使用的組合爲:優化

            <ScrollViewer x:Name="scrollViewer" Loaded="ScrollViewer_Loaded">
                <ItemsControl x:Name="listbox" ItemsSource="{Binding}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <!---雖然設置爲「虛擬面板」,可是它是不起虛擬做用的-->
                            <VirtualizingStackPanel/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal" Margin="10,30,0,0">
                                <Image VerticalAlignment="Top" Source="{Binding Photo}" Width="150"/>
                                <TextBlock Text="{Binding Title}" Width="250" Foreground="Wheat"
FontSize="25" Margin="10,0,0,0" TextWrapping="Wrap"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer>


在上一篇 demo 的基礎上,當加載 200條數據時,在 1G 的模擬器上運行時,內存佔用竟達到 200+MBspa

若是在 512MB 的模擬器上,還沒加載數據完成,應用就崩潰了:線程

 

優化算法翻譯

下面 demo 的原理很簡單,就是當列表中的項,在屏幕內的時候,把它的 Visibility 設置爲 Visibility.Visible,3d

當在屏幕外面的時候,設置爲 Visibility.Collapsed; 邏輯很簡單,可是對內存的佔用明顯降低。可是,爲了用戶code

體驗,也就是若是當用戶滑動列表到屏幕的地方,它的項目沒有及時的顯示,在用戶的角度看,是會很是沮喪的,因此

須要一個算法檢查當前列表中的項是否在屏幕內。

思路:

 

 

 

關於這個 demo 其它部分的代碼請參考上一篇文章。

1)首先在 xaml 頁面放一個按鈕,如上圖所示,當應用加載完成時,默認不錯任何處理,當點擊 「虛擬化」 按鈕時,

觸發自定義虛擬化方法,頁面中的 xaml:

<Button Content="虛擬化" HorizontalAlignment="Left" Margin="335,0,0,0"
VerticalAlignment="Top" Width="133" Height="72" Tap="Button_Tap"/>


相應的 C#:

        //當用戶單擊 按鈕時,開啓模擬虛擬化
        private void Button_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            e.Handled = true;

            Visualizition();
        }


2)當點就按鈕後,首先獲取列表中,全部由 DataTemplate 中的 StackPanel 複製的每一項。由於 ListBox 繼承自 ItemsControl,

而且它們 ItemContainerGenerator 屬性的 ContainerFromIndex(int index) 方法能夠獲取列表中的指定的 Item,而後在經過

VisualTreeHelper 的靜態方法,獲取模版產生的 StackPanel。所有的代碼:

        void Visualizition()
        {
            // 自定義一個「字典」,用來保存每項的 Y座標 和它「自己」的引用
            Dictionary<double, StackPanel> dic = new Dictionary<double, StackPanel>();

            double height_sum = 0;
            for (int i = 0; i < listbox.Items.Count; i++)
            {
                // 由於 ListBox 是經過數據綁定生成的列表,因此須要經過 ItemContainerGenerator 得到
                // 每一項的 StackPanel 這個父控件
                FrameworkElement fe = listbox.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
                StackPanel sp = FindFirstElementInVisualTree<StackPanel>(fe);
                
                dic.Add(height_sum, sp);

                // 累加 Y高
// 30 爲模版中父容器的 margin-top
height_sum = height_sum + sp.ActualHeight + 30; // 設置它的高度爲本身的實際高度
// 很重要,若是不爲父容器指定固定高度,當子元素隱藏後,父容器高度變爲0px
sp.Height = sp.ActualHeight; } // 每0.5秒鐘,循環檢查一次列表中,哪些項在屏幕內,若是在屏幕內,則顯示,若是 // 在屏幕外,則隱藏 Observable.Interval(TimeSpan.FromSeconds(.5)).ObserveOnDispatcher().Subscribe((_) => { foreach (var keyValue in dic) { if (((scrollViewer.VerticalOffset - 500) > keyValue.Key || keyValue.Key > (scrollViewer.VerticalOffset + 900))) { keyValue.Value.Children[0].Visibility = System.Windows.Visibility.Collapsed; keyValue.Value.Children[1].Visibility = System.Windows.Visibility.Collapsed; } else { keyValue.Value.Children[0].Visibility = System.Windows.Visibility.Visible; keyValue.Value.Children[1].Visibility = System.Windows.Visibility.Visible; } } }); } // 查找「視圖樹」中的控件 private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject { var count = VisualTreeHelper.GetChildrenCount(parentElement); if (count == 0) return null; for (int i = 0; i < count; i++) { var child = VisualTreeHelper.GetChild(parentElement, i); if (child != null && child is T) { return (T)child; } else { var result = FindFirstElementInVisualTree<T>(child); if (result != null) return result; } } return null; }


當加載 200 條新聞的時候,運行工程效果:

  

上面算法是每 0.5秒 遍歷一下 Dictionary 的 keys,爲了直觀就沒有再優化。好比每次遍歷的時間,

屏幕的可視區域等。

 

默認運行時,內存佔用 208MB 效果:

 

單擊按鈕後,當上下滑動的時候,能夠看到延遲顯示的 item,內存佔用減小了很多:

 

 

另外,我想到可使用 快速排序 的算法方法,能夠更快找到新滑動到屏幕裏的 Item,以前在屏幕外的 Item

若是還在屏幕外,則跳過,等等。關於如何優化上面算法這裏就不在多講了。由於項目只是在介紹減小內存的

思路,因此沒有考慮在應用中如何在「加載更多..」時,如何再次添加新 item 等等實際交互。

 

還有就是關於 Reactive Extension 相關類庫(已經集成在了 WP8 的sdk 中)的使用,這裏也不過多介紹,它

確實是一個神奇的東西,園子裏有朋友寫過相關的文章,我前段時間也翻譯了一下(譯文連接),稍後會整理

更多關於 Rx 的文章。這裏使用 Observable  做爲計時器,固然也能夠自定義 Timer ,不過感受 Observable 用起來

更加方便。

 

上面代碼中:Observable.Interval(TimeSpan.FromSeconds(.5)).ObserveOnDispatcher().Subscribe((_) => { //省略  });

的含義是,每隔 0.5秒鐘,在 UI 線程中 調用一次 Subscribe 註冊的方法。

 

 

引伸

經過這個 demo,開發者應該知道了,在頁面中,儘可能少的繪製元素,對於 Windows Phone 應用程序性能的提高,對於內存佔用

的優化,有多麼的明顯。例如,儘可能減小 UI 控件的嵌套;在 Pivot (或者 Panorama )頁面控件中的項,若是 PivotItem 不在

當前屏幕中,則把它的 Child 設爲隱藏,當用戶切換到該 PivotItem 頁面時,在給它顯示出來。等等。

 

 

本文工程 demo 下載

相關文章
相關標籤/搜索