多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

UWP 启动优化实战:Windows-universal-samples 中的 x:DeferLoadStrategy 延迟加载示例解析

UWP 启动优化实战:Windows-universal-samples 中的 x:DeferLoadStrategy 延迟加载示例解析 示例工程【免费下载链接】Windows-universal-samplesAPI samples for the Universal Windows Platform.项目地址https://gitcode.com/gh_mirrors/wi/Windows-universal-samples点击查看免费下载x:DeferLoadStrategy 是 UWP XAML 提供的一项标记扩展允许开发者把标记中定义的元素延迟到真正需要时才创建从而显著缩短应用启动时间。本文以 Windows-universal-samples 仓库中的 XamlDeferLoadStrategy 示例 为骨架结合其 C# 源码逐一讲解三种典型用法——按需创建偶发 UI、配合 VisualStateManager 构建低开销自适应界面、以及延迟控件模板部件帮助你掌握将 x:DeferLoadStrategy 应用到实际 UWP 项目中的完整方法。示例概览XamlDeferLoadStrategy 要解决什么问题在传统的 UWP XAML 中页面标记中声明的所有元素在页面加载时都会被解析并创建元素越多、层级越深启动阶段的额外开销就越大。x:DeferLoadStrategy 的目标正是推迟创建在标记中把元素标记为Lazy后该元素在应用启动时不会进入可视树visual tree只有当你显式触发创建条件时它才被实例化并挂载。本示例在仓库中的路径为 Samples/XamlDeferLoadStrategy核心说明见其 README.md原文将其能力归纳为三点Create incidental UI when needed按需创建偶发 UI通过响应事件并调用FindName基于用户交互来实例化元素Create adaptive UIs that are less expensive创建开销更低的自适应 UI延迟元素后再通过 VisualStateManager 定向触发它们从而在不符合 VisualState 条件的形态因素如小屏设备上减小自适应界面的资源占用Defer control template parts延迟控件模板部件自定义控件中某些模板部件预期不常被用到时可以把它们延迟仅在满足使用条件时才创建。示例由三个可切换的场景构成注册逻辑位于 SampleConfiguration.csBasic Deferral、Adaptive Deferral、Control Template Deferral对应本文后续的三节。主页面 MainPage.xaml 使用SplitView承载场景导航通过 MainPage.xaml.cs 的ScenarioControl_SelectionChanged在ScenarioFrame中导航到各场景页。前提条件与构建运行系统与工具要求根据 README.md 的说明运行该示例需要客户端Windows 10服务器Windows Server 2016 Technical PreviewPhoneWindows 10Windows 通用示例UWP samples需要 Visual Studio 进行构建、Windows 10 执行。构建步骤如果通过 ZIP 下载了示例集合请解压整个归档而不仅仅是单个示例所在的文件夹——示例之间共享SharedContent等依赖。启动 Microsoft Visual Studio选择FileOpenProject/Solution。在解压后的目录中进入 Samples 子文件夹再进入本示例的文件夹最后进入你偏好的语言子文件夹本仓库提供 C# 版本双击 Visual Studio 解决方案文件 xDeferLoadStrategy.sln。按 CtrlShiftB或选择BuildBuild Solution构建。运行步骤仅部署选择BuildDeploy Solution。部署并调试运行按 F5 或选择DebugStart Debugging不调试直接运行则按 CtrlF5 或选择DebugStart Without Debugging。场景一按需创建偶发 UIBasic Deferral标记层面的延迟声明场景页面 BasicDeferral.xaml 演示了最基础的延迟用法。页面中声明了一个包含四个彩色矩形的Grid并在其声明处添加x:DeferLoadStrategyLazyGrid x:NameDeferredGrid x:DeferLoadStrategyLazy Margin0,12,0,0 Grid.RowDefinitions RowDefinition HeightAuto / RowDefinition HeightAuto / /Grid.RowDefinitions Grid.ColumnDefinitions ColumnDefinition WidthAuto / ColumnDefinition WidthAuto / /Grid.ColumnDefinitions Rectangle Height100 Width100 Fill#F65314 Margin0,0,4,4 / Rectangle Height100 Width100 Fill#7CBB00 Grid.Column1 Margin4,0,0,4 / Rectangle Height100 Width100 Fill#00A1F1 Grid.Row1 Margin0,4,4,0 / Rectangle Height100 Width100 Fill#FFBB00 Grid.Row1 Grid.Column1 Margin4,4,0,0 / /Grid Button x:NameRealizeElements ContentRealize Elements ClickRealizeElements_Click Margin0,12,0,0 /关键点在于DeferredGrid及其内部的四个Rectangle在页面加载时不会被创建页面初始阶段的可视树中并不包含这些元素页面下方的Realize Elements按钮则作为触发入口。通过 FindName 触发实例化对应的代码后置文件 BasicDeferral.xaml.cs 中按钮点击事件只有一行核心逻辑private void RealizeElements_Click(object sender, RoutedEventArgs e) { this.FindName(DeferredGrid); //This will realize the deferred grid }这里FindName(DeferredGrid)会查找名为DeferredGrid的元素如果该元素当前尚未被创建处于延迟状态调用FindName会触发它的实例化将其真正挂入可视树。这就是 x:DeferLoadStrategy 与事件驱动实例化结合的典型模式——非常适合那些不一定每次都会用到的偶发 UI例如对话框、弹出面板、设置区域等把它们从启动路径中剥离出去换来更快的首屏呈现。使用要点FindName是触发延迟元素实例化最直接的途径也可以用绑定、Storyboard 目标、VisualState 等方式触发见后续场景。一旦元素被实例化它就会像普通元素一样工作后续访问其命名引用不再有延迟开销。场景二用 VisualStateManager 打造低开销自适应 UIAdaptive Deferral从手机到桌面的自适应布局场景页面 AdaptivePage.xaml 模拟了一个跨手机到桌面尺寸的邮件阅读应用包含三个主要面板账户列表AccountsListListView、邮件列表MailList、阅读窗格ReadingPaneRelativePanel。其中AccountsList与ReadingPane都被标记为延迟加载ListView x:NameAccountsList x:DeferLoadStrategyLazy LoadingAccountsList_Loading Grid.Row1 ... /ListView RelativePanel x:NameReadingPane x:DeferLoadStrategyLazy Grid.Row1 Grid.Column2 Margin12 ... ... /RelativePanel页面顶部 TextBlock 的描述文字 明确说明了意图使用延迟加载配合 VSM在屏幕尺寸变化时按需创建元素让自适应 UI 从手机一直扩展到桌面并且只为当前显示尺寸创建必要的面板。VisualState AdaptiveTrigger 触发延迟元素AdaptivePage.xaml 中定义了一个名为Layouts的VisualStateGroup包含三个 VisualState分别以窗口最小宽度为阈值VisualStateAdaptiveTrigger 阈值行为PhoneMinWindowWidth0默认态不创建额外面板TabletMinWindowWidth768Storyboard 将ReadingPane.Visibility置为VisibleDesktopMinWindowWidth1200Storyboard 将ReadingPane与AccountsList的Visibility均置为Visible示例中特意保留了被注释掉的VisualState.Setters写法例如Setter TargetReadingPane.Visibility ValueVisible /实际使用的是StoryboardObjectAnimationUsingKeyFrames的方式VisualState x:NameTablet VisualState.StateTriggers AdaptiveTrigger MinWindowWidth768 / /VisualState.StateTriggers !--VisualState.Setters Setter TargetReadingPane.Visibility ValueVisible / /VisualState.Setters-- Storyboard ObjectAnimationUsingKeyFrames Storyboard.TargetNameReadingPane Storyboard.TargetPropertyVisibility DiscreteObjectKeyFrame ValueVisible KeyTime0 / /ObjectAnimationUsingKeyFrames /Storyboard /VisualStatex:DeferLoadStrategy 与 VSM 的配合原理是延迟元素不在可视树中但 Storyboard 的Storyboard.TargetName引用会在状态触发时强制实例化该元素使其可用。这样在窄屏Phone 状态下ReadingPane与AccountsList根本不会被创建只有窗口宽度达到 768/1200 等阈值、对应状态被触发时所需面板才会被按需唤醒。原文注释也指出这里用的是自适应触发器adaptive triggers但同样的机制也可以扩展到指针状态、键盘状态等其他触发器类型。窗口尺寸变化时的二次防御除了 VSMAdaptivePage.xaml.cs 还监听了ApplicationView.GetForCurrentView().VisibleBoundsChanged在窗口变窄时主动折叠面板private void AdaptivePage_VisibleBoundsChanged(ApplicationView sender, object args) { //Here we are hiding panes when we get to certain thresholds if (ApplicationView.GetForCurrentView().VisibleBounds.Width 768 ReadingPane ! null) ReadingPane.Visibility Visibility.Collapsed; if (ApplicationView.GetForCurrentView().VisibleBounds.Width 1024 AccountsList ! null) AccountsList.Visibility Visibility.Collapsed; }注意这里对ReadingPane ! null的判空检查因为面板可能是延迟的尚未实例化时引用为 null直接访问会出错因此必须用判空保护。同理AccountsList_Loading 在Loading事件中为列表设置ItemsSource其注释说明了原因由于 AccountsList 有可能稍后才被创建需要在 Loading 中设置数据源private void AccountsList_Loading(FrameworkElement sender, object args) { //We need to do this in Loading for AccountsList, since its possible that it will be created later AccountsList.ItemsSource Accounts; }数据模型AdaptivePage.xaml.cs 中准备了四个模拟账户outlook/gmail/yahoo/mydomain图标来自 Assets/Icons 目录下的 png与五封示例邮件通过ObservableCollectionAccount/ObservableCollectionEmail绑定到两个ListView的ItemsSource。Account与Email是定义在同文件末尾的简单数据类L59-L71。使用要点延迟元素 VisualStateManager状态触发即实例化适合按屏幕形态按需创建的面板类 UI对延迟元素访问前务必判空因为其实例化时机不确定数据准备应放到Loading事件或元素实例化之后而不是页面构造函数中直接依赖命名引用。场景三延迟控件模板部件Control Template Deferral自定义控件 TitledImage第三个场景演示如何在控件模板ControlTemplate中延迟不常用的部件。自定义控件定义在 TitledImage.cspublic class TitledImage : Control { public static readonly DependencyProperty HeaderProperty DependencyProperty.Register(Header, typeof(string), typeof(TitledImage), new PropertyMetadata(null)); public string Header { get; set; } public static readonly DependencyProperty SourceProperty DependencyProperty.Register(Source, typeof(ImageSource), typeof(TitledImage), new PropertyMetadata(null)); public ImageSource Source { get; set; } private ContentPresenter _header; public TitledImage() { this.DefaultStyleKey typeof(TitledImage); //We need to register the callback in case they change the header at runtime this.RegisterPropertyChangedCallback(HeaderProperty, HeaderChanged); } protected override void OnApplyTemplate() { base.OnApplyTemplate(); //Check to see if theres a header, and realize the element if there is one if (!string.IsNullOrEmpty(Header)) { _header (ContentPresenter)GetTemplateChild(HeaderPresenter); //This will realize the element } } }这个控件由标题Header 图片Source两部分组成。设计意图是标题内容展示区HeaderPresenter不常被使用因此它应该被延迟创建——只有当确实设置了Header时才实例化。模板中标记延迟部件控件模板定义在同项目的 Themes/Generic.xaml 资源字典中。模板内部把ContentPresenter名为HeaderPresenter标记为x:DeferLoadStrategyLazyStyle TargetTypelocal:TitledImage Setter PropertyHorizontalAlignment ValueLeft / Setter PropertyTemplate Setter.Value ControlTemplate TargetTypelocal:TitledImage StackPanel ContentPresenter x:NameHeaderPresenter x:DeferLoadStrategyLazy Content{TemplateBinding Header} / Image Source{TemplateBinding Source} Height150 Width150 StretchUniformToFill / /StackPanel /ControlTemplate /Setter.Value /Setter /Style这里的关键是GetTemplateChild(HeaderPresenter)与x:DeferLoadStrategyLazy的组合模板中声明了延迟的HeaderPresenter而控件代码在OnApplyTemplate中检查Header是否为空仅当非空时才调用GetTemplateChild(HeaderPresenter)去实例化它——该调用会触发延迟元素的创建。若Header为空则HeaderPresenter永远不会被创建从而省下这部分开销。运行时修改 Header 的兜底回调TitledImage.cs 还通过RegisterPropertyChangedCallback(HeaderProperty, HeaderChanged)注册了属性变更回调处理运行期间才设置Header的情况void HeaderChanged(DependencyObject sender, DependencyProperty prop) { string header (string)sender.GetValue(prop); //Double check to make sure that we dont needlessly realize the header presenter if (!string.IsNullOrEmpty(Header) _header null) { _header (ContentPresenter)GetTemplateChild(HeaderPresenter); //This will realize the element } }回调中的双重检查_header null确保不会重复实例化已创建的 presenter。场景页中的对照验证场景页 DeferredControlTemplatePart.xaml 放置了两个TitledImage实例做对照local:TitledImage HeaderRainier Sourcems-appx:///Assets/rainier.jpg / !--This one will realize the header content presenter in the control template that was deferred -- local:TitledImage Sourcems-appx:///Assets/valley.jpg Margin0,12,0,0 / !--This one will not realize the header content presenter because Header was not specified --第一个实例指定了HeaderRainier在应用模板时会实例化被延迟的HeaderPresenter第二个实例未指定HeaderHeaderPresenter始终保持未创建状态。该场景说明文件同样可在 DeferredControlTemplatePart.xaml.cs 查看其代码后置仅包含标准的页面构造与InitializeComponent。使用要点模板部件延迟适合预期不常用、但为完整性仍需保留的模板部分触发实例化要放在OnApplyTemplate模板应用时与属性变更回调运行时改属性两处兼顾初始化与动态更新通过判空与_header null双重检查避免不必要的实例化或重复实例化。三种场景对照与选型建议场景延迟对象触发方式典型收益Basic Deferral偶发 UI弹出区、附加面板事件回调中调用FindName启动时不创建未必会用的 UIAdaptive Deferral自适应布局中的分屏面板VisualState AdaptiveTrigger / Storyboard 触发小屏不创建大屏才用的面板降低自适应 UI 开销Control Template Deferral控件模板中不常用的部件OnApplyTemplateGetTemplateChild、属性变更回调自定义控件模板瘦身减少不必要的模板部件实例化选择建议如果你的元素只在特定用户操作后才出现用FindName事件驱动场景一如果元素与窗口/设备形态强相关用 VSM 驱动场景二如果涉及自定义控件的模板部件则把延迟逻辑封装进控件自身场景三。延伸与相关主题的衔接x:DeferLoadStrategy 是 UWP XAML 中面向启动性能优化的机制之一与x:Load按需加载整个 XAML 子树并支持卸载属于同一类延迟实例化思路的演进二者都服务于减少启动阶段创建的 UI 数量。本示例所属的 Windows-universal-samples 集合中RSS reader sample 被 README 列为相关示例可结合实际应用进一步体会延迟加载策略在真实产品中的组织方式。在将该技术应用到自己的项目时请记住三点实践原则对延迟元素的所有访问都要考虑它可能尚未创建这一前提判空或使用Loading事件触发机制FindName、VSM、GetTemplateChild要与使用场景匹配以及延迟不是免费午餐——被延迟的元素在被触发前无法绑定、无法参与布局设计页面结构时应把高频必需元素留在启动路径上把低频可选元素交给延迟策略。赞分享示例工程【免费下载链接】Windows-universal-samplesAPI samples for the Universal Windows Platform.项目地址https://gitcode.com/gh_mirrors/wi/Windows-universal-samples点击查看免费下载相关推荐competitive-ads-extractor 实战指南用 Codex Skill 从广告库提取并剖析竞品广告策略competitive ads extractor 实战指南用 Codex Skill 从广告库提取并剖析竞品广告策略 本篇技术指南围绕开源仓库 awesom示例工程基于 ActivitySensor 的 UWP 活动检测实战Windows-universal-samples 活动传感器示例全解析基于 ActivitySensor 的 UWP 活动检测实战Windows universal samples 活动传感器示例全解析 导读 本文围绕 Wind示例工程UWP 中 ListView 与 GridView 的实战指南基于 Windows-universal-samples XamlListView 示例的源码解析UWP 中 ListView 与 GridView 的实战指南基于 Windows universal samples XamlListView 示例的源码解示例工程上一篇ZeroTierOne终极指南3步实现跨网游戏零延迟联机下一篇Walmart与Best Buy抢购对比Bird Bot双平台支持实战体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表