通话接听页使用PowerManager.WakeLock引发内存泄漏问题排查
问题描述
我在实现通话接听界面时,通过**距离传感器(Proximity Sensor)**和PowerManager.WakeLock实现手机贴近面部时熄屏的功能。功能可以正常工作,但存在内存泄漏问题——即使Fragment代码很简洁时就检测到泄漏,排查后确定根源是PowerManager.WakeLock。我已经在Fragment的onPause、onDestroyView、onDestroy等生命周期节点尝试释放锁,但泄漏依然存在,同时日志中出现WakeLock finalized while still held错误。
内存泄漏分析截图:

当前Fragment实现代码:
override val sensorManager: SensorManager get() = requireContext().getSystemService(Context.SENSOR_SERVICE) as SensorManager override var proximitySensor: Sensor? = null override val powerManager: PowerManager = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager override val lock: PowerManager.WakeLock = powerManager.newWakeLock( PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, PROXIMITY_WAKE_LOG_TAG ) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) activity?.onBackPressedDispatcher?.addCallback { activity?.finishAndRemoveTask() } } override fun onResume() { super.onResume() proximitySensor?.let { proximity -> sensorManager.apply { registerListener( this@AnswerFragment, proximity, SensorManager.SENSOR_DELAY_NORMAL ) } } } override fun onPause() { super.onPause() sensorManager.unregisterListener(this) if (lock.isHeld) { lock.release() } } override fun onDestroyView() { super.onDestroyView() proximitySensor = null if (lock.isHeld) { lock.release() } _binding = null } override fun onDestroy() { if (lock.isHeld) { lock.release() } super.onDestroy() } override fun onSensorChanged(event: SensorEvent?) { if (event?.values?.get(0) == 0.0f) { // 贴近面部,熄屏 lock.acquire() } else { // 远离面部,亮屏 if (lock.isHeld) { lock.release() } } } override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
问题根源
- WakeLock引用计数不匹配:默认情况下
WakeLock带引用计数,每次acquire()计数加1,release()计数减1。如果传感器频繁触发贴近动作(比如面部来回移动),会多次调用acquire(),但你释放时仅调用一次release(),最终锁仍处于持有状态,GC回收时就会触发WakeLock finalized while still held错误,导致Fragment被锁引用无法回收,引发内存泄漏。 - WakeLock实例未及时置空:你将
lock声明为Fragment的成员属性并提前初始化,即使释放了锁,只要引用存在,Fragment就无法被彻底回收。 - 传感器回调逻辑有漏洞:未判断锁是否已持有就直接调用
acquire(),导致重复持有锁。
修复方案
调整初始化时机、彻底释放锁并清空引用,修复后的代码如下:
// 改为可空私有变量,避免提前初始化 private var sensorManager: SensorManager? = null private var proximitySensor: Sensor? = null private var powerManager: PowerManager? = null private var lock: PowerManager.WakeLock? = null private val PROXIMITY_WAKE_LOG_TAG = "ProximityWakeLock" override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 延迟初始化服务和锁,与Fragment生命周期绑定 sensorManager = requireContext().getSystemService(Context.SENSOR_SERVICE) as SensorManager powerManager = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager proximitySensor = sensorManager?.getDefaultSensor(Sensor.TYPE_PROXIMITY) powerManager?.let { pm -> lock = pm.newWakeLock( PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, PROXIMITY_WAKE_LOG_TAG ) } activity?.onBackPressedDispatcher?.addCallback { activity?.finishAndRemoveTask() } } override fun onResume() { super.onResume() proximitySensor?.let { proximity -> sensorManager?.registerListener( this@AnswerFragment, proximity, SensorManager.SENSOR_DELAY_NORMAL ) } } override fun onPause() { super.onPause() sensorManager?.unregisterListener(this) // 彻底释放锁 releaseWakeLock() } override fun onDestroyView() { super.onDestroyView() proximitySensor = null releaseWakeLock() _binding = null } override fun onDestroy() { releaseWakeLock() // 清空所有引用,让GC能正常回收Fragment sensorManager = null powerManager = null lock = null super.onDestroy() } override fun onSensorChanged(event: SensorEvent?) { val currentLock = lock ?: return if (event?.values?.get(0) == 0.0f) { // 仅当锁未持有时才获取,避免重复计数 if (!currentLock.isHeld) { currentLock.acquire() } } else { // 远离时直接彻底释放锁 releaseWakeLock() } } override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} /** * 循环释放直到锁不再持有,处理多次acquire的情况 */ private fun releaseWakeLock() { lock?.let { wakeLock -> while (wakeLock.isHeld) { wakeLock.release() } } }
额外提示
PROXIMITY_SCREEN_OFF_WAKE_LOCK是系统特殊锁,理论上传感器检测到远离会自动释放,但手动处理更稳妥,可避免异常场景。- 修改完成后,建议用LeakCanary等工具再次检测,确认内存泄漏问题已解决。
内容的提问来源于stack exchange,提问作者JV17
相关产品推荐
相关产品推荐

