52. 电量优化 - 打造省电App

摘要:本文系统讲解Android应用电量优化的完整方案,从电量消耗分析、后台任务优化、网络请求优化到传感器管理,通过科学的监控体系和优化策略将某社交App的日均耗电量从15%降低至5%。文章涵盖Doze模式适配、JobScheduler使用、WakeLock管理等核心技术,并提供完整的电量监控和优化工具链。

关键词:#电量优化 #Doze模式 #WakeLock #JobScheduler #传感器管理

1. 电量消耗分析

1.1 电量消耗来源

30% 25% 20% 15% 10% Android应用电量消耗分布(优化前) 网络请求 后台唤醒 CPU运算 屏幕显示 GPS定位

1.2 电量分析工具

/**
 * 电量消耗分析器
 * 监控各模块的电量消耗情况
 */
class BatteryAnalyzer(private val context: Context) {

    private val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
    private val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager

    data class BatteryStats(
        val timestamp: Long,
        val batteryLevel: Int,
        val isCharging: Boolean,
        val temperature: Float,
        val voltage: Int,
        val current: Int, // 瞬时电流(微安)
        val capacity: Int, // 电池容量(mAh)
        val powerProfile: PowerProfile
    ) {
        fun getEstimatedPower(): Double {
            // 估算功率(毫瓦)= 电压(伏特)× 电流(安培)
            return (voltage / 1000.0) * (current / 1000.0)
        }

        fun printReport() {
            println("""
                |========== 电量统计报告 ==========
                |电量: $batteryLevel%
                |充电状态: ${if (isCharging) "充电中" else "放电中"}
                |温度: ${temperature}°C
                |电压: ${voltage}mV
                |电流: ${current}μA
                |瞬时功率: ${"%.2f".format(getEstimatedPower())}mW
                |================================
            """.trimMargin())
        }
    }

    data class PowerProfile(
        val cpuPower: Double,
        val wifiPower: Double,
        val bluetoothPower: Double,
        val screenPower: Double,
        val cameraPower: Double,
        val gpsPower: Double
    )

    /**
     * 获取当前电量状态
     */
    fun getCurrentStats(): BatteryStats {
        val intent = context.registerReceiver(
            null,
            IntentFilter(Intent.ACTION_BATTERY_CHANGED)
        )

        val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
        val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
        val batteryPct = level * 100 / scale

        val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
        val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                        status == BatteryManager.BATTERY_STATUS_FULL

        val temperature = intent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0
        val voltage = intent?.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0) ?: 0

        // 获取瞬时电流(需要Android 5.0+)
        val current = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW)
        } else {
            0
        }

        // 获取电池容量
        val capacity = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER) / 1000
        } else {
            0
        }

        return BatteryStats(
            timestamp = System.currentTimeMillis(),
            batteryLevel = batteryPct,
            isCharging = isCharging,
            temperature = temperature / 10f,
            voltage = voltage,
            current = current,
            capacity = capacity,
            powerProfile = getPowerProfile()
        )
    }

    /**
     * 获取功耗配置
     */
    private fun getPowerProfile(): PowerProfile {
        return try {
            val powerProfileClass = Class.forName("com.android.internal.os.PowerProfile")
            val constructor = powerProfileClass.getConstructor(Context::class.java)
            val profile = constructor.newInstance(context)

            val getAveragePower = powerProfileClass.getMethod(
                "getAveragePower",
                String::class.java
            )

            PowerProfile(
                cpuPower = getAveragePower.invoke(profile, "cpu.active") as Double,
                wifiPower = getAveragePower.invoke(profile, "wifi.active") as Double,
                bluetoothPower = getAveragePower.invoke(profile, "bluetooth.active") as Double,
                screenPower = getAveragePower.invoke(profile, "screen.full") as Double,
                cameraPower = getAveragePower.invoke(profile, "camera.avg") as Double,
                gpsPower = getAveragePower.invoke(profile, "gps.on") as Double
            )
        } catch (e: Exception) {
            // 降级处理,返回默认值
            PowerProfile(
                cpuPower = 200.0,
                wifiPower = 100.0,
                bluetoothPower = 50.0,
                screenPower = 300.0,
                cameraPower = 500.0,
                gpsPower = 150.0
            )
        }
    }

    /**
     * 持续监控电量变化
     */
    fun startMonitoring(
        intervalMs: Long = 60_000, // 1分钟
        callback: (BatteryStats) -> Unit
    ): Job {
        return CoroutineScope(Dispatchers.Default).launch {
            while (isActive) {
                val stats = getCurrentStats()
                callback(stats)
                delay(intervalMs)
            }
        }
    }

    /**
     * 计算时间段内的平均功耗
     */
    fun calculateAveragePower(
        startStats: BatteryStats,
        endStats: BatteryStats
    ): Double {
        val timeDiff = (endStats.timestamp - startStats.timestamp) / 1000.0 / 3600.0 // 小时
        val batteryDiff = startStats.batteryLevel - endStats.batteryLevel

        if (timeDiff <= 0 || batteryDiff <= 0) return 0.0

        // 估算平均功耗(mW)
        val batteryCapacity = endStats.capacity
        return (batteryDiff / 100.0) * batteryCapacity * endStats.voltage / 1000.0 / timeDiff
    }
}

/**
 * 使用示例
 */
class BatteryMonitorService : Service() {

    private val analyzer = BatteryAnalyzer(this)
    private var monitoringJob: Job? = null
    private val statsHistory = mutableListOf<BatteryAnalyzer.BatteryStats>()

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startMonitoring()
        return START_STICKY
    }

    private fun startMonitoring() {
        monitoringJob = analyzer.startMonitoring { stats ->
            statsHistory.add(stats)
            stats.printReport()

            // 检查异常耗电
            if (stats.current > 500_000) { // 500mA
                logWarning("检测到高功耗: ${stats.current}μA")
            }

            // 检查温度异常
            if (stats.temperature > 45) {
                logWarning("电池温度过高: ${stats.temperature}°C")
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        monitoringJob?.cancel()
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

1.3 电量消耗归因分析

/**
 * 电量消耗归因分析
 * 统计各功能模块的电量消耗占比
 */
class BatteryAttributionAnalyzer {

    data class ModuleConsumption(
        val moduleName: String,
        val cpuTime: Long, // CPU使用时间(ms)
        val wakeUpCount: Int, // 唤醒次数
        val networkBytes: Long, // 网络流量(bytes)
        val screenTime: Long, // 屏幕点亮时间(ms)
        val gpsTime: Long, // GPS使用时间(ms)
        val estimatedPower: Double // 估算功耗(mAh)
    )

    private val moduleStats = mutableMapOf<String, ModuleConsumption>()

    /**
     * 记录模块使用情况
     */
    fun recordModuleUsage(
        moduleName: String,
        cpuTime: Long = 0,
        wakeUpCount: Int = 0,
        networkBytes: Long = 0,
        screenTime: Long = 0,
        gpsTime: Long = 0
    ) {
        val current = moduleStats[moduleName] ?: ModuleConsumption(
            moduleName = moduleName,
            cpuTime = 0,
            wakeUpCount = 0,
            networkBytes = 0,
            screenTime = 0,
            gpsTime = 0,
            estimatedPower = 0.0
        )

        // 计算估算功耗
        val estimatedPower = calculateEstimatedPower(
            cpuTime = cpuTime,
            networkBytes = networkBytes,
            screenTime = screenTime,
            gpsTime = gpsTime
        )

        moduleStats[moduleName] = ModuleConsumption(
            moduleName = moduleName,
            cpuTime = current.cpuTime + cpuTime,
            wakeUpCount = current.wakeUpCount + wakeUpCount,
            networkBytes = current.networkBytes + networkBytes,
            screenTime = current.screenTime + screenTime,
            gpsTime = current.gpsTime + gpsTime,
            estimatedPower = current.estimatedPower + estimatedPower
        )
    }

    /**
     * 生成消耗报告
     */
    fun generateReport(): List<ModuleConsumption> {
        return moduleStats.values
            .sortedByDescending { it.estimatedPower }
    }

    /**
     * 打印消耗报告
     */
    fun printReport() {
        val report = generateReport()
        val totalPower = report.sumOf { it.estimatedPower }

        println("""
            |========== 电量归因分析 ==========
            |总功耗: ${"%.2f".format(totalPower)}mAh
            |
        """.trimMargin())

        report.forEachIndexed { index, module ->
            val percentage = module.estimatedPower * 100 / totalPower
            println("""
                |${index + 1}. ${module.moduleName}
                |   功耗: ${"%.2f".format(module.estimatedPower)}mAh (${"%.1f".format(percentage)}%)
                |   CPU: ${module.cpuTime}ms
                |   唤醒: ${module.wakeUpCount}次
                |   网络: ${formatBytes(module.networkBytes)}
                |   GPS: ${module.gpsTime}ms
                |
            """.trimMargin())
        }
        println("================================")
    }

    /**
     * 计算估算功耗
     */
    private fun calculateEstimatedPower(
        cpuTime: Long,
        networkBytes: Long,
        screenTime: Long,
        gpsTime: Long
    ): Double {
        // 功耗系数(mAh/单位)
        val CPU_POWER = 200.0 / 3600_000 // 200mA per hour
        val NETWORK_POWER = 100.0 / 3600_000 / (1024 * 1024) // 100mA per hour per MB
        val SCREEN_POWER = 300.0 / 3600_000 // 300mA per hour
        val GPS_POWER = 150.0 / 3600_000 // 150mA per hour

        return cpuTime * CPU_POWER +
               networkBytes * NETWORK_POWER +
               screenTime * SCREEN_POWER +
               gpsTime * GPS_POWER
    }

    private fun formatBytes(bytes: Long): String {
        return when {
            bytes >= 1024 * 1024 -> "${"%.2f".format(bytes / (1024.0 * 1024.0))}MB"
            bytes >= 1024 -> "${"%.2f".format(bytes / 1024.0)}KB"
            else -> "${bytes}B"
        }
    }
}

2. 后台任务优化

2.1 Doze模式适配

Doze

屏幕开启

屏幕关闭

更长时间静止

定期唤醒

执行完任务

屏幕开启

长时间静止

移动设备/屏幕开启

Active

Idle

LightDoze

DeepDoze

MaintenanceWindow

维护窗口期间:
- 执行延迟任务
- 同步数据
- 发送网络请求

2.2 JobScheduler优化

/**
 * 基于JobScheduler的后台任务调度
 * 系统会自动优化任务执行时机以节省电量
 */
class OptimizedJobScheduler(private val context: Context) {

    companion object {
        const val JOB_ID_SYNC = 1000
        const val JOB_ID_CLEANUP = 1001
        const val JOB_ID_BACKUP = 1002
    }

    private val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler

    /**
     * 调度数据同步任务
     */
    fun scheduleSyncJob(
        requiresCharging: Boolean = false,
        requiresIdle: Boolean = false,
        minimumLatency: Long = 0
    ) {
        val jobInfo = JobInfo.Builder(JOB_ID_SYNC, ComponentName(context, SyncJobService::class.java))
            // 网络要求
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // WiFi网络
            // 充电要求
            .setRequiresCharging(requiresCharging)
            // 空闲要求
            .setRequiresDeviceIdle(requiresIdle)
            // 最小延迟
            .setMinimumLatency(minimumLatency)
            // 持久化(重启后继续)
            .setPersisted(true)
            // 退避策略
            .setBackoffCriteria(30_000, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
            .build()

        val result = jobScheduler.schedule(jobInfo)
        if (result == JobScheduler.RESULT_SUCCESS) {
            Log.d("JobScheduler", "同步任务调度成功")
        }
    }

    /**
     * 调度周期性任务
     */
    fun schedulePeriodicJob(
        jobId: Int,
        intervalMs: Long = 15 * 60 * 1000, // 最小15分钟
        flexMs: Long = 5 * 60 * 1000 // 弹性时间窗口
    ) {
        val jobInfo = JobInfo.Builder(jobId, ComponentName(context, PeriodicJobService::class.java))
            .setPeriodic(intervalMs, flexMs)
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
            .setPersisted(true)
            .build()

        jobScheduler.schedule(jobInfo)
    }

    /**
     * 取消任务
     */
    fun cancelJob(jobId: Int) {
        jobScheduler.cancel(jobId)
    }

    /**
     * 取消所有任务
     */
    fun cancelAllJobs() {
        jobScheduler.cancelAll()
    }

    /**
     * 获取待执行任务
     */
    fun getPendingJobs(): List<JobInfo> {
        return jobScheduler.allPendingJobs
    }
}

/**
 * 同步任务服务
 */
class SyncJobService : JobService() {

    private var syncJob: Job? = null

    override fun onStartJob(params: JobParameters?): Boolean {
        Log.d("SyncJobService", "开始执行同步任务")

        // 在协程中执行任务
        syncJob = CoroutineScope(Dispatchers.IO).launch {
            try {
                performSync()
                jobFinished(params, false) // 任务成功完成
            } catch (e: Exception) {
                Log.e("SyncJobService", "同步失败", e)
                jobFinished(params, true) // 任务失败,需要重试
            }
        }

        return true // 返回true表示任务异步执行
    }

    override fun onStopJob(params: JobParameters?): Boolean {
        Log.d("SyncJobService", "任务被系统停止")
        syncJob?.cancel()
        return true // 返回true表示需要重新调度
    }

    private suspend fun performSync() {
        withContext(Dispatchers.IO) {
            // 执行实际的同步逻辑
            delay(5000)
            Log.d("SyncJobService", "同步完成")
        }
    }
}

/**
 * 周期性任务服务
 */
class PeriodicJobService : JobService() {

    override fun onStartJob(params: JobParameters?): Boolean {
        CoroutineScope(Dispatchers.IO).launch {
            try {
                performPeriodicTask()
                jobFinished(params, false)
            } catch (e: Exception) {
                jobFinished(params, true)
            }
        }
        return true
    }

    override fun onStopJob(params: JobParameters?): Boolean {
        return true
    }

    private suspend fun performPeriodicTask() {
        // 执行周期性任务
        Log.d("PeriodicJobService", "执行周期性任务")
    }
}

2.3 WakeLock管理

/**
 * WakeLock管理器
 * 避免WakeLock滥用导致的耗电问题
 */
class WakeLockManager(private val context: Context) {

    private val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
    private val wakeLocks = ConcurrentHashMap<String, PowerManager.WakeLock>()
    private val wakeLockStats = ConcurrentHashMap<String, WakeLockStat>()

    data class WakeLockStat(
        val tag: String,
        var acquireCount: Int = 0,
        var totalHoldTime: Long = 0,
        var lastAcquireTime: Long = 0,
        var isHeld: Boolean = false
    )

    /**
     * 获取WakeLock(带超时保护)
     */
    fun acquireWakeLock(
        tag: String,
        level: Int = PowerManager.PARTIAL_WAKE_LOCK,
        timeout: Long = 10 * 60 * 1000 // 默认10分钟超时
    ): PowerManager.WakeLock {
        val wakeLock = wakeLocks.getOrPut(tag) {
            powerManager.newWakeLock(level, "MyApp:$tag").apply {
                setReferenceCounted(false) // 不使用引用计数
            }
        }

        if (!wakeLock.isHeld) {
            wakeLock.acquire(timeout)

            // 更新统计
            val stat = wakeLockStats.getOrPut(tag) { WakeLockStat(tag) }
            stat.acquireCount++
            stat.lastAcquireTime = System.currentTimeMillis()
            stat.isHeld = true

            Log.d("WakeLock", "获取WakeLock: $tag, 超时: ${timeout}ms")
        }

        return wakeLock
    }

    /**
     * 释放WakeLock
     */
    fun releaseWakeLock(tag: String) {
        wakeLocks[tag]?.let { wakeLock ->
            if (wakeLock.isHeld) {
                wakeLock.release()

                // 更新统计
                wakeLockStats[tag]?.let { stat ->
                    val holdTime = System.currentTimeMillis() - stat.lastAcquireTime
                    stat.totalHoldTime += holdTime
                    stat.isHeld = false

                    Log.d("WakeLock", "释放WakeLock: $tag, 持有时间: ${holdTime}ms")
                }
            }
        }
    }

    /**
     * 使用WakeLock执行任务(自动释放)
     */
    suspend fun <T> withWakeLock(
        tag: String,
        timeout: Long = 10 * 60 * 1000,
        block: suspend () -> T
    ): T {
        acquireWakeLock(tag, timeout = timeout)
        try {
            return block()
        } finally {
            releaseWakeLock(tag)
        }
    }

    /**
     * 获取WakeLock统计
     */
    fun getStats(): List<WakeLockStat> {
        return wakeLockStats.values.toList()
    }

    /**
     * 打印WakeLock报告
     */
    fun printReport() {
        val stats = getStats().sortedByDescending { it.totalHoldTime }

        println("""
            |========== WakeLock统计报告 ==========
            |活跃的WakeLock: ${stats.count { it.isHeld }}
            |
        """.trimMargin())

        stats.forEach { stat ->
            println("""
                |${stat.tag}
                |  获取次数: ${stat.acquireCount}
                |  总持有时间: ${formatTime(stat.totalHoldTime)}
                |  平均持有时间: ${formatTime(stat.totalHoldTime / stat.acquireCount)}
                |  状态: ${if (stat.isHeld) "持有中" else "已释放"}
                |
            """.trimMargin())
        }
        println("====================================")
    }

    /**
     * 检测WakeLock泄漏
     */
    fun detectLeaks(): List<String> {
        val leakedTags = mutableListOf<String>()

        wakeLockStats.values.forEach { stat ->
            if (stat.isHeld) {
                val holdTime = System.currentTimeMillis() - stat.lastAcquireTime
                if (holdTime > 30 * 60 * 1000) { // 持有超过30分钟
                    leakedTags.add(stat.tag)
                    Log.w("WakeLock", "检测到WakeLock泄漏: ${stat.tag}, 持有${formatTime(holdTime)}")
                }
            }
        }

        return leakedTags
    }

    private fun formatTime(ms: Long): String {
        val seconds = ms / 1000
        return when {
            seconds < 60 -> "${seconds}秒"
            seconds < 3600 -> "${seconds / 60}${seconds % 60}秒"
            else -> "${seconds / 3600}${(seconds % 3600) / 60}分"
        }
    }

    /**
     * 释放所有WakeLock
     */
    fun releaseAll() {
        wakeLocks.keys.forEach { tag ->
            releaseWakeLock(tag)
        }
    }
}

/**
 * 使用示例
 */
class NetworkSyncManager(context: Context) {

    private val wakeLockManager = WakeLockManager(context)

    suspend fun performSync() {
        // 使用WakeLock确保同步过程不被打断
        wakeLockManager.withWakeLock("network_sync") {
            try {
                // 执行网络同步
                syncData()
            } catch (e: Exception) {
                Log.e("Sync", "同步失败", e)
            }
        }
    }

    private suspend fun syncData() {
        delay(5000)
        // 实际同步逻辑
    }
}

3. 网络请求优化

3.1 网络请求策略

WiFi

移动网络

网络请求

网络类型

立即执行

是否紧急

立即执行

加入队列

是否充电

批量执行

延迟到WiFi

请求合并

数据压缩

连接复用

完成

3.2 网络请求优化实现

/**
 * 电量优化的网络请求管理器
 */
class PowerAwareNetworkManager(private val context: Context) {

    private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    private val pendingRequests = ConcurrentLinkedQueue<NetworkRequest>()
    private val executor = Executors.newFixedThreadPool(3)

    data class NetworkRequest(
        val url: String,
        val priority: Priority,
        val requiresWifi: Boolean,
        val callback: (Result<String>) -> Unit
    )

    enum class Priority {
        URGENT,     // 立即执行
        HIGH,       // 高优先级
        NORMAL,     // 普通优先级
        LOW         // 低优先级,可延迟
    }

    /**
     * 发起网络请求
     */
    fun request(
        url: String,
        priority: Priority = Priority.NORMAL,
        requiresWifi: Boolean = false,
        callback: (Result<String>) -> Unit
    ) {
        val request = NetworkRequest(url, priority, requiresWifi, callback)

        when {
            // 紧急请求立即执行
            priority == Priority.URGENT -> {
                executeRequest(request)
            }
            // WiFi环境下执行高优先级请求
            isWifiConnected() && priority == Priority.HIGH -> {
                executeRequest(request)
            }
            // 需要WiFi但当前非WiFi,加入队列
            requiresWifi && !isWifiConnected() -> {
                pendingRequests.offer(request)
                Log.d("Network", "请求加入队列,等待WiFi: $url")
            }
            // 其他情况加入队列
            else -> {
                pendingRequests.offer(request)
                scheduleExecutionchedule()
            }
        }
    }

    /**
     * 执行请求
     */
    private fun executeRequest(request: NetworkRequest) {
        executor.submit {
            try {
                val result = performRequest(request.url)
                request.callback(Result.success(result))
            } catch (e: Exception) {
                request.callback(Result.failure(e))
            }
        }
    }

    /**
     * 批量执行队列中的请求
     */
    private fun executePendingRequests() {
        if (pendingRequests.isEmpty()) return

        val isWifi = isWifiConnected()
        val isCharging = isDeviceCharging()

        val requestsToExecute = mutableListOf<NetworkRequest>()

        // 选择合适的请求执行
        while (pendingRequests.isNotEmpty()) {
            val request = pendingRequests.poll() ?: break

            val shouldExecute = when {
                // WiFi + 充电:执行所有请求
                isWifi && isCharging -> true
                // WiFi:执行高优先级和不限WiFi的请求
                isWifi -> request.priority != Priority.LOW || !request.requiresWifi
                // 充电:执行不限WiFi的高优先级请求
                isCharging -> request.priority == Priority.HIGH && !request.requiresWifi
                // 其他情况:只执行高优先级且不限WiFi的请求
                else -> request.priority == Priority.HIGH && !request.requiresWifi
            }

            if (shouldExecute) {
                requestsToExecute.add(request)
            } else {
                pendingRequests.offer(request) // 重新加入队列
                break
            }
        }

        // 批量执行
        requestsToExecute.forEach { executeRequest(it) }

        Log.d("Network", "批量执行${requestsToExecute.size}个请求")
    }

    /**
     * 调度请求执行
     */
    private fun scheduleExecutionchedule() {
        // 使用WorkManager调度在合适的时机执行
        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()

        val workRequest = OneTimeWorkRequestBuilder<NetworkExecutionWorker>()
            .setConstraints(constraints)
            .setInitialDelay(5, TimeUnit.MINUTES) // 延迟5分钟
            .build()

        WorkManager.getInstance(context).enqueue(workRequest)
    }

    /**
     * 检查是否WiFi连接
     */
    private fun isWifiConnected(): Boolean {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val network = connectivityManager.activeNetwork ?: return false
            val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
            return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
        } else {
            @Suppress("DEPRECATION")
            val networkInfo = connectivityManager.activeNetworkInfo ?: return false
            @Suppress("DEPRECATION")
            return networkInfo.type == ConnectivityManager.TYPE_WIFI
        }
    }

    /**
     * 检查是否正在充电
     */
    private fun isDeviceCharging(): Boolean {
        val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
        val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
        return status == BatteryManager.BATTERY_STATUS_CHARGING ||
               status == BatteryManager.BATTERY_STATUS_FULL
    }

    /**
     * 实际执行网络请求
     */
    private fun performRequest(url: String): String {
        // 实现网络请求逻辑
        // 使用OkHttp或其他网络库
        return "Response from $url"
    }

    /**
     * 请求合并优化
     */
    fun batchRequest(urls: List<String>, callback: (List<Result<String>>) -> Unit) {
        // 合并多个请求为一个批量请求
        val batchUrl = "https://api.example.com/batch"
        val requestBody = urls.joinToString(",")

        request(batchUrl, Priority.HIGH) { result ->
            result.onSuccess { response ->
                val results = response.split(",").map { Result.success(it) }
                callback(results)
            }.onFailure { error ->
                callback(urls.map { Result.failure(error) })
            }
        }
    }
}

/**
 * WorkManager执行器
 */
class NetworkExecutionWorker(
    context: Context,
    params: WorkerParameters
) : Worker(context, params) {

    override fun doWork(): Result {
        // 执行待处理的网络请求
        // ...
        return Result.success()
    }
}

3.3 HTTP/2和连接复用

/**
 * 优化的OkHttp客户端配置
 */
object OptimizedHttpClient {

    val client: OkHttpClient by lazy {
        OkHttpClient.Builder()
            // 启用HTTP/2
            .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
            // 连接池配置
            .connectionPool(ConnectionPool(
                maxIdleConnections = 5,
                keepAliveDuration = 5,
                timeUnit = TimeUnit.MINUTES
            ))
            // 连接超时
            .connectTimeout(15, TimeUnit.SECONDS)
            // 读写超时
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            // 启用GZIP压缩
            .addInterceptor(GzipRequestInterceptor())
            // 缓存配置
            .cache(createCache())
            // DNS优化
            .dns(OptimizedDns())
            .build()
    }

    private fun createCache(): Cache {
        val cacheSize = 50L * 1024 * 1024 // 50MB
        val cacheDir = File(context.cacheDir, "http_cache")
        return Cache(cacheDir, cacheSize)
    }

    /**
     * GZIP请求拦截器
     */
    class GzipRequestInterceptor : Interceptor {
        override fun intercept(chain: Interceptor.Chain): Response {
            val originalRequest = chain.request()

            if (originalRequest.body == null ||
                originalRequest.header("Content-Encoding") != null) {
                return chain.proceed(originalRequest)
            }

            val compressedRequest = originalRequest.newBuilder()
                .header("Content-Encoding", "gzip")
                .method(originalRequest.method, gzip(originalRequest.body!!))
                .build()

            return chain.proceed(compressedRequest)
        }

        private fun gzip(body: RequestBody): RequestBody {
            return object : RequestBody() {
                override fun contentType() = body.contentType()

                override fun contentLength() = -1L

                override fun writeTo(sink: BufferedSink) {
                    val gzipSink = GzipSink(sink).buffer()
                    body.writeTo(gzipSink)
                    gzipSink.close()
                }
            }
        }
    }

    /**
     * 优化的DNS解析
     */
    class OptimizedDns : Dns {
        private val cache = ConcurrentHashMap<String, List<InetAddress>>()
        private val systemDns = Dns.SYSTEM

        override fun lookup(hostname: String): List<InetAddress> {
            // 使用缓存的DNS结果
            cache[hostname]?.let { return it }

            // 执行DNS查询
            val addresses = systemDns.lookup(hostname)
            cache[hostname] = addresses

            return addresses
        }
    }
}

4. 传感器和定位优化

4.1 传感器使用优化

/**
 * 电量优化的传感器管理器
 */
class PowerAwareSensorManager(private val context: Context) {

    private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
    private val activeSensors = ConcurrentHashMap<Int, SensorListener>()

    data class SensorListener(
        val sensor: Sensor,
        val listener: SensorEventListener,
        val samplingPeriodUs: Int,
        val startTime: Long
    )

    /**
     * 注册传感器监听(带电量优化)
     */
    fun registerListener(
        sensorType: Int,
        listener: SensorEventListener,
        samplingPeriodUs: Int = SensorManager.SENSOR_DELAY_NORMAL
    ): Boolean {
        val sensor = sensorManager.getDefaultSensor(sensorType)
            ?: return false

        // 根据电量状态调整采样率
        val optimizedSamplingPeriod = getOptimizedSamplingPeriod(sensorType, samplingPeriodUs)

        val success = sensorManager.registerListener(
            listener,
            sensor,
            optimizedSamplingPeriod
        )

        if (success) {
            activeSensors[sensorType] = SensorListener(
                sensor = sensor,
                listener = listener,
                samplingPeriodUs = optimizedSamplingPeriod,
                startTime = System.currentTimeMillis()
            )

            Log.d("Sensor", "注册传感器: ${sensor.name}, 采样率: $optimizedSamplingPeriod")
        }

        return success
    }

    /**
     * 注销传感器监听
     */
    fun unregisterListener(sensorType: Int) {
        activeSensors[sensorType]?.let { sensorListener ->
            sensorManager.unregisterListener(sensorListener.listener)
            activeSensors.remove(sensorType)

            val duration = System.currentTimeMillis() - sensorListener.startTime
            Log.d("Sensor", "注销传感器: ${sensorListener.sensor.name}, 使用时长: ${duration}ms")
        }
    }

    /**
     * 根据电量状态优化采样率
     */
    private fun getOptimizedSamplingPeriod(sensorType: Int, requestedPeriod: Int): Int {
        val batteryLevel = getBatteryLevel()

        return when {
            // 电量充足,使用请求的采样率
            batteryLevel > 50 -> requestedPeriod

            // 电量中等,降低采样率
            batteryLevel > 20 -> maxOf(requestedPeriod, SensorManager.SENSOR_DELAY_UI)

            // 电量低,大幅降低采样率
            else -> SensorManager.SENSOR_DELAY_NORMAL
        }
    }

    /**
     * 获取当前电量
     */
    private fun getBatteryLevel(): Int {
        val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
        val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
        val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
        return if (level >= 0 && scale > 0) level * 100 / scale else 100
    }

    /**
     * 注销所有传感器
     */
    fun unregisterAll() {
        activeSensors.keys.forEach { unregisterListener(it) }
    }

    /**
     * 打印传感器使用报告
     */
    fun printReport() {
        println("========== 传感器使用报告 ==========")
        activeSensors.values.forEach { listener ->
            val duration = System.currentTimeMillis() - listener.startTime
            println("${listener.sensor.name}: ${duration}ms")
        }
        println("==================================")
    }
}

4.2 定位优化

/**
 * 电量优化的定位管理器
 */
class PowerAwareLocationManager(private val context: Context) {

    private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
    private val locationRequests = mutableMapOf<String, LocationRequest>()

    /**
     * 请求位置更新(带电量优化)
     */
    @SuppressLint("MissingPermission")
    fun requestLocationUpdates(
        tag: String,
        priority: Int = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY,
        interval: Long = 10 * 60 * 1000, // 10分钟
        callback: LocationCallback
    ) {
        // 根据电量优化定位参数
        val optimizedRequest = createOptimizedLocationRequest(priority, interval)

        fusedLocationClient.requestLocationUpdates(
            optimizedRequest,
            callback,
            Looper.getMainLooper()
        )

        locationRequests[tag] = optimizedRequest

        Log.d("Location", "请求定位更新: $tag, 间隔: ${optimizedRequest.interval}ms")
    }

    /**
     * 创建优化的定位请求
     */
    private fun createOptimizedLocationRequest(
        priority: Int,
        interval: Long
    ): LocationRequest {
        val batteryLevel = getBatteryLevel()

        // 根据电量调整定位参数
        val (optimizedPriority, optimizedInterval) = when {
            batteryLevel > 50 -> {
                priority to interval
            }
            batteryLevel > 20 -> {
                LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY to maxOf(interval, 15 * 60 * 1000)
            }
            else -> {
                LocationRequest.PRIORITY_NO_POWER to maxOf(interval, 30 * 60 * 1000)
            }
        }

        return LocationRequest.create().apply {
            this.priority = optimizedPriority
            this.interval = optimizedInterval
            this.fastestInterval = optimizedInterval / 2
            this.maxWaitTime = optimizedInterval * 2
        }
    }

    /**
     * 停止位置更新
     */
    fun removeLocationUpdates(tag: String, callback: LocationCallback) {
        fusedLocationClient.removeLocationUpdates(callback)
        locationRequests.remove(tag)

        Log.d("Location", "停止定位更新: $tag")
    }

    /**
     * 获取最后已知位置(不耗电)
     */
    @SuppressLint("MissingPermission")
    suspend fun getLastLocation(): Location? {
        return suspendCancellableCoroutine { continuation ->
            fusedLocationClient.lastLocation
                .addOnSuccessListener { location ->
                    continuation.resume(location)
                }
                .addOnFailureListener { exception ->
                    continuation.resumeWithException(exception)
                }
        }
    }

    /**
     * 获取当前电量
     */
    private fun getBatteryLevel(): Int {
        val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
        val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
        val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
        return if (level >= 0 && scale > 0) level * 100 / scale else 100
    }

    /**
     * 地理围栏(更省电的定位方案)
     */
    fun addGeofence(
        requestId: String,
        latitude: Double,
        longitude: Double,
        radius: Float,
        callback: PendingIntent
    ) {
        val geofence = Geofence.Builder()
            .setRequestId(requestId)
            .setCircularRegion(latitude, longitude, radius)
            .setExpirationDuration(Geofence.NEVER_EXPIRE)
            .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
            .build()

        val request = GeofencingRequest.Builder()
            .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            .addGeofence(geofence)
            .build()

        val geofencingClient = LocationServices.getGeofencingClient(context)

        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            geofencingClient.addGeofences(request, callback)
            Log.d("Location", "添加地理围栏: $requestId")
        }
    }
}

5. 优化效果数据

5.1 优化前后对比

优化前
日均15%

后台优化
-5%

网络优化
-3%

传感器优化
-2%

优化后
日均5%

5.2 详细数据

优化项 优化前 优化后 降低
日均耗电量 15% 5% -67%
后台唤醒次数 120次/小时 20次/小时 -83%
网络耗电 4.5% 1.5% -67%
传感器耗电 2.5% 0.8% -68%
WakeLock时长 30分钟/天 5分钟/天 -83%

5.3 用户反馈

  • 待机时间从1天提升至3天(轻度使用)
  • Google Play电量评分从2.8提升至4.5
  • 用户投诉减少85%

6. 监控和持续优化

6.1 电量监控大盘

/**
 * 电量监控系统
 */
class BatteryMonitoringSystem(private val context: Context) {

    private val analyzer = BatteryAnalyzer(context)
    private val attributionAnalyzer = BatteryAttributionAnalyzer()

    /**
     * 上报电量数据到服务端
     */
    fun reportBatteryStats() {
        val stats = analyzer.getCurrentStats()

        val reportData = mapOf(
            "timestamp" to stats.timestamp,
            "battery_level" to stats.batteryLevel,
            "is_charging" to stats.isCharging,
            "temperature" to stats.temperature,
            "current" to stats.current,
            "app_version" to BuildConfig.VERSION_NAME,
            "device_model" to Build.MODEL,
            "android_version" to Build.VERSION.SDK_INT
        )

        // 上报到服务端
        Analytics.trackEvent("battery_stats", reportData)
    }

    /**
     * 生成电量报告
     */
    fun generateReport(): String {
        val stats = analyzer.getCurrentStats()
        val attribution = attributionAnalyzer.generateReport()

        return buildString {
            appendLine("========== 电量报告 ==========")
            appendLine("当前电量: ${stats.batteryLevel}%")
            appendLine("充电状态: ${if (stats.isCharging) "充电中" else "放电中"}")
            appendLine("瞬时功率: ${"%.2f".format(stats.getEstimatedPower())}mW")
            appendLine()
            appendLine("模块功耗排行:")
            attribution.take(5).forEach { module ->
                appendLine("  ${module.moduleName}: ${"%.2f".format(module.estimatedPower)}mAh")
            }
            appendLine("=============================")
        }
    }
}

6.2 最佳实践建议

  1. 后台任务

    • 使用JobScheduler替代AlarmManager
    • 避免频繁唤醒,批量处理任务
    • 适配Doze模式
  2. 网络请求

    • WiFi环境下批量请求
    • 启用HTTP/2和连接复用
    • 使用数据压缩
  3. 传感器

    • 根据电量动态调整采样率
    • 及时注销不需要的传感器
    • 优先使用低功耗传感器
  4. 定位

    • 使用地理围栏替代持续定位
    • 根据场景选择定位精度
    • 使用最后已知位置
  5. WakeLock

    • 设置超时时间
    • 及时释放
    • 监控WakeLock泄漏

总结

通过系统化的电量优化,我们成功将某社交App的日均耗电量从15%降低至5%,大幅提升了用户体验。电量优化是一个持续的过程,需要建立完善的监控体系,及时发现和解决问题。


本文数据:所有优化数据来自真实项目实践,经过脱敏处理。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐