diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b846e26..6372718 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + Unit, val setBatchSize: (Int) -> Unit, val setScreenOffRecordEnabled: (Boolean) -> Unit, + val setPreciseScreenOffRecordEnabled: (Boolean) -> Unit, val setAlwaysPollingScreenStatusEnabled: (Boolean) -> Unit, val setSegmentDurationMin: (Long) -> Unit, val setRootBootAutoStartEnabled: (Boolean) -> Unit diff --git a/app/src/main/java/yangfentuozi/batteryrecorder/ui/model/SettingsUiState.kt b/app/src/main/java/yangfentuozi/batteryrecorder/ui/model/SettingsUiState.kt index e765bea..b9ef740 100644 --- a/app/src/main/java/yangfentuozi/batteryrecorder/ui/model/SettingsUiState.kt +++ b/app/src/main/java/yangfentuozi/batteryrecorder/ui/model/SettingsUiState.kt @@ -49,6 +49,8 @@ data class SettingsUiState( val batchSize: Int = ServerSettings().batchSize, /** 息屏记录 */ val recordScreenOffEnabled: Boolean = ServerSettings().screenOffRecordEnabled, + /** 精确息屏记录 */ + val preciseScreenOffRecordEnabled: Boolean = ServerSettings().preciseScreenOffRecordEnabled, /** 轮询获取息屏状态 */ val alwaysPollingScreenStatusEnabled: Boolean = ServerSettings().alwaysPollingScreenStatusEnabled, /** 自动分段时间 */ diff --git a/app/src/main/java/yangfentuozi/batteryrecorder/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/yangfentuozi/batteryrecorder/ui/screens/settings/SettingsScreen.kt index 29f369f..c320236 100644 --- a/app/src/main/java/yangfentuozi/batteryrecorder/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/yangfentuozi/batteryrecorder/ui/screens/settings/SettingsScreen.kt @@ -67,6 +67,7 @@ fun SettingsScreen( setWriteLatencyMs = settingsViewModel::setWriteLatencyMs, setBatchSize = settingsViewModel::setBatchSize, setScreenOffRecordEnabled = settingsViewModel::setScreenOffRecordEnabled, + setPreciseScreenOffRecordEnabled = settingsViewModel::setPreciseScreenOffRecordEnabled, setAlwaysPollingScreenStatusEnabled = settingsViewModel::setAlwaysPollingScreenStatusEnabled, setSegmentDurationMin = settingsViewModel::setSegmentDurationMin, setRootBootAutoStartEnabled = settingsViewModel::setRootBootAutoStartEnabled @@ -101,6 +102,7 @@ fun SettingsScreen( writeLatencyMs = serverSettings.writeLatencyMs, batchSize = serverSettings.batchSize, recordScreenOffEnabled = serverSettings.screenOffRecordEnabled, + preciseScreenOffRecordEnabled = serverSettings.preciseScreenOffRecordEnabled, alwaysPollingScreenStatusEnabled = serverSettings.alwaysPollingScreenStatusEnabled, segmentDurationMin = serverSettings.segmentDurationMin, rootBootAutoStartEnabled = appSettings.rootBootAutoStartEnabled, diff --git a/app/src/main/java/yangfentuozi/batteryrecorder/ui/viewmodel/SettingsViewModel.kt b/app/src/main/java/yangfentuozi/batteryrecorder/ui/viewmodel/SettingsViewModel.kt index dbef6d6..74b953b 100644 --- a/app/src/main/java/yangfentuozi/batteryrecorder/ui/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/yangfentuozi/batteryrecorder/ui/viewmodel/SettingsViewModel.kt @@ -122,7 +122,7 @@ class SettingsViewModel : ViewModel() { LoggerX.d( TAG, - "[设置] loadSettings 完成: notification=${currentServerSettings.notificationEnabled} compatMode=${currentServerSettings.notificationCompatModeEnabled} dualCell=${currentServerSettings.dualCellEnabled} calibration=${currentServerSettings.calibrationValue} intervalMs=${currentServerSettings.recordIntervalMs} writeLatencyMs=${currentServerSettings.writeLatencyMs} batchSize=${currentServerSettings.batchSize} screenOffRecord=${currentServerSettings.screenOffRecordEnabled} polling=${currentServerSettings.alwaysPollingScreenStatusEnabled} logLevel=${currentServerSettings.logLevel}" + "[设置] loadSettings 完成: notification=${currentServerSettings.notificationEnabled} compatMode=${currentServerSettings.notificationCompatModeEnabled} dualCell=${currentServerSettings.dualCellEnabled} calibration=${currentServerSettings.calibrationValue} intervalMs=${currentServerSettings.recordIntervalMs} writeLatencyMs=${currentServerSettings.writeLatencyMs} batchSize=${currentServerSettings.batchSize} screenOffRecord=${currentServerSettings.screenOffRecordEnabled} preciseScreenOffRecord=${currentServerSettings.preciseScreenOffRecordEnabled} polling=${currentServerSettings.alwaysPollingScreenStatusEnabled} logLevel=${currentServerSettings.logLevel}" ) } @@ -236,6 +236,20 @@ class SettingsViewModel : ViewModel() { } } + /** + * 更新精确息屏记录开关并下发到运行中的服务端。 + * + * @param enabled `true` 表示允许 Server 在息屏记录阶段持有唤醒锁;`false` 表示恢复默认自然息屏采样。 + * @return 无。 + */ + fun setPreciseScreenOffRecordEnabled(enabled: Boolean) { + updateServerSettings( + message = "[设置] 更新精确息屏记录并准备下发: enabled=$enabled" + ) { current -> + current.copy(preciseScreenOffRecordEnabled = enabled) + } + } + fun setAlwaysPollingScreenStatusEnabled(enabled: Boolean) { updateServerSettings( message = "[设置] 更新轮询亮屏状态并准备下发: enabled=$enabled" diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8930efb..f257e48 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -95,6 +95,8 @@ 实时功率通知 通知兼容模式 息屏记录 + 精确息屏记录 + 可能导致息屏耗电增高 轮询获取息屏状态 采样间隔 写入延迟 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d2ce007..d248f2d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -95,6 +95,8 @@ Realtime power notification Notification compatibility mode Record when screen is off + Precise screen-off recording + May increase screen-off power draw. Poll screen state Sampling interval Write latency diff --git a/server/src/main/java/yangfentuozi/batteryrecorder/server/Server.kt b/server/src/main/java/yangfentuozi/batteryrecorder/server/Server.kt index be8fcf6..2d540d0 100644 --- a/server/src/main/java/yangfentuozi/batteryrecorder/server/Server.kt +++ b/server/src/main/java/yangfentuozi/batteryrecorder/server/Server.kt @@ -82,31 +82,43 @@ class Server internal constructor() : IService.Stub() { monitor.unregisterRecordListener(listener) } - override fun updateConfig(settings: ServerSettings) { - Handlers.common.post { - LoggerX.d( - tag, - "updateConfig: 应用配置, notification=${settings.notificationEnabled} compatMode=${settings.notificationCompatModeEnabled} dualCell=${settings.dualCellEnabled} calibration=${settings.calibrationValue} intervalMs=${settings.recordIntervalMs} writeLatencyMs=${settings.writeLatencyMs} batchSize=${settings.batchSize} screenOffRecord=${settings.screenOffRecordEnabled} segmentDurationMin=${settings.segmentDurationMin} logLevel=${settings.logLevel} polling=${settings.alwaysPollingScreenStatusEnabled}" - ) - LoggerX.maxHistoryDays = settings.maxHistoryDays - LoggerX.logLevel = settings.logLevel + /** + * 应用一份服务端配置到当前运行时实例。 + * + * @param settings 要生效的服务端配置。 + * @param source 本次配置应用来源,仅用于日志定位。 + * @return 无。 + */ + private fun applyConfigInternal(settings: ServerSettings, source: String) { + LoggerX.d( + tag, + "$source: 应用配置, notification=${settings.notificationEnabled} compatMode=${settings.notificationCompatModeEnabled} dualCell=${settings.dualCellEnabled} calibration=${settings.calibrationValue} intervalMs=${settings.recordIntervalMs} writeLatencyMs=${settings.writeLatencyMs} batchSize=${settings.batchSize} screenOffRecord=${settings.screenOffRecordEnabled} preciseScreenOffRecord=${settings.preciseScreenOffRecordEnabled} segmentDurationMin=${settings.segmentDurationMin} logLevel=${settings.logLevel} polling=${settings.alwaysPollingScreenStatusEnabled}" + ) + LoggerX.maxHistoryDays = settings.maxHistoryDays + LoggerX.logLevel = settings.logLevel - unlockOPlusSampleTimeLimit(settings.recordIntervalMs.coerceAtLeast(200)) + unlockOPlusSampleTimeLimit(settings.recordIntervalMs.coerceAtLeast(200)) - monitor.notificationPowerMultiplier = computeNotificationPowerMultiplier( - dualCellEnabled = settings.dualCellEnabled, - calibrationValue = settings.calibrationValue, - ) - monitor.setNotificationCompatModeEnabled(settings.notificationCompatModeEnabled) - monitor.setNotificationEnabled(settings.notificationEnabled) - monitor.alwaysPollingScreenStatusEnabled = settings.alwaysPollingScreenStatusEnabled - monitor.recordIntervalMs = settings.recordIntervalMs - monitor.screenOffRecord = settings.screenOffRecordEnabled - monitor.notifyLock() - - writer.flushIntervalMs = settings.writeLatencyMs - writer.batchSize = settings.batchSize - writer.maxSegmentDurationMs = settings.segmentDurationMin * 60 * 1000L + monitor.notificationPowerMultiplier = computeNotificationPowerMultiplier( + dualCellEnabled = settings.dualCellEnabled, + calibrationValue = settings.calibrationValue, + ) + monitor.setNotificationCompatModeEnabled(settings.notificationCompatModeEnabled) + monitor.setNotificationEnabled(settings.notificationEnabled) + monitor.alwaysPollingScreenStatusEnabled = settings.alwaysPollingScreenStatusEnabled + monitor.recordIntervalMs = settings.recordIntervalMs + monitor.screenOffRecord = settings.screenOffRecordEnabled + monitor.preciseScreenOffRecordEnabled = settings.preciseScreenOffRecordEnabled + monitor.notifyLock() + + writer.flushIntervalMs = settings.writeLatencyMs + writer.batchSize = settings.batchSize + writer.maxSegmentDurationMs = settings.segmentDurationMin * 60 * 1000L + } + + override fun updateConfig(settings: ServerSettings) { + Handlers.common.post { + applyConfigInternal(settings, "updateConfig") } } @@ -473,7 +485,8 @@ class Server internal constructor() : IService.Stub() { LoggerX.i(tag, "init: 通过 ConfigProvider 读取配置") ConfigUtil.getServerSettingsByContentProvider() } - serverSettings?.let(::updateConfig) ?: LoggerX.w(tag, "init: 未读取到配置, 使用当前默认值") + serverSettings?.let { applyConfigInternal(it, "init") } + ?: LoggerX.w(tag, "init: 未读取到配置, 使用当前默认值") monitor.start() LoggerX.i(tag, "init: Monitor 已启动, 进入消息循环") diff --git a/server/src/main/java/yangfentuozi/batteryrecorder/server/recorder/Monitor.kt b/server/src/main/java/yangfentuozi/batteryrecorder/server/recorder/Monitor.kt index ebf507f..2db8e1c 100644 --- a/server/src/main/java/yangfentuozi/batteryrecorder/server/recorder/Monitor.kt +++ b/server/src/main/java/yangfentuozi/batteryrecorder/server/recorder/Monitor.kt @@ -9,11 +9,13 @@ import android.hardware.display.IDisplayManager import android.hardware.display.IDisplayManagerCallback import android.os.Handler import android.os.IPowerManager +import android.os.PowerManager import android.os.RemoteCallbackList import android.os.RemoteException import android.os.ServiceManager import android.system.Os import androidx.annotation.Keep +import yangfentuozi.batteryrecorder.server.fakecontext.FakeContext import yangfentuozi.batteryrecorder.server.notification.LocalNotificationUtil import yangfentuozi.batteryrecorder.server.notification.NotificationInfo import yangfentuozi.batteryrecorder.server.notification.NotificationUtil @@ -68,6 +70,26 @@ class Monitor( @Volatile var screenOffRecord: Boolean = SettingsConstants.screenOffRecordEnabled.def + set(value) { + val oldValue = field + if (oldValue == value) return + field = value + LoggerX.d(tag, "screenOffRecord: 配置变更, $oldValue -> $value") + updatePreciseScreenOffWakeLockState() + } + + @Volatile + var preciseScreenOffRecordEnabled: Boolean = SettingsConstants.preciseScreenOffRecordEnabled.def + set(value) { + val oldValue = field + if (oldValue == value) return + field = value + LoggerX.d(tag, "preciseScreenOffRecordEnabled: 配置变更, $oldValue -> $value") + if (!oldValue && value) { + preparePreciseScreenOffWakeLock() + } + updatePreciseScreenOffWakeLockState() + } @Volatile var notificationUtil: NotificationUtil? = null @@ -107,6 +129,10 @@ class Monitor( @Volatile private var stopped = false + private var preciseScreenOffWakeLock: PowerManager.WakeLock? = null + @Volatile + private var preciseScreenOffWakeLockDisabledForCurrentServer = false + private var lastPreciseScreenOffWakeLockDecisionReason: String? = null private val lock = ReentrantLock() private val condition = lock.newCondition() private val callbackHandler: Handler @@ -127,6 +153,7 @@ class Monitor( LoggerX.d(tag, "@thread: 亮屏状态变化, $oldIsInteractive -> $latestIsInteractive") } isInteractive = latestIsInteractive + updatePreciseScreenOffWakeLockState() } val record = LineRecord( timestamp, @@ -195,6 +222,7 @@ class Monitor( while (!stopped && !screenOffRecord && !isInteractive && alwaysPollingScreenStatusEnabled) { condition.await(recordIntervalMs, TimeUnit.MILLISECONDS) isInteractive = iPowerManager.isInteractive + updatePreciseScreenOffWakeLockState() } } else { LoggerX.d(tag, "@thread: 暂停采样, 等待亮屏事件") @@ -208,8 +236,11 @@ class Monitor( fun start() { LoggerX.d(tag, - "start: alwaysPollingScreenStatusEnabled=$alwaysPollingScreenStatusEnabled screenOffRecord=$screenOffRecord" + "start: alwaysPollingScreenStatusEnabled=$alwaysPollingScreenStatusEnabled screenOffRecord=$screenOffRecord preciseScreenOffRecordEnabled=$preciseScreenOffRecordEnabled" ) + if (preciseScreenOffRecordEnabled) { + preparePreciseScreenOffWakeLock() + } try { iActivityTaskManager.registerTaskStackListener(taskStackListener) if (!alwaysPollingScreenStatusEnabled) { @@ -226,10 +257,24 @@ class Monitor( throw RuntimeException("start: 获取当前焦点任务信息失败", e) } isInteractive = iPowerManager.isInteractive + updatePreciseScreenOffWakeLockState() LoggerX.d(tag, "start: initial isInteractive=$isInteractive") thread.start() } + /** + * 在非 Binder 回调线程预创建精确息屏记录的唤醒锁实例,避免首次息屏回调落在 Binder 线程时 + * 触发 `FakeContext.systemContext` 的惰性初始化,进而命中无 Looper 线程创建 ActivityThread 的崩溃。 + * + * @return 无;预创建失败时会在当前 Server 生命周期内静默禁用该功能。 + */ + private fun preparePreciseScreenOffWakeLock() { + val wakeLock = requirePreciseScreenOffWakeLock() + if (wakeLock != null) { + LoggerX.d(tag, "preparePreciseScreenOffWakeLock: 唤醒锁实例预创建完成") + } + } + private fun registerDisplayEventCallback() { if (displayCallbackRegistered) { LoggerX.v(tag, "registerDisplayEventCallback: 已注册, skip") @@ -246,6 +291,7 @@ class Monitor( val oldIsInteractive = isInteractive val latestIsInteractive = iPowerManager.isInteractive isInteractive = latestIsInteractive + updatePreciseScreenOffWakeLockState() LoggerX.d(tag, "onDisplayEvent: displayId=$displayId event=$event interactive $oldIsInteractive -> $latestIsInteractive paused=$paused" ) @@ -271,6 +317,7 @@ class Monitor( fun stop() { stopped = true notifyLock() + releasePreciseScreenOffWakeLockIfHeld("服务停止") try { iActivityTaskManager.unregisterTaskStackListener(taskStackListener) } catch (e: RemoteException) { @@ -355,8 +402,141 @@ class Monitor( } } + /** + * 按当前屏幕状态与配置同步精确息屏记录的唤醒锁状态。 + * + * 这里只在“开启精确息屏记录 + 开启息屏记录 + 当前确实处于息屏”时持锁, + * 避免把亮屏阶段也变成常驻保活,额外抬高非目标场景功耗。 + * + * @return 无;状态不满足时会主动释放已持有的唤醒锁。 + */ + @Synchronized + private fun updatePreciseScreenOffWakeLockState() { + val shouldHoldWakeLock = + preciseScreenOffRecordEnabled && + !preciseScreenOffWakeLockDisabledForCurrentServer && + screenOffRecord && + !isInteractive && + !stopped + val currentReason = when { + stopped -> "服务停止" + preciseScreenOffWakeLockDisabledForCurrentServer -> "当前服务已禁用精确息屏记录" + isInteractive -> "屏幕亮起" + !screenOffRecord -> "息屏记录关闭" + !preciseScreenOffRecordEnabled -> "精确息屏记录关闭" + else -> "满足持锁条件" + } + + val wakeLock = preciseScreenOffWakeLock + if (shouldHoldWakeLock) { + if (wakeLock == null) { + LoggerX.e(tag, "updatePreciseScreenOffWakeLockState: 满足持锁条件但唤醒锁尚未初始化") + lastPreciseScreenOffWakeLockDecisionReason = "唤醒锁未初始化" + return + } + if (!wakeLock.isHeld) { + LoggerX.d( + tag, + "updatePreciseScreenOffWakeLockState: 准备持有唤醒锁, preciseScreenOffRecordEnabled=$preciseScreenOffRecordEnabled screenOffRecord=$screenOffRecord isInteractive=$isInteractive stopped=$stopped" + ) + wakeLock.acquire() + if (wakeLock.isHeld) { + LoggerX.i(tag, "updatePreciseScreenOffWakeLockState: 已持有唤醒锁") + } else { + LoggerX.w(tag, "updatePreciseScreenOffWakeLockState: acquire() 返回后仍未持有唤醒锁") + } + } else { + LoggerX.d(tag, "updatePreciseScreenOffWakeLockState: 唤醒锁已处于持有状态,跳过重复 acquire") + } + lastPreciseScreenOffWakeLockDecisionReason = currentReason + return + } + + if (wakeLock?.isHeld == true) { + releasePreciseScreenOffWakeLockIfHeld(currentReason) + lastPreciseScreenOffWakeLockDecisionReason = currentReason + return + } + if (lastPreciseScreenOffWakeLockDecisionReason != currentReason) { + LoggerX.d( + tag, + "updatePreciseScreenOffWakeLockState: 当前不持有唤醒锁, reason=$currentReason preciseScreenOffRecordEnabled=$preciseScreenOffRecordEnabled screenOffRecord=$screenOffRecord isInteractive=$isInteractive stopped=$stopped" + ) + lastPreciseScreenOffWakeLockDecisionReason = currentReason + } + } + + /** + * 释放当前已持有的精确息屏记录唤醒锁。 + * + * @param reason 本次释放的直接原因,用于日志定位。 + * @return 无;若尚未创建或当前未持锁则直接返回。 + */ + @Synchronized + private fun releasePreciseScreenOffWakeLockIfHeld(reason: String) { + val wakeLock = preciseScreenOffWakeLock ?: return + if (!wakeLock.isHeld) return + LoggerX.d(tag, "releasePreciseScreenOffWakeLockIfHeld: 准备释放唤醒锁, reason=$reason") + wakeLock.release() + if (!wakeLock.isHeld) { + LoggerX.i(tag, "releasePreciseScreenOffWakeLockIfHeld: 已释放唤醒锁, reason=$reason") + } else { + LoggerX.w(tag, "releasePreciseScreenOffWakeLockIfHeld: release() 返回后仍处于持有状态, reason=$reason") + } + } + + /** + * 获取精确息屏记录使用的部分唤醒锁实例。 + * + * 初始化失败时仅记录错误,并在当前 Server 生命周期内静默禁用该功能,避免在回调线程反复抛异常。 + * + * @return 返回单例 `PARTIAL_WAKE_LOCK`;当前服务已禁用该能力时返回 `null`。 + */ + private fun requirePreciseScreenOffWakeLock(): PowerManager.WakeLock? { + if (preciseScreenOffWakeLockDisabledForCurrentServer) { + LoggerX.d(tag, "requirePreciseScreenOffWakeLock: 当前服务已禁用精确息屏记录,跳过唤醒锁初始化") + return null + } + preciseScreenOffWakeLock?.let { + LoggerX.d( + tag, + "requirePreciseScreenOffWakeLock: 复用已有唤醒锁实例, held=${it.isHeld}" + ) + return it + } + try { + LoggerX.d(tag, "requirePreciseScreenOffWakeLock: 开始创建唤醒锁实例") + LoggerX.d(tag, "requirePreciseScreenOffWakeLock: 开始获取 systemContext") + val systemContext = FakeContext.systemContext + LoggerX.d(tag, "requirePreciseScreenOffWakeLock: 获取 systemContext 成功, context=${systemContext.javaClass.name}") + LoggerX.d(tag, "requirePreciseScreenOffWakeLock: 开始获取 PowerManager") + val powerManager = systemContext.getSystemService(PowerManager::class.java) + ?: throw IllegalStateException("获取 PowerManager 失败") + LoggerX.d(tag, "requirePreciseScreenOffWakeLock: 获取 PowerManager 成功, service=${powerManager.javaClass.name}") + LoggerX.d(tag, "requirePreciseScreenOffWakeLock: 开始调用 newWakeLock, tag=$PRECISE_SCREEN_OFF_WAKE_LOCK_TAG") + return powerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + PRECISE_SCREEN_OFF_WAKE_LOCK_TAG + ).apply { + setReferenceCounted(false) + preciseScreenOffWakeLock = this + LoggerX.d( + tag, + "requirePreciseScreenOffWakeLock: 唤醒锁实例创建完成, tag=$PRECISE_SCREEN_OFF_WAKE_LOCK_TAG held=$isHeld" + ) + } + } catch (t: Throwable) { + LoggerX.e(tag, "requirePreciseScreenOffWakeLock: 创建唤醒锁失败", tr = t) + preciseScreenOffWakeLockDisabledForCurrentServer = true + preciseScreenOffWakeLock = null + LoggerX.w(tag, "requirePreciseScreenOffWakeLock: 当前服务已静默禁用精确息屏记录") + return null + } + } + companion object { private const val POWER_SCALE_DIVISOR = 1_000_000_000_000.0 + private const val PRECISE_SCREEN_OFF_WAKE_LOCK_TAG = "BatteryRecorder:PreciseScreenOffRecord" fun computeNotificationPowerMultiplier( dualCellEnabled: Boolean, diff --git a/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/ConfigUtil.kt b/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/ConfigUtil.kt index 1a1d54d..eff70f2 100644 --- a/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/ConfigUtil.kt +++ b/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/ConfigUtil.kt @@ -116,7 +116,7 @@ object ConfigUtil { private fun logServerSettings(source: String, settings: ServerSettings) { LoggerX.d( TAG, - "$source: notification=${settings.notificationEnabled} compatMode=${settings.notificationCompatModeEnabled} dualCell=${settings.dualCellEnabled} calibration=${settings.calibrationValue} intervalMs=${settings.recordIntervalMs} batchSize=${settings.batchSize} writeLatencyMs=${settings.writeLatencyMs} screenOffRecord=${settings.screenOffRecordEnabled} polling=${settings.alwaysPollingScreenStatusEnabled} logLevel=${settings.logLevel}" + "$source: notification=${settings.notificationEnabled} compatMode=${settings.notificationCompatModeEnabled} dualCell=${settings.dualCellEnabled} calibration=${settings.calibrationValue} intervalMs=${settings.recordIntervalMs} batchSize=${settings.batchSize} writeLatencyMs=${settings.writeLatencyMs} screenOffRecord=${settings.screenOffRecordEnabled} preciseScreenOffRecord=${settings.preciseScreenOffRecordEnabled} polling=${settings.alwaysPollingScreenStatusEnabled} logLevel=${settings.logLevel}" ) } } diff --git a/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/ServerSettingsCodec.kt b/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/ServerSettingsCodec.kt index 58a416f..dc1bf6d 100644 --- a/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/ServerSettingsCodec.kt +++ b/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/ServerSettingsCodec.kt @@ -85,6 +85,10 @@ object ServerSettingsCodec { SettingsConstants.screenOffRecordEnabled.key, settings.screenOffRecordEnabled ) + editor.putBoolean( + SettingsConstants.preciseScreenOffRecordEnabled.key, + settings.preciseScreenOffRecordEnabled + ) editor.putLong(SettingsConstants.segmentDurationMin.key, settings.segmentDurationMin) editor.putLong(SettingsConstants.logMaxHistoryDays.key, settings.maxHistoryDays) editor.putInt( @@ -123,6 +127,9 @@ object ServerSettingsCodec { screenOffRecordEnabled = source.boolean(SettingsConstants.screenOffRecordEnabled.key) ?: SettingsConstants.screenOffRecordEnabled.def, + preciseScreenOffRecordEnabled = + source.boolean(SettingsConstants.preciseScreenOffRecordEnabled.key) + ?: SettingsConstants.preciseScreenOffRecordEnabled.def, segmentDurationMin = source.long(SettingsConstants.segmentDurationMin.key) ?: SettingsConstants.segmentDurationMin.def, diff --git a/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/SettingsConstants.kt b/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/SettingsConstants.kt index a36ecb4..b47a471 100644 --- a/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/SettingsConstants.kt +++ b/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/SettingsConstants.kt @@ -87,6 +87,13 @@ object SettingsConstants { def = true ) + /** 是否启用精确息屏记录;开启后 Server 会在息屏记录阶段持有唤醒锁。 */ + val preciseScreenOffRecordEnabled = + BooleanConfigItem( + key = "precise_screen_off_record_enabled", + def = false + ) + /** 数据分段时长(分钟) */ val segmentDurationMin = LongConfigItem( diff --git a/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/dataclass/ServerSettings.kt b/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/dataclass/ServerSettings.kt index b924fc6..aab132b 100644 --- a/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/dataclass/ServerSettings.kt +++ b/shared/src/main/java/yangfentuozi/batteryrecorder/shared/config/dataclass/ServerSettings.kt @@ -33,6 +33,9 @@ data class ServerSettings( val writeLatencyMs: Long = SettingsConstants.writeLatencyMs.def, /** 息屏后是否继续采样与记录。 */ val screenOffRecordEnabled: Boolean = SettingsConstants.screenOffRecordEnabled.def, + /** 是否在息屏记录阶段持有唤醒锁,提升采样定时精度。 */ + val preciseScreenOffRecordEnabled: Boolean = + SettingsConstants.preciseScreenOffRecordEnabled.def, /** 单个记录文件按时间自动分段的时长,单位分钟,0 表示不分段。 */ val segmentDurationMin: Long = SettingsConstants.segmentDurationMin.def, /** 日志文件保留天数。 */