zoukankan      html  css  js  c++  java
  • Windows Phone 性能优化(二)

    这篇文章的 demo 是在 (一)的基础上进行的调整,逻辑基本相似。本文只列和 上一篇出不同的代码

    为了实现自定义的虚拟化,把上一篇文章的 ListBox 换成 ScrollViewer + ItemsControl,这样组合在实际的项目

    中又是还是会用到的,比如,如果我们需要对 ScrollViewer 进行很多的控制,比如获取它的“滑动”事件,ScrollViewer

    中在放置其它控件,或者直接定制它的样式等等(当然可以通过 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+MB

    如果在 512MB 的模拟器上,还没加载数据完成,应用就崩溃了:

    优化算法

    下面 demo 的原理很简单,就是当列表中的项,在屏幕内的时候,把它的 Visibility 设置为 Visibility.Visible,

    当在屏幕外面的时候,设置为 Visibility.Collapsed; 逻辑很简单,但是对内存的占用明显下降。但是,为了用户

    体验,也就是如果当用户滑动列表到屏幕的地方,它的项目没有及时的显示,在用户的角度看,是会非常沮丧的,所以

    需要一个算法检查当前列表中的项是否在屏幕内。

    思路:

    关于这个 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 下载

  • 相关阅读:
    第十二章学习笔记
    UVa OJ 107 The Cat in the Hat (戴帽子的猫)
    UVa OJ 123 Searching Quickly (快速查找)
    UVa OJ 119 Greedy Gift Givers (贪婪的送礼者)
    UVa OJ 113 Power of Cryptography (密文的乘方)
    UVa OJ 112 Tree Summing (树的求和)
    UVa OJ 641 Do the Untwist (解密工作)
    UVa OJ 105 The Skyline Problem (地平线问题)
    UVa OJ 100 The 3n + 1 problem (3n + 1问题)
    UVa OJ 121 Pipe Fitters (装管子)
  • 原文地址:https://www.cnblogs.com/hebeiDGL/p/3410575.html
Copyright © 2011-2022 走看看