背景

在开发 macOS 上的窗口共享过程中,发现 demo 被其它应用覆盖后,在25s左右,观众收到的窗口画面开始变得卡顿。

经过排查发现问题出在主播端的采集逻辑处。在我的上一篇文章 macOS 屏幕共享开发 提到,窗口共享的实现跟屏幕共享不一样,窗口共享没有现有的系统接口,需要启动一个 Timer 去定时对窗口截图,再对截图进行处理。

188bf0d5672f682c730e8d524608379a

通过打印日志发现,demo 进入后台后,Timer 在25s左右,回调开始变慢,大约2s才回调一次,问题的原因就出在这里。

排查原因

为了避免其它逻辑影响了结果,新建一个干净的 demo。

我在窗口共享中用的是 GCD Timer,一开始猜测是 GCD Timer 的某些参数没有设置,查看文档后没找到相关的参数;然后猜测 GCD Timer 的问题,但是换成 NSTimer 后,发现也有一样的问题,这时候我开始猜测是系统导致的问题了。

开始查阅资料,发现了 一篇文章 里面提到:

App Nap 是 OS X 10.9 Mavericks 的一项新功能。它能帮你在同时运行多个应用程序时节省电能。

这时候有了切入点,查询关键字 App Nap 就可以发现相关资料。

App Nap

附上苹果官方说明文档:Extend App Nap

If an app isn’t performing user-initiated work such as updating content on screen, playing music, or downloading a file, the system may put the app in App Nap. App Nap conserves battery life by regulating the app’s CPU usage and by reducing the frequency with which its timers are fired.

官方说明文档介绍很清楚了,包括 App Nap 工作的条件、表现等,我们要处理的就是在启动 Timer 时,暂时把 App Nap 功能关掉。

解决方案

在开启 Timer 前加入以下代码:

// 避免 macOS 的 App Nap 功能导致在后台时 timer 停止运行。
NSActivityOptions option = NSActivityBackground;
self.processActivity = [[NSProcessInfo processInfo] beginActivityWithOptions:option reason:@"process.activity"];

关闭 Timer 后把 Activity 停掉:

if (self.processActivity) {
    [[NSProcessInfo processInfo] endActivity:self.processActivity];
    self.processActivity = nil;
}

这时候查看 Timer 的打印,发现25s后的回调都已经正常了。