Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ISSUE-004

CPU占用异常偏高

常见表现

CPU占用异常偏高,电脑发热厉害。

此问题通常会在Unity游戏和使用了重力感应功能(摇一摇)的App中出现。

相关源码

UIKitMacHelper.framework

Mac运行iOS应用时,CMMotionManager的底层实现为UINSVirtualMotionDevice

void -[UINSVirtualMotionDevice startAccelerometerUpdates]
{
	if (!_accelerometerDispatchIsActive)
	{
	    dispatch_resume(_accelerometerDispatchSource);
	    _accelerometerDispatchIsActive = YES;
	}
}

id -[UINSVirtualMotionDevice accelerometerData]
{
	if (!_accelerometerDispatchIsActive)
	{
		dispatch_resume(_accelerometerDispatchSource);
		_accelerometerDispatchIsActive = YES;
	}
	return _accelerometerData;
}

Unity引擎 iPhone_Sensors.mm

extern "C" void UnityCoreMotionStart()
{
    if (sMotionManager.accelerometerAvailable)
    {
        const int frequency = UnityGetAccelerometerFrequency();
        if (frequency > 0)
        {
            sMotionManager.accelerometerUpdateInterval = 1.0f / frequency;
            [sMotionManager startAccelerometerUpdates];
        }
    }
}

extern "C" void UnityUpdateAccelerometerData()
{
	CMAccelerometerData* data = sMotionManager.accelerometerData;
	if (data != nil)
	{
		Vector3f res = UnityReorientVector3(data.acceleration.x, data.acceleration.y, data.acceleration.z);
		UnityDidAccelerate(res.x, res.y, res.z, data.timestamp);
	}
}

原因分析

第一个问题:CMMotionManager的默认更新间隔为0秒,如果不修改更新间隔,直接调用startAccelerometerUpdates就会导致频繁更新,从而占用大量CPU时间。

第二个问题:访问CMMotionManageraccelerometerData数据时,会自动触发startAccelerometerUpdates,也就是说这个接口有副作用。

而Unity引擎在某种情况下,实例化了CMMotionManager,没有修改更新间隔,也没有主动调用startAccelerometerUpdates

但是因为之后读取了accelerometerData,间接触发了startAccelerometerUpdates,从而导致更新间隔0秒的频繁更新。

解决方法

玩家方面:使用PlayCover Nightly版,右键游戏图标 - 设置 - 杂项设置,启用“limitMotionUpdateFrequency”选项。

开发者方面:调整默认的更新间隔,避免使用0秒间隔。

extern "C" void UnityCoreMotionStart()
{
	//...
	bool initMotionManager = (sMotionManager == nil);
    if (initMotionManager) {
        sMotionManager = [[CMMotionManager alloc] init];
        sMotionManager.accelerometerUpdateInterval = 0.01;
        sMotionManager.deviceMotionUpdateInterval = 0.01;
        sMotionManager.gyroUpdateInterval = 0.01;
    }
    //...
}