Android源碼之剩餘充電時間計算

連接電腦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,之後每充一個電,該值加1
    if (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,不插入充電器時爲true
        if (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) {
        //如果充滿電且未插入充電線,記錄DailyItem
        recordDailyStatsIfNeededLocked(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;
        }
        //充電電壓升高或者降低20v
        if (volt > (mHistoryCur.batteryVoltage+20)
                || volt < (mHistoryCur.batteryVoltage-20)) {
            mHistoryCur.batteryVoltage = (char)volt;
            changed = true;
        }
        //已充電數升高或者降低10mAh
        if (chargeUAh >= (mHistoryCur.batteryChargeUAh+10)
                || chargeUAh <= (mHistoryCur.batteryChargeUAh-10)) {
            if (chargeUAh < mHistoryCur.batteryChargeUAh) {
                // Only record discharges
                final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
                mDischargeCounter.addCountLocked(chargeDiff);
                mDischargeScreenOffCounter.addCountLocked(chargeDiff);
                if (isScreenDoze(mScreenState)) {
                    mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
                }
            }
            mHistoryCur.batteryChargeUAh = chargeUAh;
            changed = true;
        }
        //modeBits是一個標誌位,long類型共64bit
        long 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,之後每充一個電,該值加1
    int stepCount = mNumStepDurations;
    //暫存上次充一個電時刻的時間
    final long lastStepTime = mLastStepTime;
    //上次充滿一個電時刻的時間>=0 && 當前電量-上次充電電量>0
    if (lastStepTime >= 0 && numStepLevels > 0) {
        //暫存每充一個電所需時長的數組
        final long[] steps = mStepDurations;
        //得到上次和這次的時長,即每充一個電量的時長
        long duration = elapsedRealtime - lastStepTime;
        //numStepLevels是每充一個電時的步數,所以是1,如12->13,numStepLevels=13-12=1
        for (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位清0
            steps[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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章