本文是我在读 WPF 源代码做的笔记。在 WPF 中的启动界面,为了能让 WPF 的启动界面显示足够快,需要在应用的 WPF 主机还没有启动完成之前就显示出启动图,此时的启动图需要自己解析图片同时也需要自己创建显示窗口
从 WPF 的 src\Microsoft.DotNet.Wpf\src\WindowsBase\System\Windows\SplashScreen.cs
文件可以看到 WPF 的 SplashScreen 的核心逻辑
在 SplashScreen 的构造函数会传入资源名,也就是启动图的资源名,或者加上指定程序集和图片资源名
当然了,这个设计扩展性不够好哈,不支持指定任意的图片。如果想要指定本地路径的任意图片作为启动图的,可以使用 lsj 提供的 kkwpsv/SplashImage: Fast splash Image with GDI+ in C# 库,当然了,这个库代码量特别少,我推荐大家可以抄抄代码。这个库提供的是高性能的版本,可以在另一个线程中执行,换句话说,就是使用 kkwpsv/SplashImage 作为欢迎界面,是可以做到不占用 WPF 主线程时间的,性能比 WPF 提供的好
在 WPF 的 SplashScreen 的 Show 方法,就是启动图的核心逻辑
先调用 GetResourceStream 从自己的程序集里面读取图片资源的原始 Stream 对象,通过此方式的读取性能特别强,因此不是真的读取到内存里面,而是获取一个指针而已。但是有趣的是在这个方法上面有注释说比 Assembly.GetManifestResourceStream 慢 200-300 毫秒,也许是当年的设备才需要这么长的时间
在获取到启动图片的 UnmanagedMemoryStream 之后,将使用下面代码转换为指针,用于后续传入给 WIC 层
接下来就是调用 CreateLayeredWindowFromImgBuffer 创建一个窗口然后这个窗口显示图片内容
可以看到在调用 CreateLayeredWindowFromImgBuffer 方法成功之后,就会调用 Dispatcher 插入 ShowCallback 函数,在 ShowCallback 里面用来自动关闭启动界面,如下面代码
从上面代码可以看到,在 WPF 中默认的启动图界面将会在 Loaded 完成之后延迟 0.3 秒执行,而具体是什么 Loaded 就不需要关注了。因为通过 BeginInvoke 插入的优先级是 DispatcherPriority.Loaded 优先级,也就是启动过程如果再没有什么比 DispatcherPriority.Loaded 更高的优先级,那就是启动完成了
在 WPF 里面的 SplashScreen 的核心逻辑里面包含以下三步
第一步是通过 WIC 层解码咱传入的图片,这样就支持不做任何优化的图片都能作为启动图
第二步就是将解码之后的图片编码为 BGRA 图片格式传给 GDI 图片对象,这样就能将咱的图片作为 GDI 图片对象能使用的资源
第三步是创建窗口显示这张 GDI 图片
回到创建窗口的核心方法 CreateLayeredWindowFromImgBuffer 上,这个方法里面大量调用 WIC 层的逻辑,用来处理图片的渲染,过程代码大概如下,下面代码为了方便说明,和 WPF 源代码有些不相同
上面代码中的 UnsafeNativeMethods.WIC 就是调用 WIC 层的逻辑,在 WPF 中的 WIC 层逻辑和其他 Win32 应用一样,通过 WindowsCodecs.dll 提供,只是在 UnsafeNativeMethods.WIC.CreateImagingFactory(UnsafeNativeMethods.WIC.WINCODEC_SDK_VERSION, out pImagingFactory);
可以看到 WPF 使用的版本是 0x236 比较古老
通过对比 6.2.9200.21830-Windows_7.0 和 6.3.9600.17415-Windows_8.1 版本的 windowscodecs.dll 可以看到有做了如下的更改
新版本的 WindowsCodecs.dll 更新请看 What’s New in WIC - Win32 apps
在调用到使用 GDI 图片创建窗口的逻辑就十分简单了,都是一些 Win32 的接口调用
当然了,在 WPF 里面再快的启动图显示速度都不如 UWP 快,因此 UWP 是系统给的优化,通过 AppFrameHost 显示的,基本上点击应用立刻打开
当前的 WPF 在 https://github.com/dotnet/wpf 完全开源,使用友好的 MIT 协议,意味着允许任何人任何组织和企业任意处置,包括使用,复制,修改,合并,发表,分发,再授权,或者销售。在仓库里面包含了完全的构建逻辑,只需要本地的网络足够好(因为需要下载一堆构建工具),即可进行本地构建
原文链接: http://blog.lindexi.com/post/dotnet-%E8%AF%BB-WPF-%E6%BA%90%E4%BB%A3%E7%A0%81%E7%AC%94%E8%AE%B0-%E5%90%AF%E5%8A%A8%E6%AC%A2%E8%BF%8E%E7%95%8C%E9%9D%A2-SplashScreen-%E7%9A%84%E5%8E%9F%E7%90%86
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系。