记小幻影视守护程序
背景 - WinUI 滚动控件的选择
小幻影视主要的使用场景是连接到 Emby/Jellyfin,提供符合 Microsoft Fluent Design 视觉语言的海报墙。
那么毋庸置疑,这是一个图片密集型的应用,在应用启动后,有大量的图片加载,滚动时也不断有新的图片被创建,或者旧的图片被销毁。

按照我过去的开发习惯,我更倾向于使用 ScrollViewer + ItemsRepeater 的方式,它相对 GridView 更轻量,同时也支持复杂的布局嵌套。
但在小幻影视推出后很快遇到了问题,主要是下面两个:
-
边滚动边加载图片时会阻塞UI,使滚动卡顿,且容易闪退。
-
在媒体详情页偶尔会无法抓到滚动事件,使遮罩位置没有及时更新。
这两个问题我无法解决,只能考虑更换 ScrollViewer。事实上 WinUI 正在推他们的新控件 ScrollView,而经过测试,ScrollView 的确可以解决这两个问题。
ScrollView 的问题
ScrollView 在刚推出的时候我就尝试过,它给我的最大印象就是 慢。
它的滚动速度非常慢,这是我此前不考虑它的主要原因。
这次遇到了功能性的问题,迫不得已需要用它的时候,我查看了一下它的源码,找到了原因。
新的 ScrollView 控件基于 InteractionTracker,它固然有一定的优势,但它在处理滚动的时候选取的基准值有很大问题。
它是以绝对的像素值作为基础滚动单位的。
我们在鼠标滚轮滚动时,并不是按像素滚动,而是以行为滚动单位。即滑动一下滚轮滚动几行(你可以在系统设置里找到相关设置)。
InteractionTracker假定了一行为40px,通常设备的初始滚动行数为3行,也就是说滚动一下滚轮,InteractionTracker会将视图移动120px。
像素大小是绝对的,这意味着,分辨率越高的屏幕(纵向或横向的像素密度越大),单次滚动产生的位移在视觉上的距离就越短。
我手上是两台5K屏幕,所以 ScrollView 的滚动速度在我看来慢得发指,一张图片我可能要滚动十几下才能让它从底部滚动到顶部。
所以这就成了我必须要解决的问题。
滚动加速
ScrollView 或 InteractionTracker 没有提供任何 API 来修改单次滚动距离(它只能调整惯性滚动的速度),我在多番尝试之后,发现只有系统的滚动行数设置能够对它产生影响。

在我将滚动行数调整为 12 时,ScrollView 的滚动速度终于可接受了。
但这显然是一个会影响到系统所有应用的设置,一旦改了,小幻影视的滚动速度正常,但其他APP的滚动速度就会太快了,轻轻滚动一下就翻了半页内容,这个显然会极大影响用户体验。
但是我也没有其他办法了,只能通过在合适的时机调整系统的滚动行数设置来间接影响 ScrollView。
加速失败
想想也知道,合适的时机哪是那么容易找的。
最简单的,应用打开时加速滚动,关闭时停止滚动。
打开时加速好说,但关闭应用的时候,存在一定概率调用 API 失败,而应用快速退出,失去了修改系统设置的机会。
再者,用户不可能只开着小幻影视而不看其他应用,那么加一条,应用失去焦点的时候就停止加速。
大部分情况都可以了,但还是会有例外(WinUI3 啊……)
就这样磕磕绊绊地发了,但滚动加速和降速的设置失败依然还会随机性地出现,一旦出现了,大部分用户都会一脸懵逼,搞得体验不佳。
守护程序
偶然的机会,我突然想到,如果在应用内部处理各种情况比较复杂,那何不跳出应用外呢?
我应该在应用启动的时候单独开一个进程,这个进程只干一件事:维护小幻影视的滚动加速状态。
它会随着应用启动而启动,启动后定时触发以下逻辑:
-
检查到 RodelPlayer.UI(小幻影视主界面进程名称)所属窗口获得焦点,开始加速滚动
-
如果窗口失焦/鼠标不在窗口内/进程退出,则恢复初始滚动速度
这样逻辑就很简单了,不用在小幻影视内做侵入式代码修改,不用监听一堆稀奇古怪的事件,不用依赖于 WinUI 本身不可靠的通知机制,外部只需要对基础条件进行判断即可。
当这件事做完,我发现它的效果要比程序内检测好太多了,它很稳定,不用担心滚动加速没有生效了。
在创建了这个守护程序之后,我发现它其实很好用(这种后台子程序应该在桌面开发领域并不罕见,我很遗憾现在才想到它)。
我在近期收到小幻影视没有成功重启的报告(在一个用户设备上稳定复现),这在之前是束手无策的,我只能通过 AppInstance.Restart 这个 API 将重启请求委托给 WinAppSDK(或者系统?) 来执行,而一旦失败,我就无法再拉起应用了。在用户眼里,这就是应用闪退。
面对这种情况,一个超出应用生命周期的 守护程序 就显得非常有必要了,它能作为一个很好的兜底。
我依然保留了标准 WinUI3 的重启机制,但在重启前,我会先通知守护程序,让它在5秒后查一下有没有小幻影视的进程,如果没有(大概率重启失败了),那就通过协议启动拉起APP。