wp7开发系列:性能优化实例分析
2013-09-15 22:22
549 查看
前言
功能开发完善的应用程序,如果性能和体验太差,只能算是一款糟糕的软件。只有流畅的体验和一流的性能才能确保一款优秀的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
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
详情页的布局大致是这样的,第一层内容是一个Pivot,大图模式在第二层,当IsShowLargeimage为ture时显示大图模式。问题是,当在2种状态之间来回切换时,中间有一个很明显的UI阻塞现象,约有0.3s。时间消耗主要在Recreate上,因为当元素的Visibility属性设置为Collapsed时将会被释放掉,重新设置为Visible则会重新创建和布局。所以我们考虑用缓存的方法消除这部分时间。
Code
修改后的布局如上所示,当需要显示大图模式时,将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
这样在左右滑动屏幕时没有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
使用此方法可以使图片加载以随机并且依次的顺序显示出来,不影响浏览体验,再配合一个图片淡出的动画效果就很cool了。
Xaml
六、优化策略总结
不难发现,我们采用性能优化策略有如下几种,分别是前面每一节用到的:
1、预加载
2、使用缓存
3、避免缓存的更新,避免重绘的发生
4、分散UI线程工作
七、结束语
在wp7手机优秀而强大的系统硬件配置下,只要在开发过程中,适当注意和使用合理的策略进行开发或者优化,保证流畅顺爽的客户端应用程序体验是不是很简单轻松?
2012.6
功能开发完善的应用程序,如果性能和体验太差,只能算是一款糟糕的软件。只有流畅的体验和一流的性能才能确保一款优秀的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
相关文章推荐
- apache kafka系列之性能优化架构分析
- 服务器数据库系列 - 由浅入深探究mysql索引结构原理、性能分析与优化
- javascript性能优化之DOM交互操作实例分析
- Android官方开发文档Training系列课程中文版:性能优化建议
- Android应用开发性能优化完全分析
- 性能优化系列第五篇-- -性能优化实例
- OpenCL性能优化实例研究系列2:避免Local Memory Bank Conflicts的两个简单方法
- Linux性能优化和监控系列(二)分析CPU性能
- 【Android应用开发技术:图像处理】Bitmap显示性能优化分析
- Android应用开发性能优化完全分析
- C#性能分析优化系列文章索引
- 开发网站性能分析及优化
- 企业应用网站性能优化实例分析
- Android应用开发性能优化完全分析
- Android官方开发文档Training系列课程中文版:布局性能优化之按需加载View
- 企业应用网站性能优化实例分析
- perf 性能分析实例——使用perf优化cache利用率
- Android应用开发性能优化完全分析
- Android应用开发性能优化完全分析
- Android应用开发性能优化完全分析-转载大神总结的.非常全面系统