找回密码
 会员注册
查看: 10|回复: 0

分析Android耗电原理后,飞书是这样做耗电治理的

[复制链接]

7

主题

0

回帖

22

积分

新手上路

积分
22
发表于 2024-9-30 17:08:17 | 显示全部楼层 |阅读模式
动手点关注干货不迷路飞书最近在进行耗电治理的专项优化,本篇文章将分析Android 系统的耗电原理,分享飞书的耗电治理规划。Android 耗电统计原理我们先了解一下 Android 系统是如何进行耗电的统计的,最精确的方式当然是使用电流仪来进行统计,但是正常状态下手机硬件不支持,所以系统统计耗电时,使用的基本是模块功率 × 模块耗时这个公式来进行的,但不同的模块还是会有一些差别。这种统计方式没法做到非常的精确,但是也基本能反应出各应用电量的消耗大小。模块功率我们先来看看模块功率,每个模块的耗电功率都是不一样的,以计算方式来分,又分为下面三类:第一类是 Camera、FlashLight、MediaPlayer 等一般传感器或设备的模块。其工作功率基本和额定功率保持一致,所以模块电量的计算只需要统计模块的使用时长再乘以额定功率即可。第二类是 Wifi、Mobile、BlueTooth 这类数据模块。其工作功率可以分为不同的档位,比如,当手机的 Wifi 信号比较弱的时候,Wifi 模块就必须工作在比较高的功率档位以维持数据链路,所以这类模块的电量计算有点类似于我们日常的电费计算,需要 “阶梯计费”。第三类是屏幕,CPU 模块。CPU 模块除了每一个 CPU Core 需要像数据模块那样阶梯计算电量之外,CPU 的每一个集群(Cluster,一般一个集群包含一个或多个规格相同的 Core)也有额外的耗电,此外整个 CPU 处理器芯片也有功耗。简单计算的话,CPU 电量 = SUM(各核心功耗)+ 各集群(Cluster)功耗 + 芯片功耗 。屏幕模块的电量计算就更麻烦了,很难把屏幕功耗合理地分配给各个 App, 因此 Android 系统只是简单地计算 App 屏幕锁(WakeLock)的持有时长,按固定系数增加 App CPU 的统计时长,粗略地把屏幕功耗算进 CPU 里面。每个模块的功耗大小位于 framework 的 power_profile.xml 文件中,由厂商自己提供,里面规定了每个模块的功耗,下面是一台一加 9 的测试机的 power_profile 文件:通过 apktook 反解出来的 power_profile 如下:文件中每个模块的对应说明,可以在谷歌提供的文档中看到详细的说明。https://source.android.com/devices/tech/power/values模块耗时了解了模块的功率,我们再来看看模块耗时,耗电模块在工作或者状态变更时,都会通知 batterystats 这个 service,而 BatteryStatsService 会调用 BatteryStats 对象进行耗时的统计,BatteryStats 的构造函数中会初始化各个模块的 Timer,用来进行耗时的统计,并将统计的数据存储在batterystats.bin文件中。我们来详细看看下面几个模块的是如何进行统计的:wifi 模块publicvoidnoteWifiOnLocked(){if(!mWifiOn){finallongelapsedRealtime=mClocks.elapsedRealtime();finallonguptime=mClocks.uptimeMillis();mHistoryCur.states2|=HistoryItem.STATE2_WIFI_ON_FLAG;addHistoryRecordLocked(elapsedRealtime,uptime);mWifiOn=true;mWifiOnTimer.startRunningLocked(elapsedRealtime);scheduleSyncExternalStatsLocked("wifi-off",ExternalStatsSync.UPDATE_WIFI);}}publicvoidnoteWifiOffLocked(){finallongelapsedRealtime=mClocks.elapsedRealtime();finallonguptime=mClocks.uptimeMillis();if(mWifiOn){mHistoryCur.states2&=~HistoryItem.STATE2_WIFI_ON_FLAG;addHistoryRecordLocked(elapsedRealtime,uptime);mWifiOn=false;mWifiOnTimer.stopRunningLocked(elapsedRealtime);scheduleSyncExternalStatsLocked("wifi-on",ExternalStatsSync.UPDATE_WIFI);}}Audio 模块publicvoidnoteAudioOnLocked(intuid){uid=mapUid(uid);finallongelapsedRealtime=mClocks.elapsedRealtime();finallonguptime=mClocks.uptimeMillis();if(mAudioOnNesting==0){mHistoryCur.states|=HistoryItem.STATE_AUDIO_ON_FLAG;if(DEBUG_HISTORY)Slog.v(TAG,"Audioonto:"+Integer.toHexString(mHistoryCur.states));addHistoryRecordLocked(elapsedRealtime,uptime);mAudioOnTimer.startRunningLocked(elapsedRealtime);}mAudioOnNesting++;getUidStatsLocked(uid).noteAudioTurnedOnLocked(elapsedRealtime);}publicvoidnoteAudioOffLocked(intuid){if(mAudioOnNesting==0){return;}uid=mapUid(uid);finallongelapsedRealtime=mClocks.elapsedRealtime();finallonguptime=mClocks.uptimeMillis();if(--mAudioOnNesting==0){mHistoryCur.states&=~HistoryItem.STATE_AUDIO_ON_FLAG;if(DEBUG_HISTORY)Slog.v(TAG,"Audiooffto:"+Integer.toHexString(mHistoryCur.states));addHistoryRecordLocked(elapsedRealtime,uptime);mAudioOnTimer.stopRunningLocked(elapsedRealtime);}getUidStatsLocked(uid).noteAudioTurnedOffLocked(elapsedRealtime);}Activity 状态改变publicvoidnoteActivityResumedLocked(intuid){uid=mapUid(uid);getUidStatsLocked(uid).noteActivityResumedLocked(mClocks.elapsedRealtime());}publicvoidnoteActivityPausedLocked(intuid){uid=mapUid(uid);getUidStatsLocked(uid).noteActivityPausedLocked(mClocks.elapsedRealtime());}publicstaticclassUidextendsBatteryStats.Uid{@OverridepublicvoidnoteActivityPausedLocked(longelapsedRealtimeMs){if(mForegroundActivityTimer!=null){mForegroundActivityTimer.stopRunningLocked(elapsedRealtimeMs);}}@OverridepublicvoidnoteActivityPausedLocked(longelapsedRealtimeMs){if(mForegroundActivityTimer!=null){mForegroundActivityTimer.stopRunningLocked(elapsedRealtimeMs);}}}通过上面三个例子可以看到,BatteryStats 在统计模块耗时,主要通过 Timer 来进行时长的统计,如 WifiOnTimer、AudioOnTimer、ForegroundActivityTimer,并且根据是否有 UID 来决定是否要统计到 UID 对应的数据中,系统在统计应用的耗电时,就是根据 UID 下各个模块的统计数据,来进行应用的耗电计算的。耗电计算当我们知道了每个模块的耗时,每个模块的功耗,那么就能计算各个模块的耗电量了,耗电量的计算在 BatteryStatsHelper 这个类中,下面详细看一下 Setting 中,应用耗电详情这个功能统计耗电的实现,Setting 中的耗电统计这个应用主要是调用了 BatteryStatsHelper 中的 refreshStats()函数。refreshStats 主要两个方法是 processappUsage 计算应用的耗电,记忆 processMiscUsage 计算杂项耗电,如 WIFI,通话等等。计算 app 的电量这里以 CameraPowerCalculator 这个简单的模块看看它是如何统计电量的:可以看到,里面只是简单的用了 totalTime * mCameraPowerOnAvg,mCameraPowerOnAvg 则是从 power_profile.xml 读取出来,其他教负责的如 CPU 模块的计算,感兴趣的可以自己看看,就不在这里说了。计算 misc 杂项的电量杂项电量用来统计一些没有特定 UID 的耗电,如蓝牙,屏幕等等,计算方式也是类似的。Android 的耗电优化策略Doze 模式Doze 模式也被称为低电耗模式,是针对整个系统进行一个耗电优化策略,进入 Doze 模式后会暂停所有的 Jobs,Alarm 和 Network 活动并推迟到窗口期执行,以及其他的一些限制来节约电量。Doze 模式的进入和退出Doze 模式分为 Deep Doze 和 Light Doze 两种模式,Doze 模式是在 Android6.0 引入的,也就是 Deep Doze 模式,Light Doze 是 Android7.0 引入的,两者进入的条件不一样,Deep Doze 的条件会更严格,下面先介绍 Deep Doze。Deep Doze系统处于息屏状态,并且 30 分钟不移动的情况下,就会进入到 Deep Doze 模式,Deep Doze 机制中有七种状态,分别如下://mState值,表示设备处于活动状态privatestaticfinalintSTATE_ACTIVE=0;//mState值,表示设备处于不交互状态,灭屏、静止privatestaticfinalintSTATE_INACTIVE=1;//mState值,表示设备刚结束不交互状态,等待进入IDLE状态privatestaticfinalintSTATE_IDLE_PENDING=2;//mState值,表示设备正在感应动作privatestaticfinalintSTATE_SENSING=3;//mState值,表示设备正在定位privatestaticfinalintSTATE_LOCATING=4;//mState值,表示设备处于空闲状态,也即Doze模式privatestaticfinalintSTATE_IDLE=5;//mState值,表示设备正处于Doze模式,紧接着退出Doze进入维护状态privatestaticfinalintSTATE_IDLE_MAINTENANCE=6;这七种状态的转换关系如下:根据上图,他们的关系总结如下:当设备亮屏或者处于正常使用状态时其就为 ACTIVE 状态;ACTIVE 状态下不充电且灭屏设备就会切换到 INACTIVE 状态;INACTIVE 状态经过 30 分钟,期间检测没有打断状态的行为 Doze 就切换到 IDLE_PENDING 的状态;然后再经过 30 分钟以及一系列的判断,状态切换到 SENSING;在 SENSING 状态下会去检测是否有地理位置变化,没有的话就切到 LOCATION 状态;LOCATION 状态下再经过 30s 的检测时间之后就进入了 Doze 的核心状态 IDLE;在 IDLE 模式下每隔一段时间就会进入一次 IDLE_MAINTANCE,此间用来处理之前被挂起的一些任务,这个时间段为一个小时,两个小时,四个小时,最后稳定为最长为六个小时IDLE_MAINTANCE 状态持续 5 分钟之后会重新回到 IDLE 状态;在除 ACTIVE 以外的所有状态中,检测到打断的行为如亮屏、插入充电器,位置的改变等状态就会回到 ACTIVE,重新开始下一个轮回。Light Doze从上面可以看到想要进入 Doze 模式的条件是很苛刻,需要在手机息屏并且没有移动的状态下才能进入,所以 Android7.0 开始引入了 Light Doze,处于息屏状态,但仍处于移动状态可进入 Light Doze,LightDoze 有 7 个状态,分别如下://mLightState状态值,表示设备处于活动状态privatestaticfinalintLIGHT_STATE_ACTIVE=0;//mLightState状态值,表示设备处于不活动状态privatestaticfinalintLIGHT_STATE_INACTIVE=1;//mLightState状态值,表示设备进入空闲状态前,需要等待完成必要操作privatestaticfinalintLIGHT_STATE_PRE_IDLE=3;//mLightState状态值,表示设备处于空闲状态,该状态内将进行优化privatestaticfinalintLIGHT_STATE_IDLE=4;//mLightState状态值,表示设备处于空闲状态,要进入维护状态,先等待网络连接privatestaticfinalintLIGHT_STATE_WAITING_FOR_NETWORK=5;//mLightState状态值,表示设备处于维护状态privatestaticfinalintLIGHT_STATE_IDLE_MAINTENANCE=6;这 6 个状态的转换关系如下:根据上图,他们的转换关系总结如下:当设备亮屏或者处于正常使用状态时其就为 ACTIVE 状态;ACTIVE 状态下不充电且灭屏设备就会切换到 INACTIVE 状态;INACTIVE 状态经过 3 分钟,期间检测没有打断状态的行为就切换到 PRE_IDLE 的状态;PRE_IDLE 状态经过 5 分钟,期间无打断就进入到 IDLE 状态进入 IDLE 状态会根据是否有网络连接选择进入 WAITING_FOR_NETWORK 还是进入 MAINTENANCE 窗口期,进入窗口期的时间为:5 分钟,10 分钟,最后稳定最长为 15 分钟进入 WAITING_FOR_NETWORK 会持续 5 分钟后重新进入到 IDLE 状态进入 MAINTENANCE 会解除耗电策略的限制,并在 1 分钟后重新进入到 IDLE 状态Doze 模式的优化策略了解了 Doze 模式的进入和退出策略,我们再来看一下在 Doze 模式中,会做哪些策略来优化耗电。Deep Doze当系统处于 Doze 模式下,系统和白名单之外的应用将受到以下限制:无法访问网络Wake Locks 被忽略AlarmManager 闹铃会被推迟到下一个 maintenance window 响应使用 setAndAllowWhileIdle 或 SetExactAndAllowWhileIdle 设置闹铃的闹钟则不会受到 Doze 模式的影响setAlarmClock 设置的闹铃在 Doze 模式下仍然生效,但系统会在闹铃生效前退出 Doze系统不执行 Wi-Fi/GPS 扫描;系统不允许同步适配器运行;系统不允许 JobScheduler 运行;Deep Doze 也提供了白名单,位于白名单中的应用可以:继续使用网络并保留部分 wake lockJob 和同步仍然会被推迟常规的 AlarmManager 闹铃也不会被触发Light DozeLight Doze 的限制没有 Deep Doze 这么严格,主要有下面几种:不允许进行网络访问不允许同步适配器运行不允许 JobScheduler 运行Deep Doze 和 Light Doze 的总结对比如下:Deep Doze 和 Light Doze 都需要达到一定条件后才能进入,并且进入后会定期提供窗口期来解除限制。它们的对比如下:Doze 模式实现原理前面已经了解了 Doze 模式了,下面就在通过 Android 中的 Doze 机制的源码,深入了解 Doze 的实现原理。Doze 机制相关的源码都在 DeviceIdleController 这个类中。进入 INACTIVE 状态从 ACTIVIE 进入到 INACTIVE 的入口方法是 becomeInactiveIfAppropriateLocked 中,当充电状态发生改变,屏幕息屏等条件触发时,都会调用该方法判断是否可进入 INACTIVE 状态。//deepdoze进入INACTIVE后的延时时间,这里的COMPRESS_TIME默认为falselonginactiveTimeoutDefault=(mSmallBatteryDevice15:30)*60*1000L;INACTIVE_TIMEOUT=mParser.getDurationMillis(KEY_INACTIVE_TIMEOUT,!COMPRESS_TIMEinactiveTimeoutDefaultinactiveTimeoutDefault/10));LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT=mParser.getDurationMillis(KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,!COMPRESS_TIME3*60*1000L:15*1000L);voidbecomeInactiveIfAppropriateLocked(){finalbooleanisScreenBlockingInactive=mScreenOn&(!mConstants.WAIT_FOR_UNLOCK||!mScreenLocked);//判断是否是灭屏且非充电状态if(!mForceIdle&(mCharging||isScreenBlockingInactive)){return;}if(mDeepEnabled){if(mQuickDozeActivated){//1.QuickDoze是Android10新引入的低电量的情况下,快速进入Doze的机制,会缩短进入Doze的耗时if(mState==STATE_QUICK_DOZE_DELAY||mState==STATE_IDLE||mState==STATE_IDLE_MAINTENANCE){return;}mState=STATE_QUICK_DOZE_DELAY;resetIdleManagementLocked();scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT,false);EventLogTags.writeDeviceIdle(mState,"noactivity");}elseif(mState==STATE_ACTIVE){mState=STATE_INACTIVE;resetIdleManagementLocked();longdelay=mInactiveTimeout;if(shouldUseIdleTimeoutFactorLocked()){delay=(long)(mPreIdleFactor*delay);}//2.执行时间为mInactiveTimeout延时的任务,这里是30分钟scheduleAlarmLocked(delay,false);EventLogTags.writeDeviceIdle(mState,"noactivity");}}if(mLightState==LIGHT_STATE_ACTIVE&mLightEnabled){mLightState=LIGHT_STATE_INACTIVE;resetLightIdleManagementLocked();//3.执行时间为LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT延时的任务,这里是3分钟scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);EventLogTags.writeDeviceIdleLight(mLightState,"noactivity");}}从源码中可以看到 Deep Doze,Light Doze 的处理都在这里,并且这里还有一个 Quick Doze,它是 Android 10 引入,能在低电量情况下快速进入 Doze 的机制。我们接着看 INACTIVE 向下一个状态的改变:Deep Doze 通过scheduleAlarmLocked(delay, false)向下一个状态转变,在这个时间过程中,有开屏,充电等操作,都会导致状态转换失败Light Doze 通过scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT)向下一个状态改变,同样在开屏和充电状态下,都会导致进入下一个状态失败从 INACTIVE 状态开始,Light Doze 和 Deep Doze 转换的入口就不一样了,所以下面会分开讲解。Deep Doze1. 从 INACTIVE 进入 STATE_IDLE_PENDINGbecomeInactiveIfAppropriateLocked 函数中将 mState 设置为 STATE_INACTIVE,然后调用 scheduleAlarmLocked 设置了一个 30 分钟的定时任务,它的逻辑实现如下。voidscheduleAlarmLocked(longdelay,booleanidleUntil){if(mMotionSensor==null){//如果没有运动传感器,则返回,因为无法判断设备是否保持静止if(mMotionSensor==nullr){return;}//设置DeepDoze的定时AlarmmNextAlarmTime=SystemClock.elapsedRealtime()+delay;if(idleUntil){mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,mNextAlarmTime,"DeviceIdleController.deep",mDeepAlarmListener,mHandler);}else{mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,mNextAlarmTime,"DeviceIdleController.deep",mDeepAlarmListener,mHandler);}}privatefinalAlarmManager.OnAlarmListenermDeepAlarmListener=newAlarmManager.OnAlarmListener(){@OverridepublicvoidonAlarm(){synchronized(DeviceIdleController.this){///每次Doze状态转换都会在该方法中进行stepIdleStateLocked("s:alarm");}}};Deep Doze 的 scheduleAlarmLocked 定时任务触发后,会回调 onAlarm,执行 stepIdleStateLocked 函数。voidstepIdleStateLocked(Stringreason){finallongnow=SystemClock.elapsedRealtime();//说明1小时内有Alarm定时时间到,暂不进入IDLE状态,30min后再进入if((now+mConstants.MIN_TIME_TO_ALARM)>mAlarmManager.getNextWakeFromIdleTime()){if(mState!=STATE_ACTIVE){//将当前设备变为活动状态,LightDoze和DeepDoze都为Active状态becomeActiveLocked("alarm",Process.myUid());becomeInactiveIfAppropriateLocked();}return;}switch(mState){caseSTATE_INACTIVE://启动SensorstartMonitoringMotionLocked();//设置STATE_IDLE_PENDING状态时长的定时Alarm,30minsscheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT,false);mNextIdlePendingDelay=mConstants.IDLE_PENDING_TIMEOUT;//5minsmNextIdleDelay=mConstants.IDLE_TIMEOUT;//60mins//此时状态变为PENDING状态mState=STATE_IDLE_PENDING;break;caseSTATE_IDLE_PENDING://此时状态变为SENSING状态mState=STATE_SENSING;//设置STATE_SENSING状态超时时长的定时Alarm,DEBUG1:4minsscheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);//取消通用位置更新和GPS位置更新cancelLocatingLocked();mNotMoving=false;mLocated=false;mLastGenericLocation=null;mLastGpsLocation=null;//开始检测是否有移动mAnyMotionDetector.checkForAnyMotion();break;caseSTATE_SENSING://取消用于STATE_SENSING状态超时时长的AlarmcancelSensingTimeoutAlarmLocked();//此时状态变为LOCATINGmState=STATE_LOCATING;//设置STATE_LOCATING状态时长的AlarmscheduleAlarmLocked(mConstants.LOCATING_TIMEOUT,false);//DEBUG15:30//请求通用位置if(mLocationManager!=null&mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER)!=null){mLocationManager.requestLocationUpdates(mLocationRequest,mGenericLocationListener,mHandler.getLooper());mLocating=true;}else{mHasNetworkLocation=false;}//请求GPS位置if(mLocationManager!=null&mLocationManager.getProvider(LocationManager.GPS_PROVIDER)!=null){mHasGps=true;mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,1000,5,mGpsLocationListener,mHandler.getLooper());mLocating=true;}else{mHasGps=false;}//如果true,则break,因为在Location的Listener中会进入下一个状态,//否则进入下一步状态if(mLocating){break;}caseSTATE_LOCATING://取消DeepDoze的AlarmcancelAlarmLocked();//取消位置更新cancelLocatingLocked();//Sensor停止检测mAnyMotionDetector.stop();caseSTATE_IDLE_MAINTENANCE://设置STATE_IDLE状态时长的定时Alarm,到时后将退出IDLE状态scheduleAlarmLocked(mNextIdleDelay,true);//设置下次IDLE时间mNextIdleDelay=(long)(mNextIdleDelay*mConstants.IDLE_FACTOR);mNextIdleDelay=Math.min(mNextIdleDelay,mConstants.MAX_IDLE_TIMEOUT);if(mNextIdleDelaymConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET){mCurIdleBudget=mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;}//设置一个定时器,到达时间后用来处理LightDoze处于维护状态的操作scheduleLightAlarmLocked(mCurIdleBudget);mLightState=LIGHT_STATE_IDLE_MAINTENANCE;//进入维护状态addEvent(EVENT_LIGHT_MAINTENANCE);//处理LightDoze进入Maintenance状态后的操作mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);}else{//将LightDoze模式置为LIGHT_STATE_WAITING_FOR_NETWORK,//在进入维护状态前需要获取网络//设置一个定时器,到达时间后用来处理LightDoze处于//WAITING_FOR_NETWORK状态的操作scheduleLightAlarmLocked(mNextLightIdleDelay);//600000,5minsmLightState=LIGHT_STATE_WAITING_FOR_NETWORK;EventLogTags.writeDeviceIdleLight(mLightState,reason);}break;}}从代码中可以看到,case 为 LIGHT_STATE_INACTIVE 的处理逻辑中,做了这几件事:将当前状态设置为 LIGHT_STATE_PRE_IDLE;并发送一个 3 分钟的闹钟,准备进入下一个状态。后续状态也全部是通过scheduleLightAlarmLocked来设置定时任务,然后在stepLightIdleStateLocked函数中处理状态的转换和对应状态的逻辑。2. 从 LIGHT_STATE_PRE_IDLE 进入 LIGHT_STATE_IDLE3. 从 LIGHT_STATE_IDLE_MAINTENANCE 进入 LIGHT_STATE_IDLELIGHT_STATE_PRE_IDLE 和 LIGHT_STATE_IDLE_MAINTENANCE 的下一个状态都是 LIGHT_STATE_IDLE,所以他们的处理也在同一个入口。LIGHT_IDLE_TIMEOUT=mParser.getDurationMillis(KEY_LIGHT_IDLE_TIMEOUT,!COMPRESS_TIME5*60*1000L:15*1000L);LIGHT_MAX_IDLE_TIMEOUT=mParser.getDurationMillis(KEY_LIGHT_MAX_IDLE_TIMEOUT,!COMPRESS_TIME15*60*1000L:60*1000L);voidstepLightIdleStateLocked(Stringreason){//如果mLigthSate为LIGHT_STATE_OVERRIDE,说明DeepDoze处于Idle状态,由//DeepDoze将LightDoze覆盖了,因此不需要进行LightDoze了if(mLightState==LIGHT_STATE_OVERRIDE){return;}switch(mLightState){……caseLIGHT_STATE_PRE_IDLE:caseLIGHT_STATE_IDLE_MAINTENANCE:if(mMaintenanceStartTime!=0){//维护状态的时长longduration=SystemClock.elapsedRealtime()-mMaintenanceStartTime;if(durationuserHistory=getUserHistory(userId);AppUsageHistoryappUsageHistory=getPackageHistory(userHistory,packageName,elapsedRealtime,false);if(appUsageHistory==null)returnscreenTimeThresholds.length-1;//app最后一次亮屏使用到现在,已经有多久的亮屏时间longscreenOnDelta=getScreenOnTime(elapsedRealtime)-appUsageHistory.lastUsedScreenTime;//app最后一次使用到现在的时间点longelapsedDelta=getElapsedTime(elapsedRealtime)-appUsageHistory.lastUsedElapsedTime;for(inti=screenTimeThresholds.length-1;i>=0;i--){if(screenOnDelta>=screenTimeThresholds[i]&elapsedDelta>=elapsedTimeThresholds[i]){returni;}}return0;}App 耗电分析Battery HistorianAndroid 官方提供了 Battery Historian 来进行电量使用的分析,Battery Historian 图表会显示一段时间内与电源相关的事件。从上面的图也可以看到,进入到 Doze 后,BLE scanning,GPS 等就无行为了,并且 cpu,wakelock 等活动的频率也变低了。我们还能通过 Battery Historian 获取应用的:在设备上的估计耗电量网络信息唤醒锁定次数服务进程信息官方文档已经讲的非常详细,就不在这儿细说了:https://developer.android.com/topic/performance/power/setup-battery-historianhl=zh-cnSlardarSlardar 电量相关的统计指标项包括:app 处于前台时,提供电流作为耗电指标通过采集 app 的 cpu、流量和 gps 等的使用,来计算出一个加权和作为耗电指标电池温度,作为衡量耗电的辅助参考归因项有:高 CPU 可以通过 cpu 菜单查看高耗 CPU 的堆栈gps(location),alarm 和 wakelock 使用在超过指定持有时间和频次后,会上报当时的采集堆栈虽然 Slardar 有上报很多功耗相关指标,但是目前还只能作为整体功耗的参考,并且很多指标波动起伏大,没法对更细化的治理提供帮助。飞书耗电治理治理目标消除主流手机的高功耗提醒建立健全的功耗监控及防劣化体系治理方案在前面我们已经知道耗电=模块功率 × 模块耗时,所以治理本质就是在不影响性能和功能的情况下,减少飞书中所使用到的模块的耗时,并且我们了解了系统进行耗电优化的策略,在飞书的耗电治理中,也可以同样的参考对应的策略。治理方案主要分为监控的完善和耗电的治理。功耗治理为了能体系化地进行功耗治理,这里分为了针对耗电模块进行治理和针对状态进行执行两大类。分模块治理模块的耗电治理主要体现在下面几个方面:1.CPU死循环函数,高频函数,高耗时函数,无效函数等不必要的 cpu 消耗或消耗较多的函数治理cpu 使用率较高的场景及业务治理2.GPU 和 Display过度绘制,过多的动画,不可见区域的动画等浪费 GPU 的场景治理主动降低屏幕亮度,使用深色 UI 等方案降低屏幕电量消耗3.网络不影响业务和性能前提下,降低网络访问频率Doze 状态时减少无效的网络请求4.GPS对使用 GPS 的场景,如小程序等,合理的降低精度,减少请求频率5.Audio、Camera、Video 等项除了分模块治理,还针对状态进行治理,主要状态有这几种:分状态治理1.前台状态渲染场景优化音视频等场景优化……2.后台状态task 任务降频或者丢弃网络访问降频,适配 Doze 模式减少 cpu 消耗较多的函数执行减少 gps 等高功耗场景完善功耗分析和监控体系为了能更好地进行治理,完善的功耗分析和监控体系是不可避免的,不然就会出现无的放矢的状态。在这一块主要建设的点有:1. 完善的 CPU 消耗监控前后台高 cpu 消耗场景监控,高 cpu 消耗线程监控(slardar 已有)高频 task,高耗时 task,后台 task 监控(已有)消耗较高,耗时较高的函数监控2. GPU 和 Display 消耗监控动画场景,过度绘制检测,View 层级检测,屏幕电量消耗监控等3. 网络Rust,OkHttp 及其他网络请求场景,频率,消耗监控后台网络访问监控4. GPSGPS 使用场景,时长,电量消耗监控5. Audio、Camera、Video使用场景,时长,电量消耗监控6. 整体和场景的电量消耗飞书整体的电量消耗和不同场景的电量消耗,用来度量版本功耗的质量加入我们我们是飞书 Android 客户端品质团队。当前团队处于组建初期,各个方向都有非常多的岗位开放,团队氛围不卷,上升空间大,可 base 北京、杭州和深圳。欢迎对性能优化和稳定性建设有兴趣的同学加入,大家一起把 lark(字节内最复杂的一款产品)打造成全球性能最好的办公套件类产品(微软 office 套件,谷歌 G-suite)。招聘链接:Lark Android 技术专家岗位https://people.bytedance.net/hire/referral/positionjob_post_id=6830690441337440520Lark Android 工程师岗位https://people.bytedance.net/hire/referral/positionjob_post_id=6811324588367268110Lark Android 开发实习生https://people.bytedance.net/hire/referral/positionjob_post_id=6916040393788377358 点击“阅读原文”了解岗位详情!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-14 19:57 , Processed in 0.535167 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表