1. 程式人生 > >[電池]設定-電池-上次充滿電時間計算

[電池]設定-電池-上次充滿電時間計算

1. 現象

為什麼第一次開機或者格式化後電池顯示上次充滿電是xxx天或者xxx分鐘前,不管之前電池電量數值

實際操作:

  1. 充滿電且拔除充電線,則顯示上次充滿電為0分鐘前
  2. 新機器第一次開機,不論當前電量大小,拔除充電線,則顯示上次充滿電為0分鐘前
  3. 修改系統時間未來時間,則顯示上次充滿電是xxx天或者xxx分鐘前
  4. 修改系統時間過去時間,則顯示為0分鐘前

2. 原因

因為上次充滿電介面getStartClockTime中 mStartClockTime = currentTime - (mClocks.elapsedRealtime()/當前自開機後,經過的時間,包括深度休眠的時間

/-(mRealtimeStart/1000))決定,其中elapsedRealtime和mRealtimeStart的數值重新整理邏輯BatteryStatsImpl->setBatteryStateLocked->setOnBatteryLocked->resetAllStatsLocked->initTimes改變

故這裡顯示上次充滿電是xxx天或者xxx分鐘前,是因為沒有實際充電的時刻時,取的上次充滿電時間為系統當前時間.故會有明明沒充滿電,顯示上次充滿電是xxx天或者xxx分鐘前的邏輯顯示。除非充滿電且拔除充電線則為正常顯示了。

Google的這個邏輯確實存在不精確

3. 原始碼

3.1 設定->電池

package com.android.settings.fuelgauge;

public class PowerUsageSummary extends PowerUsageBase implements OnLongClickListener,
        BatteryTipPreferenceController.BatteryTipListener {
        
    @VisibleForTesting
    void updateLastFullChargePreference() {
        if (mBatteryInfo != null && mBatteryInfo.averageTimeToDischarge
                != Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN) {
            // 電池在充滿電後的預估使用時間
        } else {
            final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper,
                    System.currentTimeMillis());
            mLastFullChargePref.setTitle(R.string.battery_last_full_charge); // 上次充滿電
            mLastFullChargePref.setSubtitle(
                    StringUtil.formatRelativeTime(getContext(), lastFullChargeTime,
                            false /* withSeconds */));
        }
    }

3.2 BatteryUtils.calculateLastFullChargeTime

package com.android.settings.fuelgauge;

/**
 * Utils for battery operation
 */
public class BatteryUtils {

    /**
     * Calculate the time since last full charge, including the device off time
     * 計算上次充電電時間,包含裝置關機時間
     *
     * @param batteryStatsHelper utility class that contains the data 電池資訊類
     * @param currentTimeMs      current wall time 當前時間
     * @return time in millis 返回時長
     */
    public long calculateLastFullChargeTime(BatteryStatsHelper batteryStatsHelper,
            long currentTimeMs) {
        return currentTimeMs - batteryStatsHelper.getStats().getStartClockTime();

    }

3.3 BatteryStatsHelper.getStats().getStartClockTime()

  • frameworks/base/core/java/com/android/internal/os/BatteryStatsHelper.java
package com.android.internal.os;

public class BatteryStatsHelper {

    private BatteryStats mStats;

    public BatteryStats getStats() {
        if (mStats == null) {
            load();
        }
        return mStats;
    }
    
    private static BatteryStatsImpl getStats(IBatteryStats service) {
        try {
            ParcelFileDescriptor pfd = service.getStatisticsStream();
            if (pfd != null) {
                try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
                    byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor()));
                    Parcel parcel = Parcel.obtain();
                    parcel.unmarshall(data, 0, data.length);
                    parcel.setDataPosition(0);
                    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();
    }
3.3.1 BatteryStats.getStartClockTime()
  • frameworks/base/core/java/android/os/BatteryStats.java
package android.os;

public abstract class BatteryStats implements Parcelable {

    /**
     * Return the wall clock time when battery stats data collection started.
     */
    public abstract long getStartClockTime()
3.3.2 BatteryStatsImpl.getStartClockTime()
  • frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
package com.android.internal.os;

public class BatteryStatsImpl extends BatteryStats {

    // 獲取上次充滿電時間
    @Override public long getStartClockTime() {
    final long currentTime = System.currentTimeMillis();
    if (ensureStartClockTime(currentTime)) {
            recordCurrentTimeChangeLocked(currentTime, mClocks.elapsedRealtime(),
                    mClocks.uptimeMillis());
        }
        return mStartClockTime;
    }

3.4 BatteryStatsImpl.getStartClockTime

3.4.1 BatteryStatsImpl.getStartClockTime.ensureStartClockTime
package com.android.internal.os;

public class BatteryStatsImpl extends BatteryStats {

    long mStartClockTime;

    void initTimes(long uptime, long realtime) {
        ...
        mStartClockTime = System.currentTimeMillis();
        ...
    }

    // 從電池詳情資料庫中 batterystats.bin 更新StartClockTime
    public void readSummaryFromParcel(Parcel in) throws ParcelFormatException {
        ...
        // 從 batterystats.bin 獲取上次充滿電時間
        mStartClockTime = in.readLong();
        mRealtimeStart = in.readLong();
        ...
    }


    public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
        ...
        // Pull the clock time.  This may update the time and make a new history entry
        // if we had originally pulled a time before the RTC was set.
        long startClockTime = getStartClockTime();
        out.writeLong(startClockTime);
        out.writeLong(mRealtimeStart);
        ...
    }

    // 是否需要重新校準上次充滿電時間
    boolean ensureStartClockTime(final long currentTime) {
        final long ABOUT_ONE_YEAR = 365*24*60*60*1000L; // 1 年時間 31536000000
        // 是否滿足以下條件
        // 1. 當前時間毫秒值 大於 1年,例如這裡是100%滿足,2018-12-22 10:41:46,即當前時間毫秒值 1545446651000
        // 2. 上次充滿電時間StartClockTime 小於 當前時間毫秒值與1年的差, 即這裡即記錄最長上次充滿電1年內,即我手動調整時間,最長只能記錄上次充滿電365天內

        // 或者滿足下面條件
        // 1. 上次充滿電時間StartClockTime 大於 當前時間毫秒值,這個條件可以將系統時間調整為過去的時間
        if ((currentTime > ABOUT_ONE_YEAR && mStartClockTime < (currentTime-ABOUT_ONE_YEAR))
                || (mStartClockTime > currentTime)) {
            // If the start clock time has changed by more than a year, then presumably
            // the previous time was completely bogus.  So we are going to figure out a
            // new time based on how much time has elapsed since we started counting.
            mStartClockTime = currentTime - (mClocks.elapsedRealtime()/*獲取從裝置boot後經歷的時間值*/-(mRealtimeStart/1000));
            return true;
        }
        return false;
    }
    
    
3.4.2 檢視 mStartClockTime 的賦值情況
  1. BatteryStatsImpl 初始化時間
    private BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
            PlatformIdleStateCallback cb,
            UserInfoProvider userInfoProvider) {
        ...
        long uptime = mClocks.uptimeMillis() * 1000;
        long realtime = mClocks.elapsedRealtime() * 1000;
        initTimes(uptime, realtime)
  1. resetAllStatsLocked 初始化時間
    private void resetAllStatsLocked() {
        final long uptimeMillis = mClocks.uptimeMillis();
        final long elapsedRealtimeMillis = mClocks.elapsedRealtime();
        initTimes(uptimeMillis * 1000, elapsedRealtimeMillis * 1000);
        
  1. initTimes 函式
    public interface Clocks {
        public long elapsedRealtime();
        public long uptimeMillis();
    }

    public static class SystemClocks implements Clocks {
        public long elapsedRealtime() {
            return SystemClock.elapsedRealtime(); // 自開機後,經過的時間,包括深度休眠的時間
        }

        public long uptimeMillis() {
            return SystemClock.uptimeMillis(); // 自開機後,經過的時間,不包括深度休眠的時間
        }
    }
    
    void initTimes(long uptime, long realtime) {
        mStartClockTime = System.currentTimeMillis(); // 系統當前時間,即日期時間,可以被系統設定修改,如果設定系統時間,時間值會發生跳變。
        mOnBatteryTimeBase.init(uptime, realtime);
        mOnBatteryScreenOffTimeBase.init(uptime, realtime);
        mRealtimeStart = realtime;
    }

3.5 BatteryStatsImpl.setBatteryStateLocked.setOnBatteryLocked.resetAllStatsLocked

    @GuardedBy("this")
    public void setBatteryStateLocked(final int status, final int health, final int plugType,
            final int level, /* not final */ int temp, final int volt, final int chargeUAh,
            final int chargeFullUAh) {
            if (onBattery != mOnBattery) {
                ...
                setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);


    @GuardedBy("this")
    protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
            final boolean onBattery, final int oldStatus, final int level, final int chargeUAh) {
            
        if (onBattery) {
            // We will reset our status if we are unplugging after the
            // battery was last full, or the level is at 100, or
            // we have gone through a significant charge (from a very low
            // level to a now very high level).
            boolean reset = false;
            if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL
                    || level >= 90
                    || (mDischargeCurrentLevel < 20 && level >= 80)
                    || (getHighDischargeAmountSinceCharge() >= 200
                            && mHistoryBuffer.dataSize() >= MAX_HISTORY_BUFFER))) {
                ...
                resetAllStatsLocked();