Android7.0 BatteryStatsService

BatteryStasService的主要功能是收集系统中各模块和应用进程的用电情况。
因此,我们可以认为BatteryStatsService是Android中的“电表”。
只不过这个电表比较智能,不是单纯地统计整体的耗电,而是分门别类的统计每个部分的耗电情况。
接下来我们就分析一下BatteryStatsService的主要流程。为了方便叙述,后文中我们将BatteryStatsService简称为BSS。

一、BSS启动
1、构造函数
与一般的系统服务不太一样,BSS的创建和发布是在ActivityManagerService中进行的,相关代码如下:

public ActivityManagerService(Context systemContext) {
    ..........
    File dataDir = Environment.getDataDirectory();
    File systemDir = new File(dataDir, "system");
    systemDir.mkdirs();
    //创建BSS对象,传入/data/system目录,同时传入ActivityManagerService的handler
    mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
    //调用BSS中BatteryStatsImpl对象的readLocked方法
    mBatteryStatsService.getActiveStatistics().readLocked();
    //将初始化得到的信息写入disk
    mBatteryStatsService.scheduleWriteToDisk();
    ..........
}

接下来我们先看看BatteryStatsService的构造函数:

BatteryStatsService(File systemDir, Handler handler) {
    // Our handler here will be accessing the disk, use a different thread than
    // what the ActivityManagerService gave us (no I/O on that one!).
    //创建一个本地服务线程,用于访问硬盘数据
    final ServiceThread thread = new ServiceThread("batterystats-sync",
            Process.THREAD_PRIORITY_DEFAULT, true);
    thread.start();
    mHandler = new BatteryStatsHandler(thread.getLooper());

    // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
    //创建BatteryStatsImpl类
    mStats = new BatteryStatsImpl(systemDir, handler, mHandler, this);
}

从代码可以看出,BSS维护的主要变量为一个BatteryStatsImpl对象,这个对象才是承担BSS实际工作的主体。
上文中的readLocked函数就是由BatteryStatsImpl实际执行。
Android7.0 BatteryStatsService_第1张图片
BSS与BatteryServiceImpl的关系如上图所示。
从图中可以看出,BatteryStatsImpl继承自BatteryStats,实现了Parcelable接口,因此可以通过Binder通信在进程间传递。
实际上,从设置中查到的用电信息就是来自BatteryStatsImpl。
BSS的getStatistics函数提供了查询系统用电的接口,该接口的代码如下:

public byte[] getStatistics() {
    ...............
    Parcel out = Parcel.obtain();
    //更新系统中其它组件的耗电情况,记录于BatteryStatsImpl中
    updateExternalStatsSync("get-stats", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
    synchronized (mStats) {
        //将BatteryServiceImpl中的信息写入到数据包中
        mStats.writeToParcel(out, 0);
    }
    byte[] data = out.marshall();
    out.recycle();
    return data;
}

由此可以看出,电量统计的核心类是BatteryStatsImpl,后文中简写为BSImpl。

2、BSS的发布
BSS创建完毕后,在ActivityManagerService的start函数中,BSS完成发布的工作:

private void start() {
    ..........
    mBatteryStatsService.publish(mContext);
    ..........
}

跟进一下BatteryStatsService的publish函数:

public void publish(Context context) {
    mContext = context;
    //BSImpl设置mPhoneSignalScanningTimer的超时时间(用于统计搜索手机信号消耗的时间)
    mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
            com.android.internal.R.integer.config_radioScanningTimeout)
            * 1000L);
    //创建衡量硬件耗电能力的PowerProfile,并交给BSImpl使用
    mStats.setPowerProfile(new PowerProfile(context));
    //将自己注册到service manager进程中
    ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
}

BSS发布相关的代码比较简单,但BSImpl做了两件与发布服务无关的事。
首先,BSImpl设置了mPhoneSignalScanningTimer的超时时间。mPhoneSignalScanningTimer是BSImpl的一个统计工具,实际类型为StopwatchTimer。BSImpl作为统计电量的实际类,定义了许多工具分别统计终端不同部分、场景的耗电情况。后文再详细分析这一部分。
此外,BSImpl设置了PowerProfile相关的内容。

/**
* Reports power consumption values for various device activities. Reads values from an XML file.
* Customize the XML file for different devices.
* /
public PowerProfile(Context context) {
    // Read the XML file for the given profile (normally only one per
    // device)
    if (sPowerMap.size() == 0) {
        //从XML中得到设备硬件的配置情况
        readPowerValuesFromXml(context);
    }
    //得到CPU的性能指标
    initCpuClusters();
}

实际使用的XML应该是和硬件相关的文件,存储各种操作的耗电情况(以mA.h为单位),此处具体的解析过程不做赘述。
我们回过头来看一下BSImpl的setPowerProfile函数:

public void setPowerProfile(PowerProfile profile) {
    synchronized (this) {
        mPowerProfile = profile;

        // We need to initialize the KernelCpuSpeedReaders to read from
        // the first cpu of each core. Once we have the PowerProfile, we have access to this
        // information.
        //以下是构造每个CPU集群对应的KernelCpuSpeedReader
        final int numClusters = mPowerProfile.getNumCpuClusters();
        mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
        int firstCpuOfCluster = 0;
        //轮询每个集群
        for (int i = 0; i < numClusters; i++) {
            //得到CPU支持的频率值
            final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i);
            //创建KernelCpuSpeedReader
            mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
                    numSpeedSteps);
            //当前CPU集群的核数
            firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
        }

        //得到当前评估出的平均电量
        if (mEstimatedBatteryCapacity == -1) {
            // Initialize the estimated battery capacity to a known preset one.
            mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
        }
    }
}

至此我们知道了,在BSS发布之前,它持有的BSImpl对象已经得到整个终端电量相关的硬件信息。

3、initPowerManagement
BSS发布后,SystemServer调用了ActivityManagerService的initPowerManagement函数:

..............
// Now that the power manager has been started, let the activity manager
// initialize power management features.
mActivityManagerService.initPowerManagement();
.............

在ActivityManagerService中:

public void initPowerManagement() {
    ................
    mBatteryStatsService.initPowerManagement();
    ................
}

我们进入到BSS的initPowerManagement函数:

/**
* At the time when the constructor runs, the power manager has not yet been
* initialized.  So we initialize the low power observer later.
*/
//SystemServer先启动ActivityManagerService,在其中创建出BSS
//后面才创建PowerManagerService
public void initPowerManagement() {
    final PowerManagerInternal powerMgr = LocalServices.getService(PowerManagerInternal.class);
    //向PowerManagerService注册低电模式的回调接口,即进入或退出低电模式时
    //调用BSS的onLowPowerModeChanged函数,在该函数中将调用BSImpl的notePowerSaveMode函数
    powerMgr.registerLowPowerModeObserver(this);

    //进入低电模式时启动mPowerSaveModeEnabledTimer
    //离开低电模式时停止mPowerSaveModeEnabledTimer
    //并记录对应的信息 (mPowerSaveModeEnabledTimer也是BSImpl定义的统计工具)
    mStats.notePowerSaveMode(powerMgr.getLowPowerModeEnabled());

    //启动WakeupReasonThread
    (new WakeupReasonThread()).start();
}

在这一部分的最后,我们看一下WakeupReasonThread的主要部分:

final class WakeupReasonThread extends Thread {
    ........
    public void run() {
        ..........
        try {
            String reason;
            //等待终端被唤醒
            while ((reason = waitWakeup()) != null) {
                synchronized (mStats) {
                    //记录终端变为唤醒状态的原因
                    mStats.noteWakeupReasonLocked(reason);
                }
            }
        } catch (RuntimeException e) {
            Slog.e(TAG, "Failure reading wakeup reasons", e);
        }
    }

    private String waitWakeup() {
        ..........
        int bytesWritten = nativeWaitWakeup(mUtf8Buffer);
        ..........
        // Create a String from the UTF-16 buffer.
        return mUtf16Buffer.toString();
    }
}

从上面的代码,我们可以看出对于BSS而言,WakeupReasonThread就是负责监控终端的状态,当终端变为唤醒状态时,利用BSImpl记录唤醒的原因。

利用这个机会,我们看看BSS底层是如何监控终端是否唤醒的。
在com_android_server_am_BatteryStatsService中:

static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) {
    ...........
    // Register our wakeup callback if not yet done.
    if (!wakeup_init) {
        wakeup_init = true;
        ALOGV("Creating semaphore...");
        //创建旗语
        int ret = sem_init(&wakeup_sem, 0, 0);
        ..........
        ALOGV("Registering callback...");
        //从挂起变为唤醒时,wakeup_callback将调用sem_post向wakeup_sem写入数据
        set_wakeup_callback(&wakeup_callback);
    }

    // Wait for wakeup.
    ALOGV("Waiting for wakeup...");
    //唤醒时,结束等待
    int ret = sem_wait(&wakeup_sem);
    .........
    //打开文件
    FILE *fp = fopen(LAST_RESUME_REASON, "r");
    .........
    //读取内容
    while (fgets(reasonline, sizeof(reasonline), fp) != NULL) {
        ..........
    }
    //关闭文件
    if (fclose(fp) != 0) {
        ...........
    }

    //return reason
    ........
}

现在我们明白了,对于BSS而言,initPowerManagement的工作主要包括两个:
一是监控终端低电模式的状态,当发生改变时进行相应的记录;
二是监控终端从挂起状态变为唤醒状态,并记录唤醒的原因。

BSS启动时主要的流程基本结束,接下来我们看一下BSImpl相关的内容。

二、BSImpl的构造函数
BSImpl的构造函数内容较多,我们分段来看:

public BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
        ExternalStatsSync externalSync, PlatformIdleStateCallback cb) {
    init(clocks);
    ..........

1、init初始化
首先BSImpl在构造函数中,调用init进行初始化工作:

private void init(Clocks clocks) {
    mClocks = clocks;
    mMobileNetworkStats = new NetworkStats[] {
            new NetworkStats(mClocks.elapsedRealtime(), 50),
            new NetworkStats(mClocks.elapsedRealtime(), 50),
            new NetworkStats(mClocks.elapsedRealtime(), 50)
    };
    mWifiNetworkStats = new NetworkStats[] {
            new NetworkStats(mClocks.elapsedRealtime(), 50),
            new NetworkStats(mClocks.elapsedRealtime(), 50),
            new NetworkStats(mClocks.elapsedRealtime(), 50)
    };
}

NetworkStats是实现Parcelable接口的对象,用于统计网络相关的数据,例如接口、uid、发送数据、接收数据等。
从这个架势来看,我们目前可以推测出BSImpl在统计电量时,还需要考虑每个网络具体的信息。

2、创建统计所需要的文件

..........
if (systemDir != null) {
    mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
            new File(systemDir, "batterystats.bin.tmp"));
} else {
    mFile = null;
}

//mCheckinFile存储BSImpl的snapShot;
//当充电状态从充电变为未充电,且电池累积耗电量足够大时,会将snapShot写入"batterystats-checkin.bin"
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));

mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
..........

上述代码首先创建了一个JournaledFile,其内部包含两个创建于data/system/目录下的文件,分别作为原始文件和临时文件。这么做的目的是进行备份,
防止在读写过程中文件信息丢失或出错。
这部分文件用于保存BatteryStats电量的统计信息。系统会不定时的将电量统计信息BatteryStats写入到文件中。

从代码中操作mDailyFile的接口来看,mDailyFile应该是用于记录每天的电量统计信息。

3、创建各模块电量的统计工具

mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
..............
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
    mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
    mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
}
.............
mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS);
............

BSImpl需要统计各个模块的耗电情况,甚至需要统计一个模块在不同场景下的耗电情况。
为此,BSImpl在其构造函数中创建了许多对象进行分类统计。
如上面的代码示例,BSImpl在构造函数中创建的工具类就有三种,即StopwatchTimer、LongSamplingCounter和ControllerActivityCounterImpl。
Android7.0 BatteryStatsService_第2张图片
这三种工具类的关系如上图所示,其中ControllerActivityCounterImpl包含了多个LongSamplingCounter对象。

从上图可以看出工具类实际上一共分为两种,其中Counter相关的主要用于计数,Timer相关的用于计时。
目前自己从代码来看,电量的统计主要依赖于Timer;Counter相关的对象,多进行统计发包数量、分组数量这样的工作。
这是因为:
在Android系统中,手机的电压是一定的,具体操作的耗电量在构造PowerProfile时已经得到了,因此手机中各模块的电量计算,就是利用模块使用时间*模块对应操作的单位耗电量。

这些工具类均实现了TimeBaseObs接口定义的onTimeStarted函数和onTimeStopped。
具体的用法,在具体的流程中分析比较好,此处再不作说明。

4、初始化剩余变量

.............
mOnBattery = mOnBatteryInternal = false;
long uptime = mClocks.uptimeMillis() * 1000;
long realtime = mClocks.elapsedRealtime() * 1000;
//初始化统计时间
initTimes(uptime, realtime);
mStartPlatformVersion = mEndPlatformVersion = Build.ID;
mDischargeStartLevel = 0;
mDischargeUnplugLevel = 0;
mDischargePlugLevel = -1;
mDischargeCurrentLevel = 0;
mCurrentBatteryLevel = 0;
initDischarge();
//删除用电统计的记录
clearHistoryLocked();
updateDailyDeadlineLocked();
mPlatformIdleStateCallback = cb;
.............

BSImpl的构造函数最后会初始化大量的变量,只有结合具体的流程,才能知道具体的含义,此处不做进一步的分析。
我们仅需要注意的是,系统时间分为uptime和realtime。uptime和realtime的时间起点都从系统启动开始算,
但是uptime不包括系统休眠时间,而realtime包括系统休眠时间。

三、BSS及BSImpl主要流程分析
1、BSImpl的readLocked
前面已经提到过,ActivityManagerService创建出BatteryStatsService后,执行了以下代码:

.........
//getActiveStatistics返回BSImpl
mBatteryStatsService.getActiveStatistics().readLocked();
.........

我们跟进一下BSImpl的readLocked函数:

public void readLocked() {
    if (mDailyFile != null) {
        //读取BSImpl维护的mDailyFile,解析其中的数据保存到mDailyItems中
        readDailyStatsLocked();
    }

    //mFile为"batterystats.bin"对应的JournaledFile
    if (mFile == null) {
        Slog.w("BatteryStats", "readLocked: no file associated with this instance");
        return;
    }

    //mUidStats保存了BatteryStatsImpl.Uid
    //BSImpl定义的数据结构,继承BSS.Uid,记录每个uid对应的耗电情况
    mUidStats.clear();

    try {
        //从JournaledFile选择实际文件和备份文件中的一个读取
        File file = mFile.chooseForRead();
        .........
        FileInputStream stream = new FileInputStream(file);

        byte[] raw = BatteryStatsHelper.readFully(stream);
        Parcel in = Parcel.obtain();
        in.unmarshall(raw, 0, raw.length);
        in.setDataPosition(0);
        stream.close();

        //利用从"batterystats.bin"中读取的数据初始化BSImpl的变量
        readSummaryFromParcel(in);
    } catch(Exception e) {
        Slog.e("BatteryStats", "Error reading battery statistics", e);
        resetAllStatsLocked();
    }

    mEndPlatformVersion = Build.ID;

    //开始记录历史信息
    ...............

    //当时间满足条件时,记录每日的信息
    recordDailyStatsIfNeededLocked(false);
}

从上面的代码可以看出,readLocked主要的功能是读取之前的统计信息,然后初始化BSImpl对象。
这里值的一提的是BSImpl中定义的内部类Uid。
Android7.0 BatteryStatsService_第3张图片
BSImpl中的Uid类继承自BatteryStats的Uid类。
其中:
WakeLock用于统计该Uid对应进程使用某个WakeLock的用电情况;
Proc用于统计该Uid对应的某个进程的用电情况;
Pkg用于统计某个Pacakge的电量使用情况,其内部类Serv用于统计该Package内某个服务的用电情况;
Sensor用于统计传感器的用电情况。

2、BSS的scheduleWriteToDisk
调用readLocked完成对BSImpl的初始化后,ActivityManagerService调用了如下代码:

.........
mBatteryStatsService.scheduleWriteToDisk();
.........

在BSS的scheduleWriteToDisk函数中:

/**
* Schedules a write to disk to occur. This will cause the BatteryStatsImpl
* object to update with the latest info, then write to disk.
*/
public void scheduleWriteToDisk() {
    mHandler.sendEmptyMessage(BatteryStatsHandler.MSG_WRITE_TO_DISK);
}

MSG_WRITE_TO_DISK对应的处理流程为:

..........
case MSG_WRITE_TO_DISK:
    //进行信息更新
    updateExternalStatsSync("write", UPDATE_ALL);
    synchronized (mStats) {
        //完成信息写入
        mStats.writeAsyncLocked();
    }
    break;
..........

2.1 updateExternalStatsSync信息更新

void updateExternalStatsSync(final String reason, int updateFlags) {
    //SynchronousResultReceiver用于接收消息
    //继承RunReceiver实现了Parcel接口,可以跨进程传递
    //定义了回调接口,通过回调接口得到返回消息
    //在接收到回复的消息前,可以阻塞一段时间,整个的实现使得单独拿出来分析一下
    SynchronousResultReceiver wifiReceiver = null;
    SynchronousResultReceiver bluetoothReceiver = null;
    SynchronousResultReceiver modemReceiver = null;

    synchronized (mExternalStatsLock) {
        ...............
        if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
            ................
            try {
                wifiReceiver = new SynchronousResultReceiver();
                //获取WiFi的信息,传入SynchronousResultReceiver对象
                mWifiManager.requestActivityInfo(wifiReceiver);
            } catch (RemoteException e) {
                // Oh well.
            }
        }

        if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) {
            final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
            if (adapter != null) {
                bluetoothReceiver = new SynchronousResultReceiver();
                //获取蓝牙的信息,传入SynchronousResultReceiver对象
                adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
            }
        }

        if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) {
            ..............
            if (mTelephony != null) {
                modemReceiver = new SynchronousResultReceiver();
                //获取modem的信息,传入SynchronousResultReceiver对象
                //对于Telephony而言,就是通过RIL发送消息给modem,获取返回结果
                mTelephony.requestModemActivityInfo(modemReceiver);
            }
        }

        WifiActivityEnergyInfo wifiInfo = null;
        BluetoothActivityEnergyInfo bluetoothInfo = null;
        ModemActivityInfo modemInfo = null;
        try {
            //awaitControllerInfo内部调用SynchronousResultReceiver对象的awaitResult函数
            //在获得消息前,会等待一段时间直到超时
            //获取到结果时,退出等待
            wifiInfo = awaitControllerInfo(wifiReceiver);
        } catch (TimeoutException e) {
            Slog.w(TAG, "Timeout reading wifi stats");
        }

        try {
            bluetoothInfo = awaitControllerInfo(bluetoothReceiver);
        } catch (TimeoutException e) {
            Slog.w(TAG, "Timeout reading bt stats");
        }

        try {
            modemInfo = awaitControllerInfo(modemReceiver);
        } catch (TimeoutException e) {
            Slog.w(TAG, "Timeout reading modem stats");
        }

        synchronized (mStats) {
            //更新相关的信息
            mStats.addHistoryEventLocked(
                    SystemClock.elapsedRealtime(),
                    SystemClock.uptimeMillis(),
                    BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
                    reason, 0);

            mStats.updateCpuTimeLocked();
            mStats.updateKernelWakelocksLocked();

            if (wifiInfo != null) {
                if (wifiInfo.isValid()) {
                    //更新WiFi的耗电情况
                    mStats.updateWifiStateLocked(extractDelta(wifiInfo));
                } else {
                    Slog.e(TAG, "wifi info is invalid: " + wifiInfo);
                }
            }

            if (bluetoothInfo != null) {
                if (bluetoothInfo.isValid()) {
                    //更新蓝牙的耗电情况
                    mStats.updateBluetoothStateLocked(bluetoothInfo);
                } else {
                    Slog.e(TAG, "bluetooth info is invalid: " + bluetoothInfo);
                }
            }

            if (modemInfo != null) {
                if (modemInfo.isValid()) {
                    mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime(),
                            modemInfo);
                } else {
                    Slog.e(TAG, "modem info is invalid: " + modemInfo);
                }
            }
        }
    }
}

从上面的代码,我们知道updateExternalStatsSync函数用于同步当前终端的信息,
主要是获取WiFi、BT和modem的耗电情况,并保存到BSImpl中。

2.2 BSImpl的writeAsyncLocked函数

public void writeAsyncLocked() {
    //传入的值为false,表示异步
    writeLocked(false);
}

void writeLocked(boolean sync) {
    ........
    Parcel out = Parcel.obtain();
    //将BSImpl中的信息写入到Parcel对象
    writeSummaryToParcel(out, true);
    .............
    //Parcel对象保存到mPendingWrite
    mPendingWrite = out;
    //同步时用主线程写
    if (sync) {
        commitPendingDataToDisk();
    } else {
        //异步时利用后台线程写
        BackgroundThread.getHandler().post(new Runnable() {
            @Override public void run() {
                commitPendingDataToDisk();
            }
        });
    }
}

从上面的代码易知,commitPendingDataToDisk才进行实际的写操作。

public void commitPendingDataToDisk() {
    ...........
    //写入的文件还是"batterystats.bin"
    FileOutputStream stream = new FileOutputStream(mFile.chooseForWrite());
    //next为mPendingWrite,即当前待写入的内容
    stream.write(next.marshall());
    stream.flush();
    FileUtils.sync(stream);
    stream.close();
    mFile.commit();
    .............
}

至此,我们知道scheduleWriteToDisk的作用就是将手机中最新的信息保存到硬盘中。

3、BSImpl耗电时间统计
了解BSS和BSImpl初始的一些流程后,我们来看看BSImpl到底是如何统计和计算电量的。

3.1 楔子
在BSS中定义了许多以note开头的方法,这些方法就是不同模块用于通知BSS记录用电信息的。
接下来,我们以数据业务为例,看看统计电量的过程。
在ConnectivityService的构造函数中:

public ConnectivityService(Context context, INetworkManagementService netManager,
        INetworkStatsService statsService, INetworkPolicyManager policyManager) {
    .............
    //创建出DataConnectionStats,该类继承自BroadcastReceiver
    mDataConnectionStats = new DataConnectionStats(mContext);
    mDataConnectionStats.startMonitoring();
    .............
}

跟进一下DataConnectionStats类:

public class DataConnectionStats extends BroadcastReceiver {
    .................
    public DataConnectionStats(Context context) {
        mContext = context;
        //获取到BatteryStatsService的代理对象
        mBatteryStats = BatteryStatsService.getService();
    }

    public void startMonitoring() {
        TelephonyManager phone =
                (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
        //listen phone状态的变化
        phone.listen(mPhoneStateListener,
                PhoneStateListener.LISTEN_SERVICE_STATE
              | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
              | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
              | PhoneStateListener.LISTEN_DATA_ACTIVITY);

        IntentFilter filter = new IntentFilter();
        //监听以下广播
        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
        mContext.registerReceiver(this, filter);
    }
    ..................
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
            updateSimState(intent);
            //注意notePhoneDataConnectionState
            notePhoneDataConnectionState();
        } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
                action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
            notePhoneDataConnectionState();
       }
    }
    .................
    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
        @Override
        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
            mSignalStrength = signalStrength;
        }

        @Override
        public void onServiceStateChanged(ServiceState state) {
            mServiceState = state;
            //同样会有notePhoneDataConnectionState
            notePhoneDataConnectionState();
        }

        @Override
        public void onDataConnectionStateChanged(int state, int networkType) {
            mDataState = state;
            notePhoneDataConnectionState();
        }

        @Override
        public void onDataActivity(int direction) {
            notePhoneDataConnectionState();
        }
    };
    ...............
}

从上面的代码可以看出,DataConnectionStats监控了整个终端中与数据相关的比较重要的状态,例如SIM State、Service State等。
当状态发生变化时,就会调用notePhoneDataConnectionState函数。

我们跟进一下DataConnectionStats中的notePhoneDataConnectionState:

private void notePhoneDataConnectionState() {
    ..............
    boolean simReadyOrUnknown = mSimState == IccCardConstants.State.READY
            || mSimState == IccCardConstants.State.UNKNOWN;
    //基本上可以简单的认为有服务并且数据脸上时,visible为true
    boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM
            && hasService()
            && mDataState == TelephonyManager.DATA_CONNECTED;
    int networkType = mServiceState.getDataNetworkType();   
    ................
    try {
        //进入到BSS中
        mBatteryStats.notePhoneDataConnectionState(networkType, visible);
    } catch (RemoteException e) {
        Log.w(TAG, "Error noting data connection state", e);
    }
}

3.2 notePhoneDataConnectionState函数
顺着流程,我们看看BSS的notePhoneDataConnectionState函数:

public void notePhoneDataConnectionState(int dataType, boolean hasData) {
    enforceCallingPermission();
    synchronized (mStats) {
        //委托给BSImpl处理
        mStats.notePhoneDataConnectionStateLocked(dataType, hasData);
    }
}

跟进BSImpl的notePhoneDataConnectionStateLocked函数:

public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData) {
    int bin = DATA_CONNECTION_NONE;
    //数据连接上时,hasData为true
    if (hasData) {
        //dataType对应与networkType
        switch (dataType) {
            case TelephonyManager.NETWORK_TYPE_EDGE:
                //根据NetworkType, 给bin赋予不同的值
                bin = DATA_CONNECTION_EDGE;
                break;
            ...............
        }
    }

    //数据网络的类型发生改变时
    if (mPhoneDataConnectionType != bin) {
        //实际消逝的时间
        final long elapsedRealtime = mClocks.elapsedRealtime();
        //系统处于唤醒的状态消逝的时间
        final long uptime = mClocks.uptimeMillis();
        ..............
        //记录信息
        addHistoryRecordLocked(elapsedRealtime, uptime);
        if (mPhoneDataConnectionType >= 0) {
            //旧networkType对应的StopWatchTime停止记录时间
            mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(
                    elapsedRealtime);
        }
        mPhoneDataConnectionType = bin;
        //新networkType对应的StopWatchTimer开始记录时间
        mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtime);
    }
}

对于数据业务而言,BSImpl初始化时创建了一个StopWatchTimer数组mPhoneDataConnectionsTimer,用于记录不同NetworkType下,数据业务的耗电时间。
因此,从上面的代码我们就可以看出,每次NetworkType发生改变时,就停止旧有的NetworkType对应StopWatchTimer,这样就可以得到旧有NetworkType下耗电的时间。同时,启动新的NetworkType的StopWatchTimer,这样当下次NetworkType发生改变时,就可以得到数据业务在这个NetworkType下耗电的时间。

3.2.1 StopWatchTimer的startRunningLocked

public void startRunningLocked(long elapsedRealtimeMs) {
    //mNesting可以认为是类似于引用数
    if (mNesting++ == 0) {
        final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
        //mUpdateTime保存着本次启动的起始时间
        mUpdateTime = batteryRealtime;

        //mTimerPool是保存一堆TimerWatcher的ArrayList,不用考虑,当它是null
        if (mTimerPool != null) {
            ...........
        }

        // Increment the count
        mCount++;
        //mTotalTime是这个StopWatchTimer运行过的累积总时间
        mAcquireTime = mTotalTime;
    }
}

在startRunningLocked函数中,我们只需要知道StopWatchTimer中的mUpdateTime记录者本次的启动时间。

3.2.2 StopWatchTimer的stopRunningLocked

public void stopRunningLocked(long elapsedRealtimeMs) {
    // Ignore attempt to stop a timer that isn't running
    if (mNesting == 0) {
        return;
    }

    if (--mNesting == 0) {
        final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
        if (mTimerPool != null) {
            ........
        } else {
            mNesting = 1;
            //computeRunTimeLocked进行实际的计算
            mTotalTime = computeRunTimeLocked(batteryRealtime);
            mNesting = 0;
        }
        ................
    }
}

protected long computeRunTimeLocked(long curBatteryRealtime) {
    //mTimeout是调用StopWatchTimer接口进行设置的
    //从逻辑来看,表示StopWatchTimer的指定生存期,一般没使用
    if (mTimeout > 0 && curBatteryRealtime > mUpdateTime + mTimeout) {
        curBatteryRealtime = mUpdateTime + mTimeout;
    }

    //简化来看,正常情况下:
    //mTotalTime + (curBatteryRealtime - mUpdateTime)
    //curBatteryRealtime为本次结束的时间,mUpdateTime被本次启动的时间
    return mTotalTime + (mNesting > 0
            ? (curBatteryRealtime - mUpdateTime)
                    / (mTimerPool != null ? mTimerPool.size() : 1)
            : 0);
}

从代码来看,当调用stopRunningLocked函数后,就会更新StopWatchTimer的总运行时间。

与数据业务统计耗电时间的方式类似,其它模块也会利用对应的StopWatchTimer统计耗电时间。
因此,我们大概可以认为BSS中整个耗电统计的架构,大概如下图所示:
Android7.0 BatteryStatsService_第4张图片
每个模块都会监控自己的关键状态。
当状态发生改变时,直接或间接地通知到BBS。
BSS收到通知后,通过各种以note开头的函数,将通知递交给BSImpl进行实际的处理。
BSImpl完成启动或停止对应模块的StopWatchTimer,完成对该模块耗电时间的统计。

4、电量计算
Android中计算耗电量离不开BatteryStatsHelper这个类,它主要用于计算所有应用和服务的用电量。
在Settings设置中显示电量相关信息,就是通过调用BatteryStatsHelper的接口来实现的。

从BatteryStatsHelper类的注释来看,当需要使用BatteryStatsHelper时:
必须在Activity或者Fragment初始化时,立即调用BatteryStatsHelper的create();
同时在activity或者Fragment销毁时,调用BatteryStatsHelper的destroy()。
不过在Android N中BatteryStatsHelper似乎已经没有destroy函数了。

4.1 用法示例
我们以设置中的代码为例,看看BatteryStatsHelper的使用方式。

在Android的原生代码,packages/apps/Settings/src/com/android/settings/fuelgauge/PowerBase.java中:

public abstract class PowerUsageBase extends SettingsPreferenceFragment {
    ..................
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE);
        //创建
        mStatsHelper = new BatteryStatsHelper(activity, true);
    }

    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        //初始化
        mStatsHelper.create(icicle);
        setHasOptionsMenu(true);
    }
    ................
}

BatteryStatsHelper的构造函数比较简单,就是初始化一些变量。
我们看看BatteryStatsHelper的create函数:

public void create(Bundle icicle) {
    ...........
    //获取BSS的服务端代理
    mBatteryInfo = IBatteryStats.Stub.asInterface(
            ServiceManager.getService(BatteryStats.SERVICE_NAME));
    //读取PowerProfile
    mPowerProfile = new PowerProfile(mContext);
}

PowerProfile在前文分析BSImpl的构造函数时已经提到过了,主要记录不同硬件模块在不同状态下的耗电量。

在PowerBase.java中定义了:

protected void refreshStats() {
    mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, mUm.getUserProfiles());
}

从代码可以看出,更新用电量时主要依赖于BatteryStatsHelper的refreshStats函数。

4.2 BatteryStatsHelper的refreshStats函数

public void refreshStats(int statsType, SparseArray asUsers, long rawRealtimeUs,
        long rawUptimeUs) {
    // Initialize mStats if necessary.
    //确保获取了BSImp的统计信息
    getStats();

    //初始化基本的状态
    mMaxPower = 0;
    .......

    //重置相关的列表 
    mUsageList.clear();
    ..........

    //初始化相关的电量计算工具,各种计算工具都继承自PowerCalculator类,用于计算不同模块的耗电量
    if (mCpuPowerCalculator == null) {
        mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
    }
    mCpuPowerCalculator.reset();
    ............

    //利用BSImpl中的信息,初始化一些变量
    mStatsType = statsType;
    mRawUptimeUs = rawUptimeUs;
    mRawRealtimeUs = rawRealtimeUs;
    mBatteryUptimeUs = mStats.getBatteryUptime(rawUptimeUs);
    ..........

    //1、计算各个应用的耗电量
    processAppUsage(asUsers);
    ..........

    //2、计算各个模块,除去APP之外消耗的电量
    processMiscUsage();

    //按照耗电量排序
    Collections.sort(mUsageList);

    // At this point, we've sorted the list so we are guaranteed the max values are at the top.
    // We have only added real powers so far.
    //计算耗电量最大的应用,及整个机器耗电量之和
    if (!mUsageList.isEmpty()) {
        mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
        final int usageListCount = mUsageList.size();
        for (int i = 0; i < usageListCount; i++) {
            mComputedPower += mUsageList.get(i).totalPowerMah;
        }
    }
    ...........
    mTotalPower = mComputedPower;
    //这个函数没有深入研究
    //看注释觉得的含义应该是:保证终端在上一次充电结束后,处于未充电状态的时间超过一个门限
    if (mStats.getLowDischargeAmountSinceCharge() > 1) {
        //mMinDrainedPower是统计出的电池的实际消耗电量

        //电池耗电量大于统计的总耗电量
        if (mMinDrainedPower > mComputedPower) {
            //计算差异
            double amount = mMinDrainedPower - mComputedPower;

            //更新耗电量
            mTotalPower = mMinDrainedPower;

            //利用差异量,构造一个耗电对象,type为UNACCOUNTED
            BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);

            // Insert the BatterySipper in its sorted position.、
            //按大小插入表中,同时更新最大耗电量
            int index = Collections.binarySearch(mUsageList, bs);
            if (index < 0) {
                index = -(index + 1);
            }
            mUsageList.add(index, bs);
            mMaxPower = Math.max(mMaxPower, amount);
        } else if (mMaxDrainedPower < mComputedPower) {
            //电池耗电量小于统计的总耗电量

            //多算了也没有更正mTotalPower

            double amount = mComputedPower - mMaxDrainedPower;
            // Insert the BatterySipper in its sorted position.
            //同样插入一个耗电对象,type为OVERCOUNTED
            BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
            int index = Collections.binarySearch(mUsageList, bs);
            if (index < 0) {
                index = -(index + 1);
            }
            mUsageList.add(index, bs);
            mMaxPower = Math.max(mMaxPower, amount);
        }
    }
}

从上面的代码可以看出,refreshStats函数的主要作用就是重新统计终端的耗电量,然后更新自身的存储变量。
进行更新后,创建BatteryStatsHelper的类,就可以调用BatteryStatsHelper的getTotalPower、getUsageList等接口,获取终端耗电信息。

4.2.1 processAppUsage
refreshStats函数中比较重要的函数是processAppUsage:

private void processAppUsage(SparseArray<UserHandle> asUsers) {
    .................
    //得到BSImpl中的BatteryStatsImpl.Uid对象组成的列表,统计每个uid的耗电信息(主要是时间)
    final SparseArray extends Uid> uidStats = mStats.getUidStats();
    final int NU = uidStats.size();
    //循环计算每个应用的耗电量 
    for (int iu = 0; iu < NU; iu++) {
        final Uid u = uidStats.valueAt(iu);
        final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);

        //得到该app的占用CPU消耗的电量
        mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //得到该app的持有WakeLock消耗的电量
        mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //得到该app的使用射频消耗的电量
        mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType)

        //利用其它工具得到对应模块消耗的电量
        ..........................

        //计算这个应用耗电量的总和
        final double totalPower = app.sumPower();
        ..........

        // Add the app to the list if it is consuming power.
        if (totalPower != 0 || u.getUid() == 0) {
            //
            // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
            //
            //根据应用的类型,将应用耗电量信息添加到对应的列表
            final int uid = app.getUid();
            final int userId = UserHandle.getUserId(uid);
            if (uid == Process.WIFI_UID) {
                mWifiSippers.add(app);
            } else if (uid == Process.BLUETOOTH_UID) {
                mBluetoothSippers.add(app);
            } else if (!forAllUsers && asUsers.get(userId) == null
                    && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
                // We are told to just report this user's apps as one large entry.
                //如果该应用是单独某个用户下的应用则添加的userSippers列表
                List<BatterySipper> list = mUserSippers.get(userId);
                if (list == null) {
                    list = new ArrayList<>();
                    mUserSippers.put(userId, list);
                }
                list.add(app);
            } else {
                /否则直接添加到普通的应用列表
                mUsageList.add(app);
            }

            if (uid == 0) {
                //android系统
                osSipper = app;
            }
        }
    }

    if (osSipper != null) {
        // The device has probably been awake for longer than the screen on
        // time and application wake lock time would account for.  Assign
        // this remainder to the OS, if possible.
        // 计算os消耗的电量,除去app消耗
        mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtimeUs,
                mRawUptimeUs, mStatsType);
        osSipper.sumPower();
    }
}

4.2.2 processMiscUsage
现在我们看看refreshStats中另一个比较重要的函数processMiscUsage:

private void processMiscUsage() {
    addUserUsage();
    addPhoneUsage();
    addScreenUsage();
    addWiFiUsage();
    addBluetoothUsage();
    addIdleUsage(); // Not including cellular idle power
    // Don't compute radio usage if it's a wifi-only device
    if (!mWifiOnly) {
        addRadioUsage();
    }
}

processMiscUsage中使用的几个函数,也是用于计算工具统计每个模块的耗电量。
但与processAppUsage统计的侧重点不一样。
我们以addWiFiUsage为例:

private void addWiFiUsage() {
    BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
    //注意此处调用的是calculateRemaining;processAppUsage调用的是calculateApp
    mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType);
    aggregateSippers(bs, mWifiSippers, "WIFI");
    if (bs.totalPowerMah > 0) {
        mUsageList.add(bs);
    }
}

个人认为可以这样理解:
Android将一个模块的电量粗略分为两种。
一种是app在实际使用模块,引起的耗电量;一种是没有app调用,只要开启就会消耗的电量。
例如:WiFi只要开启,不论是否有app利用WiFi上网,WiFi都需要进行CSMA,因此会有基础的电量消耗。

4.2.3 计算方法举例
上面多次提到了计算工具,现在我们以数据业务相关的MobileRadioPowerCalculator为例,看看计算工具到底是如何工作的。

public class MobileRadioPowerCalculator extends PowerCalculator {
    .............
    public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) {
        //从PowerProfile下,得到各种场景的单位耗电量

        //radio on时的单位耗电量
        mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE);

        //各种信号强度下的单位耗电量
        for (int i = 0; i < mPowerBins.length; i++) {
            mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
        }

        //搜网时的单位耗电量
        mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING);
        mStats = stats;
    }

    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
            long rawUptimeUs, int statsType) {
        // Add cost of mobile traffic.
        //得到APP利用mobile发送分组的数量
        app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
                statsType);
        app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
                statsType);

        //得到APP在mobile active时的运行时间
        app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000;
        app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);

        //得到APP利用mobile发送字节的数量
        app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
                statsType);
        app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
                statsType);

        if (app.mobileActive > 0) {
            // We are tracking when the radio is up, so can use the active time to
            // determine power use.
            //总体时间
            mTotalAppMobileActiveMs += app.mobileActive;

            //运行时间 * 单位耗电量 得到整体的耗电情况
            app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60);
        } else {
            // We are not tracking when the radio is up, so must approximate power use
            // based on the number of packets.

            //mobile处于active时,app并没有运行
            //利用整体的收发包数量得到耗电量(不进一步详细分析,近似估值,这也是前面提及的refreshStats存在统计误差的原因之一)
            app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets)
                    * getMobilePowerPerPacket(rawRealtimeUs, statsType);
        }
        .................
    }

    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
            long rawUptimeUs, int statsType) {
        double power = 0;
        long signalTimeMs = 0;
        long noCoverageTimeMs = 0;
        for (int i = 0; i < mPowerBins.length; i++) {
            long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
                    / 1000;
            //计算处于每个信号强度下的耗电量
            final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000);
            .........
            //累加
            power += p;
            //整体的统计时间
            signalTimeMs += strengthTimeMs;
            if (i == 0) {
                //无信号时间
                noCoverageTimeMs = strengthTimeMs;
            }
        }

        //计算出搜网的耗电量
        final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
                / 1000;
        final double p = (scanningTimeMs * mPowerScan) / (60*60*1000);
        ............
        power += p;

        //计算出radio on,但app未使用时段的基础耗电量
        long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
        long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
        if (remainingActiveTimeMs > 0) {
            power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60);
        }

        //将计算的结果保存在app对应的BatterySipper中
        if (power != 0) {
            if (signalTimeMs != 0) {
                app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
            }
            app.mobileActive = remainingActiveTimeMs;
            app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
            app.mobileRadioPowerMah = power;
        }
    }
    .............
}

至此,我们终于对Android计算模块电量的方法有了基本的了解。
上面的代码其实也证实了我们之前的推测:
Android将一个模块的电量粗略分为两种。
一种是app在实际使用模块,引起的耗电量;一种是没有app调用,只要开启就会消耗的电量。
对于数据业务而言:
app在进行实际的收发数据,那么这部分电量就是app引起的耗电量。
终端维持一定的信号强度消耗的电量;搜网消耗的电量;打开数据开关,但没有数据收发时消耗的电量,统称为基础耗电量。

5、BSS的setBatteryState函数
BSS统计和计算电量的流程,我们已经分析完毕。

最后,分析一下以前博客的遗留问题:
之前的博客在分析BatteryService时,介绍过processValues函数的作用。
该函数在获取电池信息,进行更新、通知等操作时,调用过BSS的setBatteryState函数进行信息记录。

我们看一看setBatteryState函数:

public void setBatteryState(final int status, final int health, final int plugType,
        final int level, final int temp, final int volt, final int chargeUAh) {
    ............
    mHandler.post(new Runnable() {
        public void run() {
            synchronized (mStats) {
                //判断是否充电
                final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE;
                if (mStats.isOnBattery() == onBattery) {
                    // The battery state has not changed, so we don't need to sync external
                    // stats immediately.
                    //充电状态未改变,仅做记录
                    mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
                            chargeUAh);
                    return;
                }
            }

            //充电状态改变,则先同步WIFI、BT等的信息
            updateExternalStatsSync("battery-state", BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL);
            synchronized (mStats) {
                //然后进行记录
                mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
                        chargeUAh);
            }
        }
    }
}

从代码可以看到,实际的记录工作还是依赖于BSImpl的setBatteryStateLocked函数:

public void setBatteryStateLocked(int status, int health, int plugType, int level,
        int temp, int volt, int chargeUAh) {
    //判断当前是否是电池供电(即是否没有插着充电器)
    final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
    ..............
    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);
        }
    }
    ............
    if (onBattery != mOnBattery) {
        //充电状态发生改变,进行一些历史信息记录
        ..............
        //setOnBatteryLocked执行充电状态发生变化的逻辑
        setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
    } else {
        boolean changed = false;
        //判断电池信息是否发生变化,变化时将changed置为true
        .............
        if (changed) {  
            //如果电量信息发生变化,就将当前的状态添加到历史记录中 
            addHistoryRecordLocked(elapsedRealtime, uptime);  
        }
    }
    .............
}

从上面的代码可以看出,setBatteryStateLocked函数:
首先判断供电状态是否发生了变化,如果发生变化,则调用setOnBatteryLocked进行处理;
如果供电状态没有发生变化,则判断当前电池的各种信息是否发生变化。如果发生了变化,就将当前的电池信息添加到历史记录中。

接下来,我们看看setOnBatteryLocked相关的代码:

void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
        final int oldStatus, final int level, final int chargeUAh) {
    boolean doWrite = false;

    //触发回调
    Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
    m.arg1 = onBattery ? 1 : 0;
    mHandler.sendMessage(m);
    ..........
    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;
        //对应上面的注释
        //如果拔去充电器的时候电量是满的,或当前电量大于90,或从一个很低的电量充电到很高的电量,或电量历史记录的数据太大
        if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL
                || level >= 90
                || (mDischargeCurrentLevel < 20 && level >= 80)
                || (getHighDischargeAmountSinceCharge() >= 200
                        && mHistoryBuffer.dataSize() >= MAX_HISTORY_BUFFER))) {
            //变化的信息的数据足够时(累积耗电量足够大),生成一个snapshot,写入到mCheckinFile中
            if (getLowDischargeAmountSinceCharge() >= 20) {
                ........
            }
            doWrite = true;
            //重置电量统计的相关信息,清除当前的电量统计
            resetAllStatsLocked();      
        }
        ............
        //由充电变为不充电,记录下变成不充电状态时的电量  
        mDischargeCurrentLevel = mDischargeUnplugLevel = level;
        ............
        //更新TimeBase,和电量统计计时器有关  
        updateTimeBasesLocked(true, !screenOn, uptime, realtime);
    } else {
        ..........
        //记录变成充电状态时的电量  
        mDischargeCurrentLevel = mDischargePlugLevel = level;
        ..........
        //当前电量低于断开电源时的电量,表示电量消耗了
        if (level < mDischargeUnplugLevel) {
            //记录下电池用电时的电量消耗
            mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
            mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
        }
        ..........
        //更新TimeBase,和电量统计计时器有关
        updateTimeBasesLocked(false, !screenOn, uptime, realtime);
        ..........
    }

    //从充电变为未充电,或写入的间隔够大
    if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
        if (mFile != null) {
            //将信息写入到BatteryStats.bin中
            writeAsyncLocked();
        }
    }
}

总结
在这篇博客中,我们介绍了BatteryStatsService的主要流程。
从代码可以看出,BSS还是比较复杂的,原因是Android试图对系统耗电量做非常细致统计,导致统计项非常繁琐。
许多细节我们不需要去死记它,个人觉得知道电源统计的通知原理和统计方法就行了。

你可能感兴趣的:(Android源码学习笔记)