Android电量统计

Android电量统计

前言

   在维护电量管家应用以及学习处理一些功耗问题的时候,经常会接触电量统计相关的知识,抽空总结下这块知识,方便自己以及他人的学习。

电量统计

概述

   在Andorid系统中的电量统计分为两种:一种是对于软件的耗电统计;一种是对于硬件设备的耗电统计。很多产商会根据系统对于这两种耗电类型的统计,给用户展示用电详情。

   而在这个过程中主要的参与者如下:

/frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl
/framework/base/core/java/com/andorid/internal/os/PowerProfile.java
/framework/base/core/java/com/andorid/internal/os/BatteryStatsHelper.java
/framework/base/core/java/com/andorid/internal/os/BatterySipper.java
/framework/base/core/res/res/xml/power_profile.xml

   其中:

   BatteryStatsImpl是用以收集电量使用信息的实现类,继承自BatteryStats,在电量统计日志分析这篇文章中所描述的电量统计日志相关的东西,都是从这个类中收集交于BatteryStatsService来dump出来的,如下:


//BatteryStatsService
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {

    ...

    synchronized (mStats) {
        mStats.dumpLocked(mContext, pw, flags, reqUid, historyStart);
        if (writeData) {
            mStats.writeAsyncLocked();
        }
    }
}

//BatteryStatsImpl

public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {

    ...

    if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
        pw.println("Statistics since last charge:");
        pw.println("  System starts: " + getStartCount()
                + ", currently on battery: " + getIsOnBattery());
        //调用重载方法,输出电量统计信息
        dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid,
                (flags&DUMP_DEVICE_WIFI_ONLY) != 0);
        pw.println();
    }

}

   PowerProfile.java用于获取各个组件的电流数值;power_profile.xml是一个可配置的功耗数据文件,每个手机产商都有自己私有的power_prfile.xml。

   BatteryStatsHelper则是用来统计各个应用,多用户的每个用户,以及蓝牙,屏幕等耗电统计的类,其中processAppUsage()方法是用以计算应用软件耗电详情,processMiscUsage()方法是用来计算硬件耗电详情。

   BatterySipper是耗电信息的实体类,主要包括应用耗电,系统服务耗电,硬件耗电等类型。

电量统计服务

   电量统计服务BatteryStatsService(BSS),顾名思义就是用来统计电量使用情况的服务,下面对其做简单的介绍。

   接触过系统源码的人应该多少都有了解,系统在启动后,会在SystemServer中启动各种服务,典型的如AMS,PMS,WMS等,当然电量统计服务BSS也会在这个时机中被创建出来,值得注意的是BSS真正创建的时机是在AMS被创建时,其依附于AMS,下面我们来看看源码。


public ActivityManagerService(Context systemContext) {

    ...

    //创建电量状态服务
    mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
    mBatteryStatsService.getActiveStatistics().readLocked();
    mBatteryStatsService.scheduleWriteToDisk();
    mOnBattery = DEBUG_POWER ? true
            : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
    mBatteryStatsService.getActiveStatistics().setCallback(this);

    ...
}

private void start() {
    Process.removeAllProcessGroups();
    //启动CPU调度线程
    mProcessCpuThread.start();
    //设置服务的参数,并添加到ServiceManager
    mBatteryStatsService.publish(mContext);
    mAppOpsService.publish(mContext);
    Slog.d("AppOps", "AppOpsService published");
    //LocalServices类似于ServiceManager的功能,主要用于系统进程内部访问的一些服务
    LocalServices.addService(ActivityManagerInternal.class, new LocalService());
}

   从上面可以看出,BSS是在AMS的构造方法中被创建出来的,随后在AMS的start方法中设置其服务的参数,我们先回过头看下BSS的构造方法里,做了些什么。


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.
    //对电量状态服务的具体实现类
    mStats = new BatteryStatsImpl(systemDir, handler, mHandler, this);
}

   可以看到,在BSS的构造方法中,先是创建了电量统计服务的消息队列用来接收消息,然后创建了消息处理对象用来处理消息,最后创建了电量统计收集类BatteryStatsImpl,从而为电量统计服务做好准备,这里也可以看出BSS与BatteryStatsImpl之间的关系。

   我们在回头看下,AMS中的start方法调用的BSS中的publish方法。

public void publish(Context context) {
    mContext = context;
    mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
            com.android.internal.R.integer.config_radioScanningTimeout)
            * 1000L);
    //设置电量统计服务的基础信息 单位时间内的电量统计,配置文件位于frameworks/base/core/res/res/xml/power_profile.xml
    mStats.setPowerProfile(new PowerProfile(context));
    ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
}

public PowerProfile(Context context) {
    // Read the XML file for the given profile (normally only one per
    // device)
    if (sPowerMap.size() == 0) {
        readPowerValuesFromXml(context);
    }
    initCpuClusters();
}

private void readPowerValuesFromXml(Context context) {
    int id = com.android.internal.R.xml.power_profile;

    ...    
}

   可以到看publish方法主要是设置了一些配置信息,而setPowerProfile方法就是设置了系统的功耗数据文件,在创建PowerProfile对象的同时会去读取系统的功耗配置文件信息,从而获取组件信息以及各个组件的电流数值。前面说到BSS中真正去收集信息的是BatteryStatsImpl,而BatteryStatsImpl中其父类方法中创建了BatteryStatsHelper对象,BatteryStatsHelper的构造方法也会去读取该配置信息,并且根据这些数值,对耗电信息进行计算统计。

//BatteryStatsImpl extends BatteryStats
final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
helper.create(this);
helper.refreshStats(which, UserHandle.USER_ALL);
final List<BatterySipper> sippers = helper.getUsageList();

   至此BSS就被创建起来了,为系统提供电量统计服务。

注:上述只是简单的介绍了下BSS,读者有兴趣,可以自行看源码,深入理解。

   了解了电量统计服务,下面来学习下耗电统计的计算方法。

耗电计算方法

   前面说过,BatteryStatsHelper负责计算统计软件与硬件耗电详情。再聊计算方法之前,先说下这个类中的几个方法。

   首先是create()方法,该方法有重载,用来获取用电信息,功耗配置信息等。在计算获取耗电详情时,必须先调用该方法。

public void create(BatteryStats stats) {
    mPowerProfile = new PowerProfile(mContext);
    mStats = stats;
}

public void create(Bundle icicle) {
    if (icicle != null) {
        mStats = sStatsXfer;
        mBatteryBroadcast = sBatteryBroadcastXfer;
    }
    mBatteryInfo = IBatteryStats.Stub.asInterface(
            ServiceManager.getService(BatteryStats.SERVICE_NAME));
    mPowerProfile = new PowerProfile(mContext);
}

   其次是refreshStats()顾名思义就是用来刷新状态的方法,该方法有多个重载方法,最终都会调用以下方法。

public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
        long rawUptimeUs) {

        //内容太多,就不附上了
        ...

        processAppUsage(asUsers);

        ...

        processMiscUsage();

        ...

}

   refreshStats方法中会刷新很多数据,对于计算方法中,最主要是调用了这两个函数,一个是processAppUsage()方法用以计算软件耗电,一个是processMiscUsage()用以计算硬件耗电。

   下面我们分别来介绍这两种类型的计算方法。

软件类

   软件耗电是针对单个用户而言的,大家都知道Android是支持多用户的,所以软件耗电需要传入用户信息。

private void processAppUsage(SparseArray<UserHandle> asUsers) {
    //判断是否统计所有用户的App耗电使用情况,目前该参数为true
    final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
    mStatsPeriod = mTypeBatteryRealtimeUs;//耗电统计时长

    BatterySipper osSipper = null;
    //每个uid的统计信息
    final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
    final int NU = uidStats.size();
    //遍历每个uid的耗电情况
    for (int iu = 0; iu < NU; iu++) {
        final Uid u = uidStats.valueAt(iu);
        final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
        //CPU功耗
        mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //锁唤醒功耗
        mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //无线电功耗
        mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //WIFI功耗
        mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //蓝牙功耗
        mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //传感器功耗
        mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //相机功耗
        mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);
        //闪光灯功耗
        mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);

        final double totalPower = app.sumPower();//对8项耗电统计进行累加
        if (DEBUG && totalPower != 0) {
            Log.d(TAG, String.format("UID %d: total power=%s", u.getUid(),
                    makemAh(totalPower)));
        }

        // 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.
            //将app添加到 app list, WiFi, Bluetooth等,或其他用户列表
            final int uid = app.getUid();
            final int userId = UserHandle.getUserId(uid);
            if (uid == Process.WIFI_UID) {//uid为wifi的情况
                mWifiSippers.add(app);
            } else if (uid == Process.BLUETOOTH_UID) {//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.
                //就目前来说 forAllUsers=true,不会进入此分支
                List<BatterySipper> list = mUserSippers.get(userId);
                if (list == null) {
                    list = new ArrayList<>();
                    mUserSippers.put(userId, list);
                }
                list.add(app);
            } else {
                mUsageList.add(app);//把app耗电加入到mUsageList
            }

            if (uid == 0) {
                osSipper = app;// root用户,代表操作系统的耗电量
            }
        }
    }
    //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.
        mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtimeUs,
                mRawUptimeUs, mStatsType);
        osSipper.sumPower();
    }
}

   可以到看,软件类功耗类型是DrainType.APP,计算耗电量之前,先记录了耗电统计时长mTypeBatteryRealtimeUs。

mTypeBatteryRealtimeUs = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);

public long computeRealtime(long curTime, int which) {
    switch (which) {
        case STATS_SINCE_CHARGED:
            return mRealtime + getRealtime(curTime);
        case STATS_CURRENT:
            return getRealtime(curTime);
        case STATS_SINCE_UNPLUGGED:
            return getRealtime(curTime) - mUnpluggedRealtime;
    }
    return 0;
}

   mTypeBatteryRealtime会根据用户所传递进来的参数,去匹配得到时间参数,其中STATS_SINCE_CHARGED表示从上次充满电数据后;STATS_CURRENT表示现在时刻;STATS_SINCE_UNPLUGGED表示拔掉USB线后的时间。从这里表明充电时间的计算是从上一次充电后,拔掉设备到现在的耗电量统计。

   然后列出8项耗电计算项,去计算每个的应用Uid对应在这些项上的电量损耗。8大项指标如下:

计算项对应的类
CPU功耗CpuPowerCalculator.java
锁唤醒功耗WakelockPowerCalculator.java
无线电功耗MobileRadioPowerCalculator.java
WIFI功耗WifiPowerCalculator.java / WifiPowerEstimator.java
蓝牙功耗BluetoothPowerCalculator.java
传感器功耗SensorPowerCalculator.java
相机功耗CameraPowerCalculator.java
闪光灯功耗FlashlightPowerCalculator.java

   在计算完以上的各项数据后,会通过BatterySipper中的sumPower()方法去计算总的耗电量,该方法如下。


public double sumPower() {
    return totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah +
            sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
            flashlightPowerMah + bluetoothPowerMah;
}
计算项说明
usage用户通用功耗(硬件计算)
wifiWifi功耗
gpsGps功耗
cupCup的功耗
sensor传感器功耗
mobileRadio无线电功耗
wakeLock锁唤醒功耗
camera相机功耗
flashLight闪光灯功耗
blueTooth蓝牙功耗

   最后将计算的数据归类,分为:

  • mWifiSippers.add(app): uid为wifi的耗电情况
  • mBluetoothSippers.add(app): uid为蓝牙的耗电情况
  • mUsageList.add(app): app耗电加入到mUsageList
  • osSipper: root用户,代表操作系统的耗电量,app之外的wakelock耗电也计算该项

   遍历完所有的应用的Uid后,软件类的耗电详情就统计完了,其计算总公式为:

SoftWare_Power = Uid_Power_1 + Uid_Power_2 + … + Uid_Power_n

   值得注意的是,由于是通过Uid来统计的,如果有多个App共享Uid,那么这些应用的耗电详情会被统计在一起,计算公式如下:

Uid_Power = process_1_Power + … + process_N_Power ;

   其中所有进程都是属于同一个uid。当同一的uid下,只有一个进程时,Uid_Power = process_Power;,而每个进程的耗电数据,就是通过上面九项数据之和得来的, 即:

process_Power = Gps功耗 +CPU功耗 + 锁唤醒功耗 + 无线电功耗 + WIFI功耗 + 蓝牙功耗 + 传感器功耗 + 相机功耗 + 闪光灯功耗。

注:用户通用功耗是属于硬件功耗,GPS功耗计算是在传感器里,android7.0以后才正式加入了蓝牙功耗统计。

   下面以CPU功耗项为例,介绍软件功耗各项指标的计算方法。

注:每个子项的计算公式不尽相同,但是逻辑基本一致,所以不一一介绍,有兴趣的读者可以自行了解。

   CPU功耗项是通过CpuPowerCalculator.java这个类来计算的,我们来看看这个类的初始化。

PowerCalculator mCpuPowerCalculator;

if (mCpuPowerCalculator == null) {
    mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
}
mCpuPowerCalculator.reset();

public CpuPowerCalculator(PowerProfile profile) {
    mProfile = profile;
}

   首先,所有计算项类都是PowerCalculator的子类,其提供了calculateApp()抽象方法以及calculateRemaining()、reset()两个方法。其中calculateApp()用于计算App耗电数值,calculateRemaining()计算不属于App的耗电数值,reset()用以重置一些参数。

   其次,在创建对象时,传入了PowerProfile对象,前面介绍了,在PowerProfile中会去读取power_profile.xml中所规定的功耗值数据,从而提供相应的基础功耗值,PowerCalculator所有子类会这个基础功耗值来计算出较为精准的功耗统计结果。

   然后,让我们看下CPU功耗的计算过程,具体看如下代码。


public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                         long rawUptimeUs, int statsType) {
    //获取cpu在用户态和内核态的执行时长
    app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;

    // Aggregate total time spent on each cluster.
    long totalTime = 0;
    // 获取cpu簇的个数
    final int numClusters = mProfile.getNumCpuClusters();
    for (int cluster = 0; cluster < numClusters; cluster++) {
        //获取每种cpu簇的主频等级的级数
        final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
        for (int speed = 0; speed < speedsForCluster; speed++) {
            //获取Cpu不同频点下的运行时间
            totalTime += u.getTimeAtCpuSpeed(cluster, speed, statsType);
        }
    }
    //获取Cpu总共的运行时间
    totalTime = Math.max(totalTime, 1);

    double cpuPowerMaMs = 0;
    //计算cup耗电量
    for (int cluster = 0; cluster < numClusters; cluster++) {
        final int speedsForCluster = mProfile.getNumSpeedStepsInCpuCluster(cluster);
        for (int speed = 0; speed < speedsForCluster; speed++) {
            // 计算时间比率
            final double ratio = (double) u.getTimeAtCpuSpeed(cluster, speed, statsType) /
                    totalTime;
            final double cpuSpeedStepPower = ratio * app.cpuTimeMs *
                    mProfile.getAveragePowerForCpu(cluster, speed);//根据cpu簇以及频率登记,获取单位时间内cup的功耗值
            if (DEBUG && ratio != 0) {
                Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " step #"
                        + speed + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
                        + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
            }
            // 总功耗计算
            cpuPowerMaMs += cpuSpeedStepPower;
        }
    }
    //换算成mAH
    app.cpuPowerMah = cpuPowerMaMs / (60 * 60 * 1000);

    if (DEBUG && (app.cpuTimeMs != 0 || app.cpuPowerMah != 0)) {
        Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + app.cpuTimeMs + " ms power="
                + BatteryStatsHelper.makemAh(app.cpuPowerMah));
    }

    // Keep track of the package with highest drain.
    //追踪不同进程的耗电情况
    double highestDrain = 0;

    app.cpuFgTimeMs = 0;
    final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
    final int processStatsCount = processStats.size();
    //统计同一个uid的不同进程的耗电情况
    for (int i = 0; i < processStatsCount; i++) {
        final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
        final String processName = processStats.keyAt(i);
        app.cpuFgTimeMs += ps.getForegroundTime(statsType);

        final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType)
                + ps.getForegroundTime(statsType);
        //App可以有多个packages和多个不同的进程,跟踪耗电最大的进程
        // Each App can have multiple packages and with multiple running processes.
        // Keep track of the package who's process has the highest drain.
        if (app.packageWithHighestDrain == null ||
                app.packageWithHighestDrain.startsWith("*")) {
            highestDrain = costValue;
            app.packageWithHighestDrain = processName;
        } else if (highestDrain < costValue && !processName.startsWith("*")) {
            highestDrain = costValue;
            app.packageWithHighestDrain = processName;
        }
    }
    //当Cpu前台时间 大于Cpu时间,将cpuFgTimeMs赋值为cpuTimeMs
    // Ensure that the CPU times make sense.
    if (app.cpuFgTimeMs > app.cpuTimeMs) {
        if (DEBUG && app.cpuFgTimeMs > app.cpuTimeMs + 10000) {
            Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
        }

        // Statistics may not have been gathered yet.
        app.cpuTimeMs = app.cpuFgTimeMs;
    }
}

   这里,我们看到,在calculateApp()方法中,通过mProfile对象获取了Cup簇的个数、每种cpu簇的主频等级的级数以及根据cpu簇以及频率登记,获取单位时间内cup的功耗值,并根据这些数值计算出CPU功耗值,也证实了前面的说法。

   最后,给出CPU项计算公式:

cpuPower = ratio_1 * cpu_time * cpu_ratio_1_power + … +ratio_n * cpu_time * cpu_ratio_n_power
其中: ratio_i = cpu_speed_time/ cpu_speeds_total_time,(i=1,2,…,n,n为CPU频点个数)

硬件类

   硬件类功耗,就没有分什么Uid了,直接是根据硬件属性来统计的,其方法如下:

private void processMiscUsage() {
    //用户功耗
    addUserUsage();
    //通话功耗
    addPhoneUsage();
    //屏幕功耗
    addScreenUsage();
    //wifi功耗
    addWiFiUsage();
    //蓝牙功耗
    addBluetoothUsage();
    //Cup空闲功耗
    addIdleUsage(); // Not including cellular idle power
    // Don't compute radio usage if it's a wifi-only device
    if (!mWifiOnly) {
        //  移动无线功耗
        addRadioUsage();
    }
}

   硬件功耗的子项共分为7项:

计算项说明类型
UserUsage用户功耗DrainType.USER
PhoneUsage手机通话功耗DrainType.PHONE
ScreenUsage手机屏幕功耗DrainType.SCREEN
WiFiUsageWifi功耗DrainType.WIFI
BluetoothUsage蓝牙功耗DrainType.BLUETOOTH
IdleUsageCPU Idle功耗DrainType.IDLE
RadioUsage移动无线功耗DrainType.CELL

   硬件功耗的总公式如下:

Hardware_Power = UserUsage + PhoneUsage + WiFiUsage + WiFiUsage + BluetoothUsage + CPU IdleUsage + RadioUsage;

   下面以手机通话功耗为例,介绍硬件功耗子项的计算方式。

private void addPhoneUsage() {
    // 获取手机通话时间
    long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtimeUs, mStatsType) / 1000;
    // 通过通话时间来计算总电量
    double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)// 获取单位时间内通话功耗
            * phoneOnTimeMs / (60*60*1000);
    if (phoneOnPower != 0) {
        // 将该项数值添加到功耗列表中
        addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
    }
}

private BatterySipper addEntry(DrainType drainType, long time, double power) {
    BatterySipper bs = new BatterySipper(drainType, null, 0);
    bs.usagePowerMah = power;
    bs.usageTimeMs = time;
    // 计算该项总的功耗
    bs.sumPower();
    // 添加至功耗列表
    mUsageList.add(bs);
    return bs;
}

   手机通话计算公式:

phone_powers = (phoneOnTimeMs * phoneOnPower) / (60* 60* 1000)

结语

   根据上述所计算出来的软件与硬件的功耗详情,在应用中,我们可以通过反射去调用框架中的代码逻辑,从而获取功耗信息,再将这些数据进行一些处理,然后通过应用界面展示给客户。


try {
    mStatsHelper = Class.forName("com.android.internal.os.BatteryStatsHelper");
    Constructor<?>[] constructors = mStatsHelper.getDeclaredConstructors();
    for (int i = 0; i < constructors.length; i++) {
        mConstructor = constructors[i];
        if (mConstructor.getGenericParameterTypes().length == 3)
            break;
    }

    mConstructor.setAccessible(true);
    mStatsHelperObj = mConstructor.newInstance(context, false, false);
    methodCreate = mStatsHelper.getDeclaredMethod(METHOD_BATTERY_CREATE, Bundle.class);
    methodCreate.setAccessible(true);
    methodCreate.invoke(mStatsHelperObj, bundle);
    methodRefresh = mStatsHelper.getDeclaredMethod(METHOD_BATTERY_STATS_REFRESH, int.class, int.class);
    methodRefresh.setAccessible(true);
    methodRefresh.invoke(mStatsHelperObj, statsType, asUsers);

    methodGetUsageList = mStatsHelper.getDeclaredMethod(METHOD_BATTERY_GETUSAGELIST);
    methodGetUsageList.setAccessible(true);

    batterySipperList = (List<BatterySipper>)methodGetUsageList.invoke(mStatsHelperObj);

} catch (Exception e) {
    e.printStackTrace();
    Log.e(TAG, "refreshStats exception: " +e);
} finally {
    return batterySipperList;
}

   至此本人所理解的电量统计部分基本上介绍完毕,但这并不是Android中电量统计的所有内容,还有很多细节需要读者去阅读源码细细体会,并且由于本人能力有限,有遗漏或错误的地方,还请批评指出,谢谢。

参考博客

Android耗电统计算法

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值