功耗优化 · 方案篇 · Android功耗优化指导规范

简介: 功耗优化 · 方案篇 · Android功耗优化指导规范

image.png

【小木箱成长营】功耗优化系列文章:

功耗优化 · 入门篇 · 浅析Android耗电量优化

功耗优化 · 工具篇 · 常用的7款功耗优化工具

功耗优化 · 实战篇 · 100万日活APP功耗优化实践

Tips: 关注小木箱成长营公众号, 回复"功耗优化"可免费获取功耗优化思维导图。

一、序言

Hello,我是小木箱,欢迎来到小木箱成长营系列教程,今天将分享功耗优化 · 方案篇 · Android功耗优化指导规范。小木箱从五个维度将Android功耗优化指导规范解释清楚。

Android功耗优化指导规范第一部分内容是5W2H分析功耗优化。第二部分内容是功耗优化技术方案。第三部分内容是功耗优化方案分析。第四部分内容是功耗APM监控建设。最后一部分内容是总结与展望。

其中,功耗优化技术方案分为三部分内容,第一部分内容是功耗优化工具,第二部分内容是功耗优化指南,最后一部分内容是功耗优化SOP。

其中,功耗优化方案分析主要分为六部分内容。第一部分内容是CPU异常SOP,第二部分内容是Camera功耗,第三部分内容是低功耗,第四部分内容是热缓解,第五部分内容是动态帧率,最后一部分内容是SurfaceView替换TextureView。

image.png

如果学完小木箱功耗优化的入门篇、工具篇、方案篇和实战篇,那么任何人做功耗优化都可以拿到结果。

二、5W2H分析功耗优化

首先我们说说我们的第一部分内容,5W2H 分析功耗优化,5W2H 分析功耗优化提出了 6 个高价值问题

  • What : 功耗优化定义
  • Why: 高功耗的原因
  • How: 功耗优化流程
  • Who: 功耗痛点治理
  • How Much: 功耗优化价值
  • Where: 方案使用场景

2.1 What: 功耗优化定义

image.png

Android功耗优化是指优化Android APP在运行过程中的电量消耗,以获得更长的电池续航时间和更优秀的用户体验。功耗优化包括:设备的动态调频、网络优化、电池感知、界面优化、后台活动优化和内存优化等

2.2 How Much: 功耗优化价值

image.png

下面的思维导图小木箱从性能价值、用户价值和社会价值三个维度分析了功耗优化价值。

image.png

2.3 Why: 高功耗的原因

image.png

高功耗是因为设备在运行过程中需要使用大量的电力。

因为CPU高负载、高亮度显示器、频繁GPS定位、后台运行程序、高频率网络通信和不必要动画等原因会导致设备高功耗

下面思维导图小木箱简单总结高功耗6个原因。

image.png

2.4 Who: 功耗痛点治理

该如何做功耗优化呢?

image.png

首先, 高功耗任务要分析设备功耗优化的占比情况,然后逐一进行排查

image.png

影响高功耗的设备主要有NetWorrk、GPS、Audio、Camera、Display、GPU、BlueTooth和Sensor等。详见下图:

image.png

小木箱将所有高功耗设备按照MECE原则进行分类如下表格:

设备 功耗影响因子
Display 亮度、显示内容、刷新率
CPU 工作频率、工作负载、运算内容
GPU 工作频率、工作负载、绘制内容、分辨率、绘制帧率
WIFI 信号、协议(影响收发速率)、工作频率(2.4G , 5G)
Audio 音量、音效算法
GPS 频率、定位精度
Modem 天线信号强度、协议(如5g功耗远高于4g)、环境(如高铁上会频繁测量搜网)
Camera 摄像头(主摄、微距、广角、前置、tof等)工作模式(预览、拍照和录像)分辨率、录制帧率

小木箱建议大家使用Android的电池监控工具来监测APP的电池使用情况

根据业务表现不同特征, 尽量使用系统 API、减少后台进程、避免让手机长时间处于高亮度模式、合理使用 GPS 和网络、优化图像和视频、尽量避免定时高功耗任务和避免频繁的唤醒 CPU方式进行优化功耗。

Android常见的高功耗设备可以参考下图:

image.png

那么, 该如何找出正在运行的高耗设备?

image.png

查找高功耗电Android设备,需要使用Android电池管理API,详细代码如下:

BatteryStats batteryStats = BatteryStatsHelper.getBatteryStats(context, batteryInfo);
Map<String, ? extends BatteryStats.Uid> batteryData = batteryStats.getUidStats();
for (Map.Entry<String, ? extends BatteryStats.Uid> entry : batteryData.entrySet()) {
    BatteryStats.Uid uid = entry.getValue();
    int uidBatteryUsage = uid.getBatteryPercentOfTotal();
    if (uidBatteryUsage > 0) {
        // Use this information to identify which APPs are consuming the most battery power.
    }
}

首先,高功耗任务需要新增android.permission.BATTERY_STATS权限,方便使用电池管理 API。

然后,使用Android电池管理API获取APP的电池使用情况,并循环遍历每个APP,最后, 确定它们对总电池使用情况的贡献。

最后,如果APP对总电池使用情况的贡献大于零,那么可以将该APP标识为消耗大量电力的APP。

减少设备使用方式详见有如下思维导图:

image.png

下面我们逐一讲解着五种减少设备使用的方式

2.4.1 降低亮度

image.png

WindowManager.LayoutParams layout = getWindow().getAttributes();
layout.screenBrightness = 0.5F;
getWindow().setAttributes(layout);

在上面的代码中,通过获取当前窗口的布局参数,并将其中的屏幕亮度设置为 0.5F。请注意,此亮度值仅在 0 到 1 之间。

因此,如果希望更低的亮度,则可以将此值设置为更小的值。

请注意,需要在活动中调用此代码,以便更改当前活动的屏幕亮度。

此外,还需要检查是否具有 android.permission.WRITE_SETTINGS 权限,以便更改系统设置。

2.4.2 降低动画帧率

image.png

getWindow().setWindowAnimationsScale(0.5f);

首先,请确保有SYSTEM_ALERT_WINDOW设置的权限。

然后,通过调用 setWindowAnimationsScale 方法并将其参数设置为0.5f,可以降低当前活动的动画帧率,该值仅在0到1之间。

最后, 需要在Activity中调用上面的代码就可以更改当前Activity的动画帧率

2.4.3 减少数据收发

image.png

减少数据收发的方式有四种,分别是

  1. 使用后台线程:在后台线程中执行网络请求,而不是在主线程中,以避免阻塞主线程。
  2. 缓存数据:使用本地缓存存储经常使用的数据,以避免不必要的网络请求。
  3. 压缩数据:在发送数据前对数据进行压缩,以减少数据量。
  4. 避免不必要的数据收发:使用状态管理,避免对已经存在的数据进行不必要的收发。

以下是一个使用 Retrofit 和 OkHttp 实现网络请求的代码示例,并将数据缓存到本地:

public class NetworkService {
    private static final String BASE_URL = "https://github.microkibaco.com/api/";
    private static final int CACHE_SIZE = 10 * 1024 * 1024; // 10 MB
    private static NetworkService instance;
    private Retrofit retrofit;
    private OkHttpClient client;
    private NetworkService() {
        // 创建缓存目录
        File cacheDirectory = new File(
                Environment.getDownloadCacheDirectory(), "retrofit-cache");
        Cache cache = new Cache(cacheDirectory, CACHE_SIZE);
        // 配置 OkHttpClient
        client = new OkHttpClient.Builder()
                .cache(cache)
                .build();
        // 配置 Retrofit
        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .build();
    }
    public static NetworkService getInstance() {
        if (instance == null) {
            instance = new NetworkService();
        }
        return instance;
    }
    public Retrofit getRetrofit() {
        return retrofit;
    }
}
复制代码

2.4.4 降低音量

image.png

在 Android 代码层面,可以使用以下代码来降低音量,从而优化电池使用寿命:

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC), 0);

该代码将音频流的音量设置为最低值,从而降低手机的音量。

2.4.5 治理CPU高负载

image.png

治理CPU高负载异常并优化电池使用寿命常见方式有四个:

检测 CPU 负载异常可以通过使用 Android 提供的两个工具。

第一, adb shell top

image.png

第二, adb shell dumpsys CPUinfo

image.png

分析负载异常原因方式也有两种:

第一使用Android Studio的“Profile GPU Rendering”

image.png

第二使用“Debug GPU Overdraw”功能。

image.png

如果分析结果是APP代码引起的负载异常,那么可以减少不必要的动画、使用多线程等来优化 CPU 负载。

如果分析结果表明是后台服务引起的负载异常,那么可以通过关闭服务来减少CPU负载。

小木箱后文会对CPU负载进行详细分析

2.5 How: 功耗优化流程

image.png

当执行高功耗任务时候,业务开发应该反思下面四个问题:

  • 高功耗任务是否必须要做?
  • 高功耗任务是否必须准时做?
  • 高功耗任务是否必须保证效果?
  • 高功耗任务必须的方案是否具有唯一性?

关于问题1: 高功耗任务是否必须要做? 如果高功耗任务不是必须要做的,业务开发可以考虑延迟或不执行,达到动画泄漏治理,让后台高功耗任务延迟做

image.png

关于问题2: 高功耗任务是否必须准时做?如果高功耗任务不是必须准时做的,高功耗任务业务开发可以考虑分批减少频率,达到MQTT心跳降级,GPS定位频率因此会降低

image.png

关于问题3: 高功耗任务是否必须保证效果?如果高功耗任务不是必须保证效果,高功耗任务业务开发可以考虑降低效果,常见的做法是算法降级如:定位降级为网络定位

image.png

关于问题4: 高功耗任务必须的方案是否具有唯一性?如果高功耗任务必须的方案不具有唯一性,高功耗任务业务开发可以考虑硬解密替代软解密,厂商Push代替自建长链接

image.png

功耗设备降级的方式主要有异常治理、正面优化和业务降级四种.

异常治理有三种情况: 死循环治理、资源不释放治理和高频耗时函数优化

正面优化有三种情况: 动态帧率和刷新率、绘制图层优化和长链接心跳优化

业务降级有三种情况: 预加载降级、常驻动画降级和超分算法降级

后文小木箱会逐一进行讲解

2.6 Where: 方案使用场景

image.png

方案使用场景方案使用场景小木箱这里只会带大家了解一下异常治理和无损降级,后文会讲解有感降级。

image.png

2.6.1 异常治理

异常处理不是直接解决Android功耗优化的方法,但是通过异常处理可以避免不必要的资源浪费,从而减少功耗消耗。

高功耗任务可以通过避免不必要的资源分配、避免不必要计算、不必要数据库操作等异常处理解决Android功耗问题。

2.6.2 业务降级(无感知)

使用JobScheduler代替AlarmManager:JobScheduler可以在系统空闲时执行高功耗任务,因此可以降低功耗消耗。

image.png

使用后台服务降级:对于需要长时间运行的后台服务,可以通过降级方式(例如,从运行在前台的服务切换到后台定时高功耗任务)来减少功耗消耗。

使用Doze模式优化:Doze模式可以在系统进入空闲状态时减少后台活动,从而减少功耗消耗。

image.png

使用Firebase Cloud Messaging(FCM)代替定时高功耗任务:FCM可以在系统空闲时向APP发送消息,从而减少功耗消耗。

image.png

使用Battery Optimizations API:Battery Optimizations API可以在系统空闲时减少APP的活动,从而减少功耗消耗。

三、功耗优化技术方案

功耗优化技术方案主要分为四个部分,第一个部分是功耗优化工具,第二个部分是功耗优化指南,第三个部分是功耗优化SOP。

3.1 功耗优化工具

功耗优化工具有八个可以聊聊,分别是电流仪、Battery Historian、Energy Profiler、Dumpsys Batterystats、Battery-Metrics、BatteryCanary、Diggo、和Systrace

3.1.1 电流仪

image.png

优点

最准确的测试方法是稳压电源+电流仪, 稳压电源+电流仪优点是可以测试整机电流,并且数据精准

缺点

因为稳压电源+电流仪需要准备硬件工具,稳压电源+电流仪测试操作复杂,并且稳压电源+电流仪不能测试APP消耗电量,一般没有人在移动开发中使用稳压电源+电流仪

使用场景

ROM厂商整机电流科学实验

3.1.2 Battery Historian

image.png

传送门: 使用 Battery Historian 分析耗电情况

使用指南(待更新): space.bilibili.com/455432199

参数说明

Historian
参数 说明
Battery Level 电量
Userpace WakeLock 用户唤醒锁
Long WakeLocks 长期持有唤醒锁事件
GPS 是否使用GPS
Wifi radio 是否正在通过wifi传输数据
Network Connectivity 网络连接状态(wifi、mobile是否连接)
Mobile signal strength 移动信号强度(great/good/moderate/poor)
Mobile network type 移动信号类型
Audio 音频是否开启
Camera 相机是否在工作
Video 是否在播放视频
Temperature 电池温度
App Selection
参数名 说明
Device estimated power use 预计耗电量
Vibrator use 手机振动器
Wakelocks 唤醒锁事件
Services app开启的服务

优点

  1. Battery Historian以图表形式展示,对APP耗电情况有详细数据展示,同步还提供了手机当前的状况,方便分析APP的具体原因
  2. Battery Historian有耗电量的详细数据如: APP的使用时间、电池消耗、唤醒锁定和CPU使用情况
  3. Battery Historian可以追踪应用程序的后台运行时间、网络使用情况、设备唤醒频率等
  4. Battery Historian可以确定哪些APP或服务高功耗

缺点

  1. Battery Historian只适用于Android5.0及以上系统
  2. Battery Historian无法定位具体是什么方法或者功能引起的耗电异常,无法获取APP异常运动时的堆栈信息
  3. Battery Historian收集的数据是历史数据,非实时电耗情况。
  4. Battery Historian收集 CPU 使用情况、网络数据传输等需要 root 权限。

使用场景

  • 诊断应用程序、系统服务和硬件组件的电池消耗问题
  • 评估Android设备电池的使用寿命信息
  • 比较不同设备、不同应用程序或不同操作系统版本的电池消耗情况

3.1.3 Energy Profiler

传送门: developer.android.com/studio/prof…

使用指南(待更新): space.bilibili.com/455432199

优点

  • Energy Profiler 可以提供实时的电池消耗
  • Energy Profiler 可以针对特定的应用程序进行电池消耗分析,而不是整台设备
  • Android Studio自带Buff,无需安装插件
  • Energy Profiler提供CPU 使用情况、传感器使用情况和网络数据传输能力
  • Energy Profiler支持历史数据分析

缺点

  • 无法多端复用
  • 只支持Android 8.0以上版本
  • 不提供高功耗解决方案

使用场景

  • 测试电池寿命
  • 比较不同版本的应用程序
  • 确认电池相关问题

3.1.4 Dumpsys Batterystats

传送门: developer.android.com/studio/comm…

使用指南(待更新): space.bilibili.com/455432199

image.png

优点

  • Dumpsys Batterystats可以监测系统服务和其他应用程序等系统级别的电池消耗
  • Dumpsys Batterystats可以监测应用程序在使用电池时的功耗情况,并将其记录在日志文件中
  • Dumpsys Batterystats会生成应用程序、系统服务、电池消耗原因和耗电量使用情况报告

缺点

  • 无法检测应用程序和服务之间的相互影响
  • Dumpsys Batterystats测试只能在设备上运行一段时间后收集数据
  • Dumpsys Batterystats方式获取的是原始的耗电记录数据,需要人肉查找关键信息,可读性不高

使用场景

  • 电池寿命测试
  • 电源管理优化
  • Dumpsys Batterystats帮助用户排除设备电池故障

3.1.5 Battery-Metrics

传送门: github.com/facebookinc…

使用指南(待更新): space.bilibili.com/455432199

优点

  • Battery-Metrics可以检测到应用程序和服务之间的相互影响
  • Battery-Metrics测试功耗方法和技术比Batterystats精准
  • Battery-Metrics测试功耗能够检测用户APP使用习惯、网络连接质量、屏幕亮度、环境温度
  • Battery-Metrics支持多种设备和操作系统版本
  • Battery-Metrics可以通过hook和插桩的方式上报关键节点的电池和手机相关数据,可高度定制化

缺点

Battery-Metrics插件方案Hook点位受到API版本限制

使用场景

不同领域和高功耗优化场景高可用

3.1.6 BatteryCanary

传送门: github.com/MicroKibaco…

使用指南(待更新): space.bilibili.com/455432199

BatteryCanary也可以通过hook和插桩的方式上报关键节点的电池和手机相关数据,已实现关键点的插桩和hook

优点

  • 实时监测
  • 易于集成
  • 精细化分析
  • 易于定位问题
  • 开源免费

缺点

  • 对测试设备要求较高
  • 有些数据可能不够准确
  • 导致应用程序的安装包变得更大

使用场景

不同领域和高功耗优化场景高可用

image.png

image.png

image.png

3.1.7 Diggo

传送门: github.com/bytedance/F…

使用指南(待更新): space.bilibili.com/455432199

优点

  • Diggo 可以对应用程序的每个组件进行功耗采样和统计,得到精确的功耗数据,并可以将功耗数据可视化,便于开发人员分析和优化
  • Diggo 提供了一些自动化的功耗优化策略,例如优化启动过程、减少资源使用等
  • Diggo可以与Traceview、Jupyter进行集成

缺点

  • 侵入业务代码
  • 无法处理硬件功耗问题
  • 可能会影响应用程序的性能
  • 无法跨端使用

使用场景

  • 开发人员和测试团队对App进行基准测试
  • 优化应用程序的电池寿命

3.1.8 Systrace

传送门: blog.csdn.net/u011578734/…

使用指南(待更新): space.bilibili.com/455432199

image.png

优点

精确定位功耗问题:Systrace 可以提供精确到微秒级别的时间戳,定位功耗问题问题时间和位置。

缺点

Systrace 只能监测 Android 系统本身以及应用程序,但不能监测硬件功耗,例如屏幕、摄像头、传感器等硬件设备的功耗

使用场景

识别瞬时功耗:Systrace 可以捕捉瞬时功耗事件,例如启动应用程序、打开相机等操作。

可以识别到不必要的系统服务或者过多的数据同步操作等后台功耗问题

3.2 功耗优化指南

3.2.1 APP功耗统计流程

 厂商耗电排行

在Android手机上,可以按照 设置 → 电池 → 电池百分比 步骤查看耗电排行

image.png

“耗电排行”显示的是自上次充满电后的一个累计情况,很难满足测试需求

关于耗电有一个很重要的API BatteryStatsHelper

首先, BatteryStatsHelper 通过createFromParcel方法统一耗电口径,然后,再通过addPhoneUsage,最后, 通过refreshStats显示最近数天甚至一周以上的 APP 的电量统计数据。详细源码调用链参考如下:

        private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
        private static BatteryStatsImpl getStats(IBatteryStats service) {
            try {
                ParcelFileDescriptor pfd = service.getStatisticsStream();
                if (pfd != null) {
                   //---------------------------------
                    try {
                    //---------------------------------
                        BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
                                .createFromParcel(parcel);
                                // 统计口径
                      //---------------------------------
                        return stats;
                    } catch (IOException e) {
                        Log.w(TAG, "Unable to read statistics stream", e);
                    }
                }
            } catch (RemoteException e) {
                Log.w(TAG, "RemoteException:", e);
            }
            return new BatteryStatsImpl();
        }
    }
    // 耗电数据集
       private void addPhoneUsage() {
            long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtime, mStatsType) / 1000;
            double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
                    * phoneOnTimeMs / (60*60*1000);
            if (phoneOnPower != 0) {
                BatterySipper bs = addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
            }
        }
        private void processMiscUsage() {
       //---------------------------------
            addPhoneUsage();
         //---------------------------------
        }
        // 有的 Android 设备系统可以显示最近数天甚至一周以上的 APP 的电量统计数据    
        public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
                long rawUptimeUs) {
            // Initialize mStats if necessary.
            // 计算APP的耗电
            processAPPUsage(asUsers);
            // 计算杂项的耗电
        }

 电量计算公式

Android APP中,可以使用以下代码获取设备当前的电量信息:

    // 获取电池管理器对象
    BatteryManager batteryManager = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE);
    // 获取当前电量
    int currentBatteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
    // 获取电池容量(需要 API Level 28 及以上支持)
    long batteryCapacity = batteryManager.getLongProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);

通过上述代码获取到的电量和电池容量可以使用上面提到的公式计算剩余电量。

注意,电池容量的单位为毫安时(mAh),需要将其转换为标准单位(例如,使用瓦时,1 毫安时 = 0.001 瓦时)。

电量计算公式是:

    模块电量(mAh)= 模块电流(mA)*模块耗时(h)

耗电器件模块主要是Android 设备的各种硬件模块,主要可以分为以下三类:

image.png

第一类,像 Camera/FlashLight/MediaPlayer/ 一般传感器等之类的模块,其工作功率基本和额定功率保持一致,所以模块电量的计算只需要统计模块的使用时长再乘以额定功率即可。

第二类,像 Wifi/Mobile/BlueTooth 这类数据模块,其工作功率可以分为几个档位。

比如,当手机的 Wifi 信号比较弱的时候,Wifi 模块就必须工作在比较高的功率档位以维持数据链路。

所以这类模块的电量计算有点类似于高功耗任务日常的电费计算,需要 “阶梯计费”。

第三类,也是最复杂的模块,CPU 模块除了每一个 CPU Core 需要像数据模块那样阶梯计算电量之外,CPU 的每一个集群(Cluster,一般一个集群包含一个或多个规格相同的 Core)也有额外的耗电,此外整个 CPU 处理器芯片也有功耗。

简单计算的话,CPU 电量 = SUM (各核心功耗) + 各集群(Cluster)功耗 + 芯片功耗 。

如果往复杂方向考虑的话,那么CPU 功耗还要考虑超频以及逻辑运行的信息熵损耗等电量损耗(Android 系统 CPU 的电量统计只计算到芯片功耗层)。

屏幕模块的电量计算就更麻烦了,很难把屏幕功耗合理地分配给各个 APP, 因此 Android 系统只是简单地计算 APP 屏幕锁(WakeLock)的持有时长,按固定系数增加 APP CPU 的统计时长,粗略地把屏幕功耗算进 CPU 里面。

最后,需要特别注意的是,以上提到的各种功率和时间在 Android 系统上的统计都是估算的,可想而知最终计算出来的电量数值可能与实际值相差巨大,Facebook 的工程师对此也有所吐槽:Mistrusting OS Level Battery Levels

 硬件电量统计

功率:power_profile.xml,Android 系统使用此文件来描述设备各个硬件模块的额定功率,包括上面提到的多档位功率和 CPU 电量算需要到的各种参数值。

时长:StopWatch & SamplingCounter,其中 StopWatch 是用来计算 APP 各种硬件模块的使用时长,而 SamplingCounter 则是用来采样统计 APP 在不同 CPU Core 和不同 CPUFreq 下的工作时长。

计算:PowerCalculators,每个硬件模块都有一个相应命名的 PowerCalculator 实现,主要是用来完成具体的电量统计算法。

存储:batterystats.bin,电量统计服务相关数据的持久化文件。

3.2.2 APP功耗配置文件

通常,power_profile.xml位于/system/framework/framework-res.apk中,这是一个android设备的系统apk,高功耗任务可以通过

adb  pull  /system/framework/framework-res.apk ./

获取当前系统的framework-res apk,这步不需要root即可进行,接着高功耗任务可以通过反编译工具,apktool或者jadx都可以,对该apk进行反编译处理,高功耗任务所需要的功耗文件就在 /res/xml/power_profile.xml 中。

3.2.3 APP功耗优化模式

Android的Doze模式是一种省电模式,旨在减少设备的能耗。当设备进入Doze模式时,Android会暂停一些不必要的后台活动,包括网络访问、同步、定位和其他不必要的服务。这样可以降低设备的能耗并延长电池寿命。Doze模式分为两种:一种是Light Doze,一种是Deep Doze。

Doze模式

Light Doze会在设备未使用时激活,比如用户长时间未操作设备或将设备放置不用,它会限制APP的后台活动和一些系统服务。

image.png

而Deep Doze则需要设备完全静止一段时间,只有当设备完全静止一段时间后,才会激活Deep Doze。在Deep Doze下,所有APP和系统服务都将被暂停,直到设备被唤醒。

image.png

需要注意的是,Doze模式并不会完全关闭设备,而只是将设备进入一种低功耗状态。当用户开始使用设备时,设备会自动退出Doze模式,并恢复正常的工作状态。

// PowerManager提供的两种判断当前是否处于Doze模式方法
public boolean isDeviceIdleMode(){
    try {
       return mService.isDeviceIdleMode();
    } catch (RemoteException e){、3603
       throw e.rethrowFromSystemServer();
    }
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
 public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
Deep Doze

系统处于息屏状态,并且30分钟不移动的情况下,就会进入到Deep Doze 模式,Deep Doze 机制中有七种状态,分别如下

//mState值,表示设备处于活动状态
private static final int STATE_ACTIVE = 0; 
//mState值,表示设备处于不交互状态,灭屏、静止
private static final int STATE_INACTIVE = 1; 
//mState值,表示设备刚结束不交互状态,等待进入IDLE状态
private static final int STATE_IDLE_PENDING = 2; 
//mState值,表示设备正在感应动作
private static final int STATE_SENSING = 3; 
//mState值,表示设备正在定位
private static final int STATE_LOCATING = 4; 
//mState值,表示设备处于空闲状态,也即Doze模式
private static final int STATE_IDLE = 5; 
//mState值,表示设备正处于Doze模式,紧接着退出Doze进入维护状态
private static final int STATE_IDLE_MAINTENANCE = 6;

image.png

  1. 当设备亮屏或者处于正常使用状态时其就为ACTIVE状态;
  2. ACTIVE状态下不充电且灭屏设备就会切换到INACTIVE状态;
  3. INACTIVE状态经过3分钟,期间检测没有打断状态的行为就切换到PRE_IDLE的状态;
  4. PRE_IDLE状态经过5分钟,期间无打断就进入到IDLE状态
  5. 进入IDLE状态会根据是否有网络连接选择进入WAITING_FOR_NETWORK还是进入MAINTENANCE窗口期,进入窗口期的时间为:5分钟,10分钟,最后稳定最长为15分钟
  6. 进入WAITING_FOR_NETWORK会持续5分钟后重新进入到IDLE状态
  7. 进入MAINTENANCE会解除耗电策略的限制,并在1分钟后重新进入到IDLE状态
Light Doze

从上面可以看到想要进入Doze模式的条件是很苛刻,需要在手机息屏并且没有移动的状态下才能进入,所以Android7.0开始引入了Light Doze,处于息屏状态,但仍处于移动状态可进入Light Doze,LightDoze有7个状态,分别如下:

//mLightState状态值,表示设备处于活动状态
private static final int LIGHT_STATE_ACTIVE = 0; //mLightState状态值,表示设备处于不活动状态
private static final int LIGHT_STATE_INACTIVE = 1; //mLightState状态值,表示设备进入空闲状态前,需要等待完成必要操作
private static final int LIGHT_STATE_PRE_IDLE = 3; //mLightState状态值,表示设备处于空闲状态,该状态内将进行优化
private static final int LIGHT_STATE_IDLE = 4; //mLightState状态值,表示设备处于空闲状态,要进入维护状态,先等待网络连接
private static final int LIGHT_STATE_WAITING_FOR_NETWORK = 5; //mLightState状态值,表示设备处于维护状态
private static final int LIGHT_STATE_IDLE_MAINTENANCE = 6;

image.png

根据上图,他们的转换关系总结如下:

  1. 当设备亮屏或者处于正常使用状态时其就为ACTIVE状态;
  2. ACTIVE状态下不充电且灭屏设备就会切换到INACTIVE状态;
  3. INACTIVE状态经过3分钟,期间检测没有打断状态的行为就切换到PRE_IDLE的状态;
  4. PRE_IDLE状态经过5分钟,期间无打断就进入到IDLE状态
  5. 进入IDLE状态会根据是否有网络连接选择进入WAITING_FOR_NETWORK还是进入MAINTENANCE窗口期,进入窗口期的时间为:5分钟,10分钟,最后稳定最长为15分钟
  6. 进入WAITING_FOR_NETWORK会持续5分钟后重新进入到IDLE状态

Standby模式

Android Standby模式是一种类似于Doze模式的省电模式,它会在设备没有活动时将设备置于低功耗状态。在Standby模式下,APP的后台活动和系统服务将被暂停,但仍可以使用特定的API允许部分后台活动和网络访问。

PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (powerManager.isIgnoringBatteryOptimizations(getPackageName())) {
   // 已经允许APP忽略电量优化
   // 开始执行后台高功耗任务和网络访问
   // ...
} else {
   // 请求允许APP忽略电量优化
   Intent intent = new Intent();
   intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
   intent.setData(Uri.parse("package:" + getPackageName()));
   startActivity(intent);
}

以上代码获取了PowerManager对象,并使用isIgnoringBatteryOptimizations()方法检查APP是否允许忽略电量优化。

如果已经允许,则可以开始执行后台高功耗任务和网络访问。如果没有允许,则需要使用ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent请求用户允许APP忽略电量优化。

需要注意的是,与Doze模式类似,启用Standby模式并不意味着所有后台活动和网络访问都将被暂停。

APP需要使用特定的API允许必要的后台活动和网络访问,以确保在Standby模式下能够正常工作。

3.2.4 APP功耗工作流程

BatteryStatsService 的工作流程大致可以分为两个部分:时长统计 & 功耗计算。

image.png

BatteryStatsService 时长统计流程
BatteryStats

BatteryStatsService 框架的核心是 BatteryStatsService 持有的一个叫 BatteryStats 的类,BatteryStats 又持有一个 Uid [] 数组,每一个 Uid 实例实际上对应一个 APP,当高功耗任务安装或者卸载 APP 的时候,BatteryStats 就会更新相应的 Uid 元素以保持最新的映射关系。同时 BatteryStats 持有一系列的 StopWatch 和 SamplingCounter,当 APP 开始使用某些硬件模块的功能时,BatteryStats 就会调用相应 Uid 的 StopWatch 或 SamplingCounter 来统计其硬件使用时长。

Wifi 模块

这里以 Wifi 模块来举例:当 APP 通过 WifiManager 系统服务调用 Wifi 模块开始扫描的时候,实际上会通过 WifiManager#startScan () --> WifiScanningServiceImp --> BatteryStatsService#noteWifiScanStartedFromSource () --> BatteryStats#noteWifiScanStartedLocked (uid) 等一连串的调用,通知 BatteryStats 开启 APP 相应 Uid 的 Wifi 模块的 StopWatch 开始计时。当 APP 通过 WifiManager 停止 Wifi 扫描的时候又会通过类似的流程调用 BatteryStats#noteWifiScanStoppedLocked (uid) 结束 StopWatch 的计时,这样一来就通过 StopWatch 完成 APP 对 Wifi 模块使用时长的统计。

BatteryStatsService 功耗计算流程
BatteryStatsHelper

具体电量计算方面,BatteryStats 是通过 BatteryStats 依赖的一个 BatteryStatsHelper 的辅助类来完成的。BatteryStatsHelper 通过组合使用 Uid 里的时长数据、PoweProfile 里的功率数据(power_profile.xml 的解析实例)以及具体各个模块的 PowerCalculator 算法,计算出每一个 APP 的综合电量消耗,并把计算结果保存在 BatterySipper [] 数组里(按计算值从大到小排序)。

image.png

image.png

WIFI模块

还是以 Wifi 模块来举例:当需要计算 APP 电量消耗的时候,BatteryStats 会通过调用 BtteryStatsHelper#refreshStats () --> #processAPPUsage () 来刷新 BatterySipper [] 数组以计算最新的 APP 电量消耗数据。而其中 Wifi 模块单独的电量统计就是在 processAPPUsage 方法中通过 WifiPowerCalculator 来完成的:Wifi 模块电量 = PowerProfile 预置的 Idle 功率 × Uid 统计的 Wifi Idle 时间 + 上行功率 × 上行时间 + 下行功率 × 下行时间。

public class WifiPowerCalculator extends PowerCalculator {
    @Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) {        ...
        app.wifiPowerMah = ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa)) / (1000 * 60 * 60);
    }
    public void noteWifiOnLocked() {
        if (!mWifiOn) {
            final long elapsedRealtime = mClocks.elapsedRealtime();
            final long uptime = mClocks.uptimeMillis();
            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG;
            addHistoryRecordLocked(elapsedRealtime,uptime);
            mWifiOn = true;
            mWifiOnTimer.startRunningLocked(elapsedRealtime);
            scheduleSyncExternalStatsLocked("wifi-off",ExternalStatsSync.UPDATE_WIFI);
        }
    }
}
Audio模块

image.png

Activity状态改变

image.png

3.3 功耗优化SOP

3.3.1 统计指标

  • app处于前台时,提供电流作为耗电指标
  • 通过采集app的CPU、流量和GPS等的使用,来计算出一个加权和作为耗电指标
  • 电池温度,作为衡量耗电的辅助参考

3.3.2 线下监测

3.3.2.1 监测专项

参数 参数说明
test_time 监测时间
power_time 监测耗时
power_env 功耗环境
power_consumption 功耗应用
data_src 数据来源
power_consumption_device 功耗设备
device_version 设备版本
total_battery_capacity 电池容量
battery_health_status 电池健康度
power_detector 功耗监测人

3.3.2.2 监测SOP

监测时间段内,在游戏回播业务播放同一个测试回播资源20分钟。

监测时间内,确保应用走完倍速、音量梯度调整等播放主流程。

监测时间内,测量时间段内确保游戏回播全程播放。

监测时间内,确保游戏回播不切换视频资源。

监测过程中,屏幕保持常亮状态,亮度中等。因为屏幕唤醒本身就会有耗电开销。

监测过程中,使用WiFi网络,因为蜂窝测试数据不具备公正性。

监测过程中,把没有用到的传感器关掉,且不要充电。

3.3.2.3 监测结论

器件名称 耗电占比
WIFI xxx%
Camera xxx%
Audio xxx%
Screen xxx%
NetWork xxx%
Display xxx%
合计 xxx%

异常: 电池温度骤增,发热明显

3.3.2.4 监测数据

3.3.2.4.1 手机硬件运行状况及电量消耗曲线图

image.png

  • 测量过程中,耗电1%需要的时间大概xxx分钟
  • 测量过程中,应用耗电曲线平滑,耗电波动稳定
  • 测量过程中,保持屏幕常亮,长时间开启Audio和Screen无法进入深度休眠,导致手机耗电严重
3.3.2.4.2 手机各应用及硬件耗能

image.png

以上数据为部分应用及硬件的耗电估算数据,仅供参考

3.3.2.5 问题分析

监测期间影响APP高功耗的三个原因是CPU、Audio和Screen三个组件,可从中寻求优化方案

3.3.3 功耗归因

  • 高CPU可以通过CPU菜单查看高耗CPU的堆栈
  • GPS(location)、Alarm和WakeLock使用在超过指定持有时间和频次后,会上报当时的采集堆栈

3.3.4 治理目标

  1. 消除主流手机的高功耗提醒
  2. 建立健全的功耗监控及防劣化体系

3.3.5 耗电治理

3.3.5.1 分模块治理

模块的耗电治理主要体现在下面几个方面

3.3.5.1.1 CPU

死循环函数、高频函数、高耗时函数和无效函数等不必要的CPU消耗或消耗较多的函数治理

CPU使用率较高的场景及业务治理

3.3.5.1.2 GPU&Display

过度绘制、过多的动画和不可见区域的动画等浪费GPU的场景治理

主动降低屏幕亮度,使用深色UI等方案降低屏幕电量消耗

3.3.5.1.3 NetWork

不影响业务和性能前提下,降低网络访问频率

Doze状态时减少无效的网络请求

3.3.5.1.4 GPS

对使用GPS的场景,如小程序等,合理的降低精度,减少请求频率

3.3.5.1.5 Audio、Camera、Video等

3.3.5.2 分状态治理

除了分模块治理,还针对状态进行治理,主要状态有这几种

3.3.5.2.1 Foreground状态

渲染场景优化

音视频等场景优化

……

3.3.5.2.2 Background状态

task任务降频或者丢弃

网络访问降频,适配Doze模式

减少CPU消耗较多的函数执行

减少GPS等高功耗场景

3.3.5.3 防腐化建设

为了能更好的进行治理,完善的功耗分析和监控体系是不可避免的,不然就会出现无的放矢的状态。在这一块主要建设的点有

3.3.5.3.1 CPU监控

前后台高CPU消耗场景监控,高CPU消耗线程监控

高频task、高耗时task和后台task监控

消耗较高、耗时较高的函数监控

3.3.5.3.2 GPU&Display监控

动画场景、过度绘制检测、View层级检测和屏幕电量消耗监控等

3.3.5.3.3 NetWork监控

Rust、OkHttp及其他网络请求场景,频率,消耗监控

后台网络访问监控

3.3.5.3.4 GPS监控

GPS使用场景、时长、电量消耗监控

3.3.5.3.5 Audio、Camera、Video等监控

使用场景、时长、电量消耗监控

3.3.5.3.6 Global电量监控

整体的电量消耗和不同场景的电量消耗,用来度量版本功耗的质量

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
3月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
74 20
Android经典面试题之图片Bitmap怎么做优化
|
2月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
64 4
|
1月前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
3月前
|
开发框架 Dart 前端开发
Android 跨平台方案对比之Flutter 和 React Native
本文对比了 Flutter 和 React Native 这两个跨平台移动应用开发框架。Flutter 使用 Dart 语言,提供接近原生的性能和丰富的组件库;React Native 则基于 JavaScript,具备庞大的社区支持和灵活性。两者各有优势,选择时需考虑团队技能和项目需求。
420 8
|
3月前
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
76 2
|
3月前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
3月前
|
Web App开发 网络协议 Android开发
Android平台一对一音视频通话方案大比拼:WebRTC VS RTMP VS RTSP,谁才是王者?
【9月更文挑战第4天】本文详细对比了在Android平台上实现一对一音视频通话时常用的WebRTC、RTMP及RTSP三种技术方案。从技术原理、性能表现与开发难度等方面进行了深入分析,并提供了示例代码。WebRTC适合追求低延迟和高质量的场景,但开发成本较高;RTMP和RTSP则在简化开发流程的同时仍能保持较好的传输效果,适用于不同需求的应用场景。
193 1
|
3月前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
87 0
|
3月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
160 0
|
4月前
|
存储 缓存 前端开发
安卓开发中的自定义控件实现及优化策略
【8月更文挑战第31天】在安卓应用的界面设计中,自定义控件是提升用户体验和实现特定功能的关键。本文将引导你理解自定义控件的核心概念,并逐步展示如何创建一个简单的自定义控件,同时分享一些性能优化的技巧。无论你是初学者还是有一定经验的开发者,这篇文章都会让你对自定义控件有更深的认识和应用。