连接电脑USB充电,50%提示需要4H充满,55%需要5H充满

分析:
电量充满时间 = 充一格电所需的时间 x (100-当前电量)
充一格电所需的时间 = 充电总时间/充电格数

mBatteryLevel = 45 时候 mBatteryPlugged = false 变为true ,开始充电,一个满格的时间计算从46开始计算
从46开始 20:27:02.916 到50 20:47:36.202,共耗时1234秒
根据公式:1234/4=308.5 308.5X50=15425 s_
连续充电到 55 21:20:11.410,共耗时3189 s
_根据公式:3189/9=354.3 354.3X45 = 15945s 大于上面的数据

结论:采用USB充电,存在电流不稳且有边充电边使用情况,都会影响计算数值。建议采用原装充电器+充电线验证。

当设备插入充电且电量发生变化一段时间后,在Settings->Battery中和锁屏界面都会有”还需多长时间充满”提示,这里来分析下这个时长是如何获得的。

源码分析

Settings中调用接口:
packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryInfo.java

Final long chargeTime = stats.computeChargeTimeRemaining(elapsedRealtimeUs);

KeyGuard中调用接口:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java

mBatteryInfo = IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME));
chargingTimeRemaining = mBatteryInfo.computeChargeTimeRemaining();

以上两种方式中,KeyGuard通过获得BatteryStatsService对象开始调用方法,Settings中则通过BatteryStatsImpl对象调用。但最终都调用的是BatteryStatsImpl中的 ++computeChargeTimeRemaining(long)++ 方法,先看BatteryStatsService中的 ++computeChargeTimeRemaining()++ 方法:
frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java

public long computeChargeTimeRemaining() {synchronized (mStats) {long time = mStats.computeChargeTimeRemaining(SystemClock.elapsedRealtime());return time >= 0 ? (time/1000) : time;}
}

在这个方法中,获取了当前系统运行时间后,作为参数调用BatterStatsImpl中的方法,得到计算值并返回,因此,这个方法就是核心方法。该方法如下:

@Override
public long computeChargeTimeRemaining(long curTime) {Slog.d(TAG,"computeChargeTimeRemaining()---start,mOnBattery="+mOnBattery);//放电情况下直接返回-1 if (mOnBattery) {// Not yet working.return -1;//mChargeStepTracker是充电记录器//mNumStepDurations表示充电量的步数纸盒,每次开始充电,为0,之后每充一个电,该值加1if (mChargeStepTracker.mNumStepDurations < 1) {return -1;}//获取充一个电量的时间long msPerLevel = mChargeStepTracker.computeTimePerLevel();if (msPerLevel <= 0) {return -1;}//充一个电的时间*距离充满还有多少电得到预估的时间return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000;
}

在这个方法中,首先计算充一个电所需的时长,然后通过这个时长x充满所需多少电得到总时长返回。

整个逻辑思路非常简单,但是其中的算法和逻辑相对来说比较难以理解,尤其是mChargeStepTracker这个对象是做什么的。因此,这里在分析是如何计算充一个电所需时长之前,先缕清楚电池信息是如何流转到BatteryStateImpl中的,这个流程搞清楚之后,之后的逻辑就不那么吃力了。

首先来看看上述方法中的mChargeStepTracker对象,它是LevelStepTracker类的一个实例,从名称来看就知道是负责电量等级跟踪的,这里先看下它的三个属性和构造方法:

public static final class LevelStepTracker {public long mLastStepTime = -1;//上次充了一个电时的时间public int mNumStepDurations;//充一个电的步数和,如电量由1充到2时该值为1,由2冲到3时该值为2,...public final long[] mStepDurations;//充一个电所用时长的数组public LevelStepTracker(int maxLevelSteps) {mStepDurations = new long[maxLevelSteps];}public LevelStepTracker(int numSteps, long[] steps) {mNumStepDurations = numSteps;mStepDurations = new long[numSteps];System.arraycopy(steps, 0, mStepDurations, 0, numSteps);}

mChargeStepTracker对象的初始化如下:

//实例化mChargeStepTracker
final LevelStepTracker mChargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS);//200

还有一个重要方法,会在稍后进行分析。
接下来看看computeChargeTimeRemaining()中所需值的来源。

我们知道,在Framework层中和电池相关的有两个服务类,BatteryService是System服务之一,负责监听电池数据,它会获取由healthd上报的电池信息。BatteryStatsService则由AMS中启动,负责统计电池使用数据。当BatteryService中获取到新的电池数据时,将会通过setBatteryState()方法通知给BatteryStatsService以进行统计,因此,我们就从这个方法入手,看看当有电池数据上报时,它是如何处理的。

首先来看BatteryService中接收healthd中的上报信息后,通过setBatteryState()方法将电池数据传送给BatteryStatsService:

/frameworks/base/services/core/java/com/android/server/BatteryService.java

// Let the battery stats keep track of the current level.
try {mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth,mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature,mBatteryProps.batteryVoltage, mBatteryProps.batteryChargeCounter,mBatteryProps.batteryFullCharge);
} catch (RemoteException e) {// Should never happen.
}

而BatteryStatsService中的setBatteryState()方法又调用了BatteryStatsImpl的setBatteryStateLocked()方法,并在BatteryStatsImpl中进行最终的处理。setBatteryStateLocked()方法比较庞大,详细的解释都在注释中,代码如下:

public void setBatteryStateLocked(int status, int health, int plugType, int level,int temp, int volt, int chargeUAh, int chargeFullUAh) {//温度没有带符号位,如果存在负值,一律按0处理temp = Math.max(0, temp);//是否插有充电器,true表示没有插入任何充电器final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;//获取当前系统时间final long uptime = mClocks.uptimeMillis();final long elapsedRealtime = mClocks.elapsedRealtime();//开机第一次该值为false,之后恒为true//因此此处做一些开机后的赋值,这些赋值将会在之后的逻辑中被覆盖if (!mHaveBatteryLevel) {mHaveBatteryLevel = true;// We start out assuming that the device is plugged in (not// on battery).  If our first report is now that we are indeed// plugged in, then twiddle our state to correctly reflect that// since we won't be going through the full setOnBattery().//插入充电器时为false,不插入充电器时为trueif (onBattery == mOnBattery) {//进行置位操作if (onBattery) {//未插入充电器,移除标志mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;} else {//插入充电器,设置标志mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;}}// Always start out assuming charging, that will be updated later.//第一次进入时,假设当前处于充电状态,设置一个标志mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;mHistoryCur.batteryStatus = (byte)status;//电池状态mHistoryCur.batteryLevel = (byte)level;//电量等级mHistoryCur.batteryChargeUAh = chargeUAh;//当前电量//初始化在系统运行期间,所充的最大电量值、最小电量值、上一次充电的电量值//如从23充电至50,则以上三值将分别为23,50,50.mMaxChargeStepLevel = mMinDischargeStepLevel =mLastChargeStepLevel = mLastDischargeStepLevel = level;mLastChargingStateLevel = level;//当前电量不等于新上报电量值 || 是否插入充电器有发生改变} else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {//如果充满电且未插入充电线,记录DailyItemrecordDailyStatsIfNeededLocked(level >= 100 && onBattery);}//将原来电池状态值保存在局部变量中int oldStatus = mHistoryCur.batteryStatus;if (onBattery) {//没有插入充电器,也即现在开始要放电了,标记当前电量为放电时电量mDischargeCurrentLevel = level;//记录在历史数据中if (!mRecordingHistory) {mRecordingHistory = true;startRecordingHistory(elapsedRealtime, uptime, true);}} else if (level < 96) {//电量值小于96时if (!mRecordingHistory) {mRecordingHistory = true;//记录在历史数据中startRecordingHistory(elapsedRealtime, uptime, true);}}//将level设置为当前电池电量mCurrentBatteryLevel = level;//初始化,该值表示放电中时插入充电时刻的电量if (mDischargePlugLevel < 0) {mDischargePlugLevel = level;}//"是否插入充电器"发生了改变if (onBattery != mOnBattery) {//将当前电池信息设置到mHistoryCur中mHistoryCur.batteryLevel = (byte)level;mHistoryCur.batteryStatus = (byte)status;mHistoryCur.batteryHealth = (byte)health;mHistoryCur.batteryPlugType = (byte)plugType;mHistoryCur.batteryTemperature = (short)temp;mHistoryCur.batteryVoltage = (char)volt;//电池已充UAh数,如果小于上次记录值,说明在放电if (chargeUAh < mHistoryCur.batteryChargeUAh) {// Only record discharges//获取消耗的电量final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;//累加消耗了多少电量,mDischargeCounter是LongSamplingCounter的一个实例,用来统计放电总量mDischargeCounter.addCountLocked(chargeDiff);//累加在灭屏状态下消耗了多少电量mDischargeScreenOffCounter.addCountLocked(chargeDiff);if (isScreenDoze(mScreenState)) {//累加在Doze状态下消耗了多少电量,Doze状态下也处于灭屏状态,但cpu未休眠mDischargeScreenDozeCounter.addCountLocked(chargeDiff);}}//将已充电量(UAh为单位)赋值给mHistoryCur属性值mHistoryCur.batteryChargeUAh = chargeUAh;//用来设置OnBattery相关setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);} else {//"是否插入充电器"没有发生改变boolean changed = false;//电量发生改变if (mHistoryCur.batteryLevel != level) {mHistoryCur.batteryLevel = (byte)level;changed = true;// TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record// which will pull external stats.//开始拉取外部设备(Wifi、BT、modem)的电池信息scheduleSyncExternalStatsLocked("battery-level", ExternalStatsSync.UPDATE_ALL);}//电池状态发生改变if (mHistoryCur.batteryStatus != status) {mHistoryCur.batteryStatus = (byte)status;changed = true;}//电池健康状态发生改变if (mHistoryCur.batteryHealth != health) {mHistoryCur.batteryHealth = (byte)health;changed = true;}//充电类型发生改变if (mHistoryCur.batteryPlugType != plugType) {mHistoryCur.batteryPlugType = (byte)plugType;changed = true;}//电池温度升高10度或降低10度if (temp >= (mHistoryCur.batteryTemperature+10)|| temp <= (mHistoryCur.batteryTemperature-10)) {mHistoryCur.batteryTemperature = (short)temp;changed = true;}//充电电压升高或者降低20vif (volt > (mHistoryCur.batteryVoltage+20)|| volt < (mHistoryCur.batteryVoltage-20)) {mHistoryCur.batteryVoltage = (char)volt;changed = true;}//已充电数升高或者降低10mAhif (chargeUAh >= (mHistoryCur.batteryChargeUAh+10)|| chargeUAh <= (mHistoryCur.batteryChargeUAh-10)) {if (chargeUAh < mHistoryCur.batteryChargeUAh) {// Only record dischargesfinal long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;mDischargeCounter.addCountLocked(chargeDiff);mDischargeScreenOffCounter.addCountLocked(chargeDiff);if (isScreenDoze(mScreenState)) {mDischargeScreenDozeCounter.addCountLocked(chargeDiff);}}mHistoryCur.batteryChargeUAh = chargeUAh;changed = true;}//modeBits是一个标志位,long类型共64bitlong modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)//64-57位存储mInitStepMode| (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)//56-49存储mModStepMode| (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);//48-40存储当前电量//没有插入充电器,即放电if (onBattery) {//在这个方法中会根据是否charging改变发送BatteryManager.ACTION_CHARGING/DISCHARGING广播changed |= setChargingLocked(false);//上次放电时的电量!=当前新电量&&放电过程中最小电量>当前新电量if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {//使用放电跟踪器记录放电时电量步数mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,modeBits, elapsedRealtime);//使用放电跟踪器记录放电时电量步数mDailyDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,modeBits, elapsedRealtime);mLastDischargeStepLevel = level;mMinDischargeStepLevel = level;mInitStepMode = mCurStepMode;mModStepMode = 0;}} else {//说明插有充电器if (level >= 90) {// If the battery level is at least 90%, always consider the device to be// charging even if it happens to go down a level.//如果电量大于等于90,则一律认为设备正在充电changed |= setChargingLocked(true);//上次充电时电量mLastChargeStepLevel = level;} if (!mCharging) {//没有进行充电if (mLastChargeStepLevel < level) {// We have not reporting that we are charging, but the level has now// gone up, so consider the state to be charging.//设置为放电changed |= setChargingLocked(true);mLastChargeStepLevel = level;}} else {if (mLastChargeStepLevel > level) {//如果上次充电时电量大于当前level,说明是没有进行充电changed |= setChargingLocked(false);mLastChargeStepLevel = level;}}//这三个值不等,则说明没有进入以上if-else中,一般在充电且小于90时,每充一个level//的电都会进入以下方法,进行记录if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {//使用充电跟踪器记录充电时电量步数,这是计算电量充满时间的关机mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,modeBits, elapsedRealtime);//每日充电跟踪器记录充电时电量步数mDailyChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,modeBits, elapsedRealtime);//上次充一个电时的电量mLastChargeStepLevel = level;mMaxChargeStepLevel = level;mInitStepMode = mCurStepMode;mModStepMode = 0;}}if (changed) {//如果电池状态发生改变//添加历史记录addHistoryRecordLocked(elapsedRealtime, uptime);}}//如果插入充电器且电池状态值为充满状态,说明此时点已经充满if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {mRecordingHistory = DEBUG;}// ...........
}

这个方法可以说是非常大了,其中这个方法中还调用了如setOnBatteryLocked()等方法,在这篇文章中就先不进行分析了。

在以上方法中,针对于计算还需多久充满这个场景,需要清楚以下一个对象及属性即可:

  • 1.onBattery:该值表示是否插有充电装置(USB,AC等),没有插入时为true,因此充电时该值为false。
  • 2.mChargeStepTracker:LevelStepTracker类的一个实例,用来记录充电步数的一个跟踪器,计算时间时通过它记录的数据实现。
  • 3.mChargeStepTracker.addLevelSteps():每充一格电,都会使用这个方法记录充电时长和步数。

在这个方法中,有如下一句:

mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,modeBits, elapsedRealtime);

这里正是计算电池充满时间的关键方法,现在就来看看当有新的电量值时,mChargeStepTracker.addLevelSteps()中做了什么:

public void addLevelSteps(int numStepLevels, long modeBits, long elapsedRealtime) {//暂存mNumStepDurations值,这个值已说过,表示充电量的步数和,每次开始充电,为0,之后每充一个电,该值加1int stepCount = mNumStepDurations;//暂存上次充一个电时刻的时间final long lastStepTime = mLastStepTime;//上次充满一个电时刻的时间>=0 && 当前电量-上次充电电量>0if (lastStepTime >= 0 && numStepLevels > 0) {//暂存每充一个电所需时长的数组final long[] steps = mStepDurations;//得到上次和这次的时长,即每充一个电量的时长long duration = elapsedRealtime - lastStepTime;//numStepLevels是每充一个电时的步数,所以是1,如12->13,numStepLevels=13-12=1for (int i=0; i<numStepLevels; i++) {//数组每次往后移动一位,会将新值写到step[0]System.arraycopy(steps, 0, steps, 1, steps.length-1);//时长除以步长,每充一个电的时长long thisDuration = duration / (numStepLevels-i);duration -= thisDuration;if (thisDuration > STEP_LEVEL_TIME_MASK) {thisDuration = STEP_LEVEL_TIME_MASK;}//将时长和modeBits信息保存在数据第一个元素,同时modeBits的值为高位41-64位的值//在获取时长时,将通过steps[i] & STEP_LEVEL_TIME_MASK将高48位清0steps[0] = thisDuration | modeBits;}stepCount += numStepLevels;//每次将充一个电时的步数累加if (stepCount > steps.length) {stepCount = steps.length;}}//得到累加后新的步数mNumStepDurations = stepCount;//标记上次充一个电的时间mLastStepTime = elapsedRealtime;
}

在这个方法中,每次充一格电,都将会得到mStepDurations数组和mNumStepDurations,mStepDurations[0]中保存的是每次充一格电所需的时间,mNumStepDurations则表示每次充电所经历的步数之和,每充一格电,该值将会加1.

现在,我们再回到直接计算电量充满时间的computeChargeTimeRemaining()方法中,就容易理解多了,从之前的代码中来看,其计算公式可以用如下公式来表示:

电量充满时间 = 充一格电所需的时间 x (100-当前电量)

充一格电所需的时间 = 充电总时间/充电格数

充一格电所需的时间通过mChargeStepTracker.computeTimePerLevel()获取,我们看看这个方法:

public long computeTimePerLevel() {//充电步数数组final long[] steps = mStepDurations;//充电步数和final int numSteps = mNumStepDurations;// For now we'll do a simple average across all steps.//说明此时没有完成充一个电if (numSteps <= 0) {return -1;}long total = 0;for (int i=0; i<numSteps; i++) {//高位清零,得到实际时间,因为在计算时使用了step[0]=thisDuration | modeBits.total += s

Android源码之剩余充电时间计算相关推荐

  1. 一键抠图Portrait Matting人像抠图 (C++和Android源码)

    一键抠图Portrait Matting人像抠图 (C++和Android源码) 目录 一键抠图Portrait Matting人像抠图 (C++和Android源码) 1. 项目介绍: 2. MOD ...

  2. Android 源码 输入系统之 InputReader

    InputReaderThread 线程负责读取事件.InputReaderThread 启动后会执行 threadLoop 函数.threadLoop 函数返回 true,InputReaderTh ...

  3. Android源码解析(一)动画篇-- Animator属性动画系统

    Android源码解析-动画篇 Android源码解析(一)动画篇-- Animator属性动画系统 Android源码解析(二)动画篇-- ObjectAnimator Android在3.0版本中 ...

  4. 高通android开源代码下载,高通平台Android源码bootloader分析之sbl1(三)

    前两篇博文分析了启动流程.代码流程.cdt,接下来就分析另外几个需要格外关注的部分. ##log系统 sbl1中的log系统也是sbl1部分调试会经常接触得部分高通平台在sbl中做的log系统并不是很 ...

  5. Android源码设计模式探索与实战【建造者模式】

    IT行业,一直讲一句话,拼到最后都拼的是"内功",而内功往往就是指我们处理问题的思路.经验.想法,而对于开发者来说,甚至对于产品也一样,都离不开一个"宝典",就 ...

  6. android源码编译1

    一.环境说明: 1.liunx系统:Ubuntu12.04 2.jdk:sun-java6-jdk 3.g++4.5 gcc4.5 二.android源码的目录结构 |--Makefile|--bio ...

  7. android源码分析

    01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 ...

  8. Android 源码分析

    查看源码版本号: build\core\version_defaults.mk //搜索该文件中的 PLATFORM_VERSION值 frameworks 目录 (核心框架--java及C++语言) ...

  9. Android源码目录结构分析

    Android源码目录结构分析(知识笔记) 根目录结构: |-- Makefile |-- abi |-- art |-- bionic (bionic C库) |-- bootable (启动引导相 ...

最新文章

  1. Linux疑难杂症解决方案100篇(十五)-万字长文带你深入Linux 内核学习:环境搭建和内核编译
  2. [Web 前端] mobx教程(二)-mobx主要概念
  3. mysql k,mysql事务有关概念-怀念K.Dures
  4. Eclipse 报java.lang.OutOfMemoryError: PermGen space错
  5. 我和《Visual c++2013入门经典(第7版)》的那些事
  6. 什么是区块链预言机(BlockChain Oracle)
  7. 中兴Axon 30至臻版开启预售:搭载屏下摄像头技术
  8. hash算法_hash一致性算法
  9. 小姐姐给我讲,一听就懂的财务报表
  10. Android仿微信源码下载
  11. TXT阅读神器 分分钟打造一本电子书
  12. 服务器远程关机软件,实现远程关机需要哪些步骤?向日葵怎么实现远程关机控制?...
  13. linux win10 mac地址修改,两种方法教你修改Win10专业版MAC物理地址
  14. 4个开源对象存储的解决方案
  15. 微信隐藏功能系列:微信怎么恢复聊天记录?3步完成修复
  16. 查看QQ空间加密相册 真实(视频)
  17. 【错误记录】安卓项目编译报错 ( offline mode 错误 )
  18. 数据分析实战(二) 基于美国人口adult数据集R语言分析实战
  19. 荣耀什么时候更新鸿蒙,华为鸿蒙系统升级时间表:荣耀系列暂未在列
  20. 呕心沥血之飞冰(icejs)项目使用vue组件 -- vuera

热门文章

  1. IOS开发基础Object-C( 04)—构造方法和description方法
  2. 黑鲨手机计算机科学技术器,手机秒变3D 黑鲨科技与康得新共推裸眼3D神奇“膜”法...
  3. python dataframe
  4. 倩女幽魂甲士技能如何操作,需要哪些装备?
  5. Idea遇到This may be useful when development is performed under newer SDK version as the target platfor
  6. 多媒体播放器-VLC media player提供下载
  7. 自定义视图,重绘多张图片形成动画
  8. 移动邮箱(139):开启服务+密码登录
  9. Edittext限制输入字符长度,解决中英文混用情况保证规定长度
  10. 《反脆弱》读书笔记(1)