wp7开发系列:性能优化实例分析

前言
功能开发完善的应用程序,如果性能和体验太差,只能算是一款糟糕的软件。只有流畅的体验和一流的性能才能确保一款优秀的App能够走得更远。最理想的情况是在开发过程中就能够保证App的高质量,一般情况下还是需要后期做有针对性的优化的。
抛开我们无法改变的如网络速度,硬件环境,系统SDK等因素,我们可以优化的地方实在太多,而且前者往往并不是性能瓶颈,绝大部分导致性能糟糕的原因都是我们自己的开发方式造成的,都是可以解决的或者绕开的,本文并没有系统性的讲解的性能优化的一些方法,只是以实例的形式介绍了典型了几种性能优化的方法。
一、Cases
动画启动延迟/阻塞优化
场景切换速度优化
界面流畅度优化
UI阻塞优化
二、动画启动延迟/阻塞优化
2.1、LongListSelector性能问题
在使用toolkit里面的LongListSelector控件时,我们发现当打开和关闭索引界面时,其中的延迟令人抓狂,至少能感觉出来整个界面停顿了0.5s以上。如果我们再给27个索引字母加上翻转的动画(类似系统首页的动画效果),启动时甚至连动画都看不到,这种体验实在太差了。(可以体验下Lumia800手机上的微信和系统索引的速度对比)
  

图2-1-1 LongListSelector控件                   图2-1-2 索引界面


2.2、优化方法
为什么和系统首页使用的控件性能差别那么大呢,这只有分析源码了,幸运的是toolkit的源码可以自由修改。
分析源码,首先找到点击List触发的OnSelectionChanged事件,当确认点击的是索引字母时,会调用DisplayGroupView方法,如果设置了GroupItemTemplate,就会调用OpenPopup方法,可以看到OpenPopup里面的实现如下。

Code
private void OpenPopup()
        {
            SaveSystemState(false, false);
            BuildPopup();
            AttachToPageEvents();
            _groupSelectorPopup.IsOpen = true;

            // This has to happen eventually anyway, and this forces the ItemsControl 
            //to  expand it's template, populate it's items etc.
            UpdateLayout();
        }

SaveSystemState用于保存系统的一些状态以便还原(AppBar的Visible属性),因为打开索引界面时会隐藏掉Appbar。
AttachToPageEvents用于监听Page的事件,BackKeyPressed和OrientationChanged。
关键就是BuildPopup方法,在这个方法里创建了整个的索引界面,不得不说这是一件超级耗时的事情,导致界面的停顿0.5s左右就一点也不奇怪了。与之对应的就是ClosePopup方法,这里面对所有对象都进行了析构,所以每次打开和关闭索引界面都是动态创建的,速度当然是超级慢了。优化方法很简单,将BuildPopup放在构造方法里,也就是预加载的方式,打开和关闭索引界面只需要将Popup打开和关闭即可,完全省略掉构造的时间,这样可以确保流畅的动画效果和无停滞的UI。
至此我们已经提升了至少80%的性能,但是仍然无法像系统首页那样完美的流畅,剩下的20%可能的原因是什么呢?请先看下一章。
三、场景切换速度优化
3.1、详情/大图切换性能问题
在开发比较复杂的单个页面时,经常需要在多种状态之间切换,这时就可能遇到切换速度较慢的性能问题,一般有2种情况。
1、类似于上一节所遇到的问题,新界面是动态创建的。
2、预加载的,一般使用VisualStateGroup管理,新界面需要显示时设置Visibility属性为Visible,不需要显示时设置为Collapsed。
详情页打开和关闭大图模式就属于第二种情况。
  


图3-1-1 宝贝详情                                  图3-1-2 大图模式(同一个Page)
3.2、优化方法
这里我们提供了第二种情况的优化方法,以详情页面打开和隐藏大图模式为例。

Code
<Grid x:Name="LayoutRoot" >
        <tbcontrols:TBPivot 
            Visibility="{Binding IsShowLargeImage,Converter={StaticResource falseToVisibilityConverter}}">
  <local:LargeImageModeBrowseControl
            Visibility="{Binding IsShowLargeImage,Converter={StaticResource trueToVisibilityConverter}}">
        </local:LargeImageModeBrowseControl>
 </Grid>

详情页的布局大致是这样的,第一层内容是一个Pivot,大图模式在第二层,当IsShowLargeimage为ture时显示大图模式。问题是,当在2种状态之间来回切换时,中间有一个很明显的UI阻塞现象,约有0.3s。时间消耗主要在Recreate上,因为当元素的Visibility属性设置为Collapsed时将会被释放掉,重新设置为Visible则会重新创建和布局。所以我们考虑用缓存的方法消除这部分时间。

Code
<Grid x:Name="LayoutRoot">
        <tbcontrols:TBPivot 
            Opacity="{Binding IsShowLargeImage,Converter={StaticResource    falseToOpacityConverter}}"
            IsHitTestVisible="{Binding    IsShowLargeImage},Converter={StaticResource tbNegativeConverter}">

    <local:LargeImageModeBrowseControl
            IsHitTestVisible="{Binding IsShowLargeImage}"
            CacheMode="BitmapCache"
            Opacity="{Binding IsShowLargeImage,Converter={StaticResource    trueToOpacityConverter}}">
        </local:LargeImageModeBrowseControl>

修改后的布局如上所示,当需要显示大图模式时,将Pivot透明度设为0(同时禁止用户输入事件),并将大图模式控件透明度设置为1,反之类似。由于大图模式控件使用了BitmapCache,完全没有构造和布局的时间消耗,呈现速度就如同展现Bitmap图像一样迅速。
上一章提到的剩余20%性能优化即与此有关,Popup的打开和关闭应该是和设置Visibility属性类似,如果对LonglistSelector做进一步优化相信能够达到系统级别的流畅度。
四、界面流畅度优化
3.1、Panorama左右滑动性能问题
这一类的优化的原因很大一部分就是靠感觉了,尽管动画有较高的帧率,尽管使用了各种缓存,使用了GPU动画,然而界面左右滑动切换就是有一种不流畅的感觉,总会“钝”那么一下。
在首页使用的panorama控件中,其中有2页内容是以列表的形式展示的,此外其他页面还有一些按钮。问题是当我们在左右滑动时,当手指刚刚触碰到按钮或者列表项,就会触发Tilt动画(使用toolkit的TiltEffect),这会导致整个大内容区块BitmapCache缓存的更新,滑动切换就没有那么流畅了,总会感觉“钝”了一下,其实就是gpu超负荷跳帧了。
  

   图3-1-1 Panorama其中=中一页 图3-1-2 Panorama的另一页

3.2、优化方法
优化的方法也比较简单,当检测到滑动手势时就不要触发Tilt动画,但是滑动手势的检测需要一小段时间间隔,而且sdk也没用提供任何相关接口和事件,我们得自己实现了。首先得实现我们自己的点击效果,提供2个方法beginTouchDownAnimation和beginTouchUpAnimation,分别是按下和恢复的动画效果。正常情况下在ManipulationStarted事件发生时会执行动画,但是我们加了一个60ms的延时,如果在30ms之内发现有ManipulationDelta事件发生(滑动)或者ManipulationCompleted事件发生(手指离开屏幕),则取消执行动画。

Code
static void effectElement_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
        {
            FrameworkElement element = sender as FrameworkElement;
            e.ManipulationContainer = element;
            Point origin = e.ManipulationOrigin;
            element.CaptureMouse();

            isSingleTap = true;
            isTapEnded = false;
            if (myAction != null)
            {
                TBDelayExecuter.CancelPerform(myAction);
            }

            myAction = new Action(() =>
            {
                if (isSingleTap && !isTapEnded)
                {
                    beginTouchDownAnimation(element, origin);
                }
            });
            TBDelayExecuter.Perform(myAction, 30);
        }

static void effectElement_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            isSingleTap = false;
        }

 static void effectElement_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            if (!isTapEnded)
            {
                FrameworkElement element = sender as FrameworkElement;
                element.ReleaseMouseCapture();
                beginTouchUpAnimation(element);
                isTapEnded = true;
            }
        }

这样在左右滑动屏幕时没有Tilt动画发生,变得流畅起来,在配合BitmapCache缓存,滑动内容复杂的panorama就相当于滑动内容仅为Bitmap的页面一样流畅。
五、UI阻塞优化
3.1、图片更新性能问题

图5-1-1 瀑布流图片墙

在一小段时间内,UI线程执行任务太多会导致UI阻塞现象,这时需要进行优化。比如首页的瀑布流分页加载时,每一页有16张图片需要下载,虽然我们使用了后台线程去下载图片,但是返回的情况是不确定的。最常见的情况,使用wifi时有可能16张图片在零点几秒的时间里面全部下载成功了,这是全部通知到UI,必然造成UI阻塞,表现为短时间里(也是零点几秒)几乎不响应拖动事件了。
3.2、优化方法
我们的采用的优化策略是,分散UI线程工作,控制图片下载后集中通知UI刷新的时机。
使用TBImage控件绑定一个图片的Url地址,后台实际使用我们的QueueDownloadImageProxy代理图片的下载。这个类的功能就是控制图片下载成功后依次延时通知到UI,保持一个最近一张图片通知到UI的时间,后续图片以100ms的间隔依次通知到UI。

Code
static Object lockObj= new Object();

static void QueueNotify(TBQueueDownloadImageProxy proxy, TBImage tbImage, object context)
        {
            lock (lockObj)
            {
                if (tbImage == null)
                    return;
                DateTime now = DateTime.Now;
                TimeSpan timeSpan;
                timeSpan = now.Subtract(PreNotifyImageTime);
                if (timeSpan.Milliseconds < minNotifyImageInterval)
                {
                    PreNotifyImageTime = now +   TimeSpan.FromMilliseconds(minNotifyImageInterval - timeSpan.Milliseconds);
     Thread.Sleep(minNotifyImageInterval - timeSpan.Milliseconds);
                }
                else
                {
                    PreNotifyImageTime = now;
                }
            }

            Deployment.Current.Dispatcher.BeginInvoke(delegate()
            {
                if (proxy.DownloadImageSuccessed != null)
                {
                    proxy.DownloadImageSuccessed(proxy, new TBQueueDownloadImageProxyEventArgs(tbImage.GetImage(), context));
                }
            });
        }

使用此方法可以使图片加载以随机并且依次的顺序显示出来,不影响浏览体验,再配合一个图片淡出的动画效果就很cool了。

Xaml
<tbcontrols:TBImage 
           Width="130"
           Background="{StaticResource imageBackgroundColor}"
             Stretch="Fill"
             CacheType="PERSIST_FILE"
             ImageUrl="{Binding ImageUrl}">
                <i:Interaction.Behaviors>              <tbBehavior:TBImageFadeInOnImageDownloadedBehavior
   DurationInMillisecond="{StaticResource imageFadeInDurationInMillisecond}"/>
                    </i:Interaction.Behaviors>
     </tbcontrols:TBImage>

六、优化策略总结
不难发现,我们采用性能优化策略有如下几种,分别是前面每一节用到的:
1、预加载
2、使用缓存
3、避免缓存的更新,避免重绘的发生
4、分散UI线程工作
七、结束语

在wp7手机优秀而强大的系统硬件配置下,只要在开发过程中,适当注意和使用合理的策略进行开发或者优化,保证流畅顺爽的客户端应用程序体验是不是很简单轻松?


2012.6

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值