使用.NET平台,如何玩转UniversalWindows应用?
2015年7月30日
成都创新互联于2013年开始,是专业互联网技术服务公司,拥有项目成都做网站、网站设计网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元行唐做网站,已为上家服务,为行唐各地企业和个人服务,联系电话:18982081108
本文作者是 Managed Languages 团队项目经理 Lucian Wischik。
不久前,Visual Studio 2015上新增 Windows 10 应用的开发工具——Universal Windows App开发工具。这个发布拥有重大意义:开发者可利用最新的 .NET 技术开发 Universal Windows Platform (「UWP」) 应用,这些应用程序可运行在任一款 Windows 设备上——Windows 手机、平板电脑或者笔记本电脑、PC 机、Xbox 游戏机,以及 Windows 新出的HoloLens、Surface Hub 和 Raspberry Pi 2(IoT 设备)等等。
安装 UWP 工具
开发者可下载安装免费的 VS2015 的社区版,该版本默认安装 UWP 工具。如需安装专业版或是企业版,可从VisualStudio.com 处下载安装。在安装过程中,选择「Custom(自定义)」安装 Universal Windows Apps 开发工具。
如果已经安装了 Visual Studio 2015,有两种方式获得 Universal Windows Apps 开发工具:
下载并运行 Windows Tools installer。
从控制面板打开「程序和功能(Programs and Features)」,选择 「Visual Studio 2015」并点击「更改(Change)」。然后在安装过程中,点击「修改( Modify)」,选择「Tools for Universal Windows Apps」。
UWP 新功能
只要是 .NET 开发者都会喜欢 UWP 提供的特性——
UWP 应用在安装 Windows 10 操作系统的台式机上以窗口化视图运行。
UWP 应用在任一款 Windows 10 设备上均可运行——手机、XBox、HoloLens 甚至是 Raspberry Pi 等物联网设备。
UWP 应用利用了最新 .NET Core 的技术优势,通过使用 .NET Core 的最新版本的新加功能简化应用程序的开发。
应用程序和业务逻辑核心的 .NET,同样可在如 ASP.NET 5 等平台(支持 .NET Core 的平台)上运行。
UWP 应用在程序内部署缩减后的 .NET 副本,以便应用总是使用经过验证的 .NET 版本 。
UWP 应用使用 .NET Native 技术,在客户机下载代码前,.NET Native 可生成高度优化的原生机器代码。.NET Native 技术的使用,使得应用程序的启动时间缩短、电量消耗降低和性能加快。
用户可很方便地在 Windows 商店内购买、安装和升级 UWP 应用程序。
UWP 应用程序完美地结合了用于详细测试和分析的Application Insights插件——一个了解用户需求和提高应用程序质量的重要工具。
新工具带来的新用途——
使用 .NET 编写 Windows 10 UWP 应用程序。
编写用于 .NET Core 的 Portable Class Libraries
相比之前 Windows Store 或 Phone 应用,UWP 应用程序中可以使用更多的 .NET 外部工具,包括 System.Net.Sockets、WCF Client、System.Numerics.Vectors 和新的 Diagnostics APIs。
NuGet 3.1(由文件「project.json」识别)可用于不同类型项目项目。
UWP 开发入门
下面是关于 UWP 开发的一些实用的概述和教程:
如何开发 Windows 10 通用应用程序[MSDN]——利用自适应 UI 界面和自适应代码使得 UWP 应用在 Windows 10 设备上看起来更加美观和运行更加流畅。
UWP 应用程序指南[MSDN]--「通用」应用程序如何在所有设备上运行的。
移植应用程序到 UWP[MSDN]--从 Phone Silverlight、Win8.1 和 VS2015 RC 移植到 UWP 上。
利用 C# 和 XAML 编写 Universal Windows Apps[Microsoft Virtual Academy]——Jerry Nixon 教授发布的长达22小时实用在线训练课程。
在 VS2015 上开发 UWP 应用程序[BUILD talk]。
深入研究 XAML 和 .NET UWP 开发[BUILD talk]。
在本篇博客中,笔者将会介绍:作为 .NET 开发者,需要注意的哪些改进的地方——其他教程不会涉及的内容。首先需要建立平台,下面十张图中涵盖了 .NET UWP 开发过程中全部 Microsoft 工具。
File > New > C#/VB > Windows > Universal 开始编写一个全新的 UWP 应用。改进后的 NuGet 比 VS2015 RC 要快得多。开发者同样可创建一个兼容 UWP、ASP.NET 5 和 .NET 4.6 的 Portable Class Libraries (PCLs) 。
Solution Explorer > References References利用独特的图标显示 NuGet 程序包。「Microsoft.NETCore.UniversalWindowsPlatform」是其中比较重要的一个包;它包含了 .NET Core 运行时和框架。 project.json 文件取代 packages.config 驱动 NuGet 3.0。NuGet 3.0 与 NuGet 2.0 相比,运行速度更快且更加复杂。
Adaptive XAM 开发人员经常设计「自适应的 UIs」以便其适应于不同设备、不同形式。现在随着 XAML 的发展,ViewState triggers、更多设备预览和现场 XAML Tree 调试等方式使得这项任务变得非常容易。同样, 在高性能数据绑定使用 x:Bind。
Adaptive code 一个优秀的通用应用程序的关键在于在不同的设备间可尽可能多的分享代码,与此同时还要保障每个设备上都有最好的应用体验。开发者可通过调用特定平台 WinRT APIs,在 .NET 中编写自适应代码。这比使用 Reflection(自适应代码的前沿技术)方式要好的多。
Fast graphics:Win2d和System.Numerics.Vectors。对于快速绘图,可利用Win2d 库——是DirectX上 .NET 一个「精致」的封装。当然,这里仍可以使用SharpDX 或者 MonoGame。System.Numerics.Vectors 通过 CPU 的 SIMD 指令进行快速矢量和矩阵运算。在来利用这些技术后,在中端 Nokia 635计算 Mandelbrot Fractal 仅需70毫秒。
WCF,HTTP/2 and Sockets目前 .NET 库包括WCF和 AddServiceReference,两者之前均不适用手机应用程序。HttpClient已被重写:重写后性能更好,并且支持HTTP/2协议。这里同样需要System.Net.Sockets,Windows Store 应用中期待已久 .NET 特性。
Improved debugging and EnC 现在,开发者在仿真器上调试时可以使用「Edit and Continue (EnC)」。整个调制器引擎早已修改——在即时和观察窗口中支持 lambdas 和 LINQ 表达式,同时与之前相比,在更多地方支持 EnC。一些开发者在 EnC 上编写整个程序的代码。快尝试下吧!
.NET Native 当处于 Release 模式中,应用程序通过新「.NET Native」编辑器编译。这就将其转化为高度优化的原生机器代码——应用程序启动时间缩短、电量损耗降低和整体性能加快。
Store submission 开发人员将十分喜爱新的统一开发者中心( Developer Center)。在提交一个应用时,向导将会提交应用程序的 MSIL。商店使用 .NET Native 进行编译,将应用程序优化为原生机器代码(这是一个很难的反向工程,就像 C++ 代码那样),并将其部署到用户设备中。
Application Insights and Diagnostics 新项目中默认安装Application Insights 插件。该插件为应用程序提供崩溃和使用时的详细分析。应用商店中排名较高的应用程序都已知晓排名较高的原因是接收和分析响应。在ETW中有着更为丰富的追踪功能。
.NET Native
.NET Native 是预(「AOT」)编译技术:在编译的时,它将代码转为机器代码。这与传统的 .NET 使用的实时(「JIT」)编译技术不同,在运行时延迟本地编译直到它第一次执行。.NET Native 更接近与 C++ 编译器。事实上,它的工具链中有 Visual C++ 编译器,在 Windows Store 中,该工具用于提交应用程序(并非最终用户设备)。它能够快速地、精确地、独立地生成代码。
.NET Native 对终端用户有着巨大的好处:应用启动时间缩短60%,并且优化应用程序的内存占用。在一些 UWP 应用程序上,笔者曾成功地将启动时间由1秒缩短到110ms,测试机型号为 Nokia 635。这都归功于 .NET Native 和 VS 2015 新的perf-tips 功能和 Diagnostics Tools 窗口。
目前在 .NET 团队博客上已经发布了很多关于 .NET Native 预览版的文章。UWP中创新点在于它是第一个使用 .NET Native 的产品。对于大部分情况而言,.NET Native 是透明的:当在 Release 模式下使用时,它编译进行的相当隐蔽;Release 版本建立需要更长的时间,并且调试稍微差一点,性能特点也稍微有点不同;但除此之外应用程序能继续正常运行并实现功能。Debug 版本主要依靠 CoreCLR,如你期望的那样,有着杰出的调试体验。
尽管 .NET Native 早在一年前就已公开预览,对于很多人乱来说,这仍将是在 UWP 中第一次使用。出于这个原因,笔者会更详细的介绍下它是如何工作的。不仅因为以此为豪,同时也为了满足读者的好奇心。
.NET Core Framework
上周的一篇博文中已经讨论过了 .NET Core Framework ("CoreFX"):
.NET Core 是现代化设备和云工作负载使用的 .NET 最新版本。.NET Core 以通用化为目的,并采用模块化部署,可在不同环境下的多种工作负载移植和使用。
CoreFX 常用于 UWP 应用程序。它是曾用于 Windows Store 开发的 .NET APIs 的扩展包。
这里重点介绍下 UWP 开发者比较感兴趣的 .NET Core FX:
System.Net.Sockets(曾用于 UDP 通信)曾用在 WinRT 应用程序上。其方式是使用特定的 WinRT UDP APIs。现在 System.Net.Sockets 已经成为 .NET Core 的一部分,所以可被 UWP 应用使用。事实上,开发者可以在其他的 .NET 应用程序上共享 Sockets 代码。注:这里正在致力于 System.Net.Sockets 的开源工作。
HttpClient(类似许多 .NET Core FX 的低 level 部分)需要进行不同的部署才能在不同的平台运行。在 UWP 应用中,它被部署在 WinRT HTTP 栈的顶部。其默认的情况下使用HTTP/2协议,以获得更低的延迟和更少的往返通信次数,当然前提是服务器支持该协议。
WCF Clien(和关联的Add Service Reference dialog)曾在 Windows Phone Appx 项目中使用。但自从它变成 .NET Core 的一部分后,就经常被用于 UWP 应用程序中了。
System.Numerics.Vectors提供了向量和矩阵类型,该类型常用于 CPU 的 SIMD 操作码——单指令多数据。矢量和矩阵的运算速度相比于正常的「单指令单数据」操作码要快得多。
-System.Diagnostics.Tracing。目前调度事件中,EventSource 可发送更丰富的有效负载到 Windows 事件跟踪(ETW)中。
CoreFX 另外两个令人兴奋的方面是:开源和解除了与 Windows 和 Visual Studio 发布捆绑。每个开发者都可以参与其中并作出自己的贡献,.NET 团队每天都有所贡献。该团队与社区一起致力于扩展 CoreFX,添加更多的 APIs。只要这些接口能加入其中,就能为 UWP 应用程序所用。由于 project.json 和 NuGet 存在,任一 UWP 开发人员都能使用最新版 .NET Core FX Packages(只要它们可用)——仅通过「Manage NuGet Packages」对话框即可。
注意:File>New 可以生成一个具有全套官方 Microsoft NET Core 组件的 UWP 应用程序,这些与 UWP 应用相关组件是用于其测试。如果想其他的或者尚未开发的 Microsoft 库,不能再使用「References > Add References」下——相反地,而是在「References > Manage NuGet References」中。
如果想自行编写一个 .NET Core 库,大可试着开发任一 .NET4.6、UWP 或 ASP.NET 5 平台可用的 PCLs。
Universal Projects
利用 UWP 可以编写通用的应用——单一的 VS 项目、单一的代码库、单一上传到 Windows Developer Center --即便其运行在多个「家族设备」(桌面、手机、Xbox 等等)。利用 UWP,应用程序代码不再需要使用#ifdefs 或 Shared Projects。通过此方法,应用程序开发和维护将会变得更加容易。
MSDN 上的「UWP 应用程序指南」对如何让应用程序在不同的设备上界面看起美观这一问题做了很好的解释。好的方面是,通过 UI 调整能使得应用程序在桌面不同大小的窗口看起来都很美观,在不同设备同样也可做到这一点。
从 .NET 方面来看,最有趣的技术就是采用自适应代码。例如:
在 Windows 10 电脑桌面上,我的应用程序及其美观,但是在 Windows 手机界面上,它仅仅显示 Status Bar(状态栏)。如果使用了StatusBar.HideAsync
,应用程序应该会看起来更好看一点。然而StatusBar是电脑桌面上不存在的类型。处理此情况的代码看起来非常简单——WinRT API: Windows.Foundation.Metadata.ApiInformation.IsTypePresent
用于检测应用程序正运行的设备上有无 WinRT 类型,并且它只会调用该案例中特定平台方法。
有时候很难记住哪个 API 需要 IsTypePresent 保护。为此,这里开发一个名为PlatformSpecific.Analyzer的 NuGet 包,开发者可以将其添加到项目中:如果忘记保护某个接口,它将会在 IDE 中显示警告信息。
有趣的是,这种自适应代码目前仅在 UWP 平台上的 .NET 中可用,并且是只针对 UWP 类型。刚入门的 .NET 专家可能比较想了解详情。对于 Debug builds,CoreCLR需要( JIT)实时编译 SetupAsync 方法,想要做到这一点必需要清楚代码主体中每种类型和方法的元数据,同时还要弄明白那些即便不运行的分支中方法类型。 UWP 处理此问题需要建立一个本地应用程序文件「windows.winmd」,该文件包含全部 UWP 设备和各个版本中使用过 WinRT 类型和方法的元数据。对于 Release builds,.NET Native 将必要的元数据最终编译成机器代码,其格式一般是 COM IIDs 或者虚拟表形式。
因为将现有的代码库移植到 UWP 十分重要,这里不得不提自适应应用程序中PCLs的最后一个话题。如果编写一个「8.1 通用PCL」,即能同时在 Windows 8.1 和 Phone 8.1 使用。可参考 UWP 应用程序中使用 PCLs 的方式,将其做的完美。这是因为那些 PCLs 只能称之为 WinRT APIs 的子集,所有 UWP 平台都兼容该子集。
NuGet 3.0和「project.json」
在 .NET 应用程序中,NuGet 事实上已经成为包管理的标准。这里本打算将 .NET Core 作为 NuGet 包进行部署,但现有的 NuGet 2.0 客户端和 packages.config(尽管前景很好),却不是扩展到100+子包后 .NET Core的最佳选择——速度太慢,又不够灵活。NuGet 3.0解决了这些问题。最初是用于 ASP.NET 5中,现在 UWP 也在使用。
如果一个项目中使用了 project.json 文件而非 packages.config,同样可以说该项目使用了 Nuget 3.0。开发人员同样可以将 project.json 添加到任一现有的 .NET 项目中,同样会起作用(首先需要项目卸载再重载)。下面是 project.json 的工作方式:
当安装一个 NuGet 包时,project.json 文件中将会自动添加一个引用,可以在 SolutionExplorer > References 下查看它。
在 Build 之前,VS 确保所有的 NuGet 包(以及相关文件)成功的下载到用户设备上的缓存中心内,由它选择当前项目目标/架构所要使用的包。
在 Build 时,如果存在 project.json 文件,MSBuild 将会读取该文件并引用相应的 DLLs 和它包含的 .targets 文件。
这里看下 project.json 带来的好处:
.vbproj/.csproj 将不再包含任何 NuGet 引用:它们将完全分开。这将使得源代码控制和合并冲突解决更加容易!
可以修改应用程序的目标平台,更改 Debug/Release 以及 x86/x64/ARM/ 任一 CPU,NuGet 将会实现这些设置。
在同一个需要 NuGet 的项目中,可以在两个不同的目录下分别部署不同的解决方案。当需要在两个库中工作时,这将十分有效。
Solution Explorer > References 将会更加简洁,因为它只包含了安装时选择的包而非全部相关的包。卸载 NuGet 包也变得更加方便,同样是因为只需卸载选定的包而非全部相关的包。
包可在全局(每台机器上的每个用户)内缓存,省略了单个解决方案使用时下载+解压缩的步骤。
File > New 和Manage NuGet Packages > Install 两者速度都有所加快。
在 NuGet 包升级和版本不匹配时,控制更加精确。
请在 NuGet Team Blog 和 NuGet Home repo 查看更多关于 NuGet 的资料。两者均是与该团队讨论 NuGet 变化的最佳场所。现知的一个问题(并期望在未来升级版中解决该问题):UWP 应用中 Solution Explorer References 节点下显示 NuGet 包所有相关的文件,正如 ASP.NET 5同样具有该功能,这是一个好的改变吗?
一些 NuGet 包安装在 UWP 应用时,其工作方式会发生变化。如果你发现某些包出现异常情况时,请在该贴底部的评论区留言。
SharpDX.Toolkit 2.6.3 升级之后的 SharpDX 3(目前仍是 Alpha 版本)在 UWP 应用中表现出色。SharpDX 工具包已被废弃,并将不会在版本3中添加,同时也将不会安装到 UWP 应用中。这里将考虑 Paradox 和 MonoGame 作为其在 SharpDX 上代替工具包。
MvvmLight 该 NuGet 包可在 UWP 应用上正常工作,除了在安装时需要多加一个额外步骤。安装时能自动修改 App.xaml 文件和向项目中添加其他一些文件。但这并不适用于 UWP 应用,所以这里需要手动修改或者使用 MvvmLight VSIX。
Sqlite-net 尽管 UWP 应用中不再安装该 NuGet 包,与其类似的Sqlite.Net-PCL(同一作者)包表现抢眼。
LiveSDK 该 NuGet 包尽管仍会安装,但没有实际引用 DLLs。在短期工作中,可以自行手动添加引用 Microsoft.Live.dll(如何确定 DLL 文件的位置?最简单的方法是在 UWP 应用中添加 LiveSDK 引用,然后将 NuGet 下载到%USERPROFILE%.nuget\packages\LiveSDK 路径下)。另一个选择,还可以使用该文档描述的Windows.Security.Authentication.OnlineID,至于 OneDrive,可通过 HttpCliet 使用 REST APIs。
顺便说一句,「现代化」PCLs 默认使用 project.json——例如某些可用于 .NET4.6、UWP 和 ASP.NET 5 Core 的 PCLs。
UWP 应用使用 CoreCLR 进行调试,而 .NET Native 使用 CoreCLR Release 下图显示了当编译、调试和提交 UWP 应用到商店时发生的状况。VB 和 C# 编译器在 MSIL 不断生成 DLLs 文件。这是接下来发生的变化...
Debug build: CoreCLR. When you build your UWP app in Debug mode, it uses the 「.NET Core CLR」 runtime, the same as used in ASP.NET 5. This provides a great edit+run+debug experience – fast deploy, rich debugging, Edit and Continue. It also means
调试版本:CoreCLR。当在 Debug 模式下编译 UWP 应用时,运行的是「.NET Core CLR」,在 ASP.NET 5 中同样如此。这提供了一个 edit+run+debug 极好体验——快速部署、多次 Debug、Edit 和 Continue。
发布版本:.NET Native。当在 Release 模式下编译程序时,它需要额外的30秒将 MSIL 和引用转换为优化的原生机器代码。这里正在努力缩短此时间。通过「tree-shaking」方式删除从未调用过的代码。并在「Marshalling Code Generation」预编译序列化代码以便在运行中无需反射。优化全部程序代码。本机代码的优化和编译生成单一本地 DLL 文件。可以在 bin\x86\Release\ilc 目录下找到此文件。
**.NET Core:CoreCLR和 .NET Native 两者均是「.NET Core Runtimes」。它们可以同时使用和运行相同的 .NET Core 库(CoreFX),所以在 Debug 模式和 Release 模式下不存在差异。在 Windows 8/8.1 中, .NET Framework 曾用于 .NET 的底层部署。在 Windows 10 中使用 .NET Core,可在调用新 CoreFX 库的同时提供 Debug 和 Release 两种模式。
Store submission。当开发了一个准备提交到 Windows Store 商店的 Appx 包时,该 Appx 附加包中包含 MSIL。然后 Windows Store 进行 .NET Native 编译。这就减轻了一些人对 .NET Core FX「局部应用部署」的担忧。他们担心如果在 .NET 中出现安全漏洞怎么办。在过去,通常的解决方法是进行 Windows 更新。现在,通过识别 Appx 包是否易受***,然后和其开发者一起进行修复解决。
.NET Native 开发技巧
在发布模式下测试 请在 Release 模式下定期测试的应用程序。Release 模式使用了 .NET Native。如果定期测试(比如四小时测试一次),将能够及时发现问题,如 Expression.Compile 的不同性能。如果在测试中发现问题需要调试时,当心发布版本是完全优化的,需要禁用优化以获得最佳的调试效果。
.NET Native 分析器。有一些 .NET Native 不支持的 .NET 功能,例如超过四维度以上的多维度矩阵(!)。当进行 .NET Native 编译时会了解到这些的。但是想要节约 .NET Native 编译多出30+秒的时间,可以通过自行引用Microsoft.NETNative.Analyzer(NuGet 包)立即得到警告。
AnyCPU被取消。这是因为 .NET Native 编译为原生机器代码。对于应用程序开发而言,CPU 几乎毫无份量。开发者仅需牢记在本地设备或者模拟器上部署时选择 x86;在 Win10 移动设备上部署选择 ARM ;或者 x64(这并不比 x86 效果好)。当为应用商店开发 Appx 包时,该向导将会生成三种不同的类型并提交到应用商店。
如果正在开发一个类库或 PCL,应当将其开发成「AnyCPU」类型。这将会使得事情简单化——可单独分配一个全部项目均可用的 DLL 文件。
我能找到的最简单的方式就是:点击 Build > ConfigurationManager 对话框。可以这样设置,对于该库而言,即使工具栏显示「AnyCPU」,仍将 builds+deploys应用为 x86 类型。
调试.NET Native。有时,开发者想在 .NET Native 中设置断点来调试代码。但最好请不要在 Release 模式下这样做,因为调试本身就很困难,.NET Native 的对代码积极优化将使得其难上加难。结果显而易见,使用 Debug 模式(关闭优化),然后临时修改项目配置以便使用 .NET Native(即便是在 Debug 版本也要这样做)。在 C# 中,在 .NET Native 工具栏中设置方式:Project > Properties > Compile。在 VB 中,设置方式:MyProject > Build > Advanced。
定制 .NET Native 优化。尤其是在应用程序中,通过巧妙地使用反射方式,.NET Native 可以删除多余的优化。这是可控的。这些博客解释得很好:
1. 静态代码中的动态特性。
2. 求助!我遇到 MissingMetadataException 异常。
3. 求助!我没遇到 MissingMetadataException 异常。
4. .NET Native 深入探索:让库变得更好。
5. [.NET Native 深入探索:优化运行指令][1]。
Expression.Compile。之所以介绍它,是因为它在 Newtonsoft‘s Json.Net 内部使用并且对开发者有着深远的影响。在传统的 CLR 中,表达树可在运行时编译为 MSIL,然后 JIT 将其转为原生代码。这在 .NET Native 中不会发生,.NET Native 将会解读这些表达树。请注意与 Json.Net 相比发生的变化,例如,启动时间缩短(无需加载 CLR 表达树架构),但在序列化大的数据文件时速度变慢。如果这对你的应用较为重要,请亲自测量。这一改变使得应用程序启动时间缩短了200ms。
F#-- 任一 UWP 商店应用程序均不能使用 F# DLLs:目前 .NET Native 不支持该文件。这是未来需要修复的一个问题。如果这对你很重要,请及时通知我们。
Get help。如果在使用 .NET Native 遇到问题,寻求解决方法的最好方式是发送电子邮件到 dotnetnative@microsoft.com。
总结
今天 Universal Windows Platform 发布为 .NET 开发者提供了一个全新的契机。对 UWP 应用而言,这是一笔巨大的财富,开发者可以使用最新的 .NET 技术开发应用。
请大胆地做出尝试并交流你的想法。如果存在任何问题。请在此处或者在Windows Dev Center 的「Developing Universal Apps」论坛上留言。如果通过使用 .NET Native 优化应用程序的启动时间在200ms以下,请在这里大胆的炫耀吧!
OneAPM 助您轻松锁定 .NET 应用性能瓶颈,通过强大的 Trace 记录逐层分析,直至锁定行级问题代码。以用户角度展示系统响应速度,以地域和浏览器维度统计用户使用情况。想阅读更多技术文章,请访问 OneAPM 官方博客。
本文转自 OneAPM 官方博客
原文链接:http://blogs.msdn.com/b/dotnet/archive/2015/07/30/universal-windows-apps-in-net.aspx
+
分享题目:使用.NET平台,如何玩转UniversalWindows应用?
文章转载:http://scyanting.com/article/pisigs.html