1. 程式人生 > >Android App耗電量統計

Android App耗電量統計

還沒有完成的,初稿

App耗電量統計:原始碼

  • PowerUsageSummary.java 繼承PowerUsageBase.java類
BatteryHistoryPreference類--sp 獲取耗電量歷史(讀取sp檔案)--sp檔案資料來自xml檔案(power_usage_summary.xml)
PreferenceGroup類--統計所有App耗電量同history

refreshStats()方法重新整理耗電量狀態:內部有以下幾個類(BatteryStatsHelper.java)獲取
1.PowerProfile:提供部件電流數值。
2.BatteryStats:App各部件執行時間。(獲取封裝在BatteryStatsHelper類中具體看下面)
final BatteryStats stats = mStatsHelper.getStats();
3.根據BatteryStatsHelper類中統計
- App
耗電量統計:processAppUsage()
- 硬體耗電量統計:processMiscUsage() 獲取耗電量更新SP資料: mAppListGroup.addPreference(pref);
  • PowerProfile.java
    (1)Android部件電流資訊存於:power_profile.xml

(2)每個OEM廠商有私有power_profile.xml

(3)PowerProfile讀取power_profile.xml,並提供API訪問部件電流數值。

/**
 * Reports power consumption values for various device activities. Reads values from an XML file.
 * Customize the XML file for different devices.
 * [hidden]
 */
38public class PowerProfile { private void readPowerValuesFromXml(Context context) { int id = com.android.internal.R.xml.power_profile;//xml檔案power_profile.xml //下面就是xml解析了根據下面定義節點獲取值: //private static final String TAG_DEVICE = "device"; //private static final String TAG_ITEM = "item"; //private static final String TAG_ARRAY = "array";
//private static final String TAG_ARRAYITEM = "value"; //private static final String ATTR_NAME = "name"; .... 最後新增資料到sPowerMap (一個HashMap) sPowerMap.put(key, (double) value); key是各部件名稱。value是各部件的電流值 } }

//power_profile.xml 各部件定義的電流值 螢幕 cpu 藍芽 wifi gps等等

<device name="Android">  
  <!-- Most values are the incremental current used by a feature,  
       in mA (measured at nominal voltage).  
       The default values are deliberately incorrect dummy values.  
       OEM's must measure and provide actual values before  
       shipping a device.  
       Example real-world values are given in comments, but they  
       are totally dependent on the platform and can vary  
       significantly, so should be measured on the shipping platform  
       with a power meter. -->  
  <item name="none">0</item>  
  <item name="screen.on">100</item>  <!-- ~200mA -->  
  <item name="screen.full">200</item>  <!-- ~300mA -->  
  <item name="bluetooth.active">90.5</item> <!-- Bluetooth data transfer, ~10mA -->  
  <item name="bluetooth.on">2.5</item>  <!-- Bluetooth on & connectable, but not connected, ~0.1mA -->  
  <item name="wifi.on">1.25</item>  <!-- ~3mA -->  
  <item name="wifi.active">130</item>  <!-- WIFI data transfer, ~200mA -->  
  <item name="wifi.scan">100</item>  <!-- WIFI network scanning, ~100mA -->  
  <item name="dsp.audio">30.5</item> <!-- ~10mA -->  
  <item name="dsp.video">72.5</item> <!-- ~50mA -->  
  <item name="radio.active">135</item> <!-- ~200mA -->  
  <item name="radio.scanning">5.3</item> <!-- cellular radio scanning for signal, ~10mA -->  
  <item name="gps.on">30</item> <!-- ~50mA -->  
  <!-- Current consumed by the radio at different signal strengths, when paging -->  
  <array name="radio.on"> <!-- Strength 0 to BINS-1 -->  
      <value>3.5</value> <!-- ~2mA -->  
      <value>2.4</value> <!-- ~1mA -->  
  </array>  
  <!-- Different CPU speeds as reported in  
       /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state -->  
  <array name="cpu.speeds">  
      <value>624000</value> <!-- 624 MHz CPU speed -->  
      <value>699563</value> <!-- 699 MHz CPU speed -->  
      <value>799500</value> <!-- 799 MHz CPU speed -->  
      <value>899438</value> <!-- 899 MHz CPU speed -->  
      <value>999375</value> <!-- 999 MHz CPU speed -->  
      <value>1099313</value> <!-- 1099 MHz CPU speed -->  
      <value>1199250</value> <!-- 1199 MHz CPU speed -->  
      <value>1299188</value> <!-- 1299 MHz CPU speed -->  
      <value>1399125</value> <!-- 1399 MHz CPU speed -->  
      <value>1499063</value> <!-- 1499 MHz CPU speed -->  
      <value>1599000</value> <!-- 1599 MHz CPU speed -->  
  </array>  
  <!-- Current when CPU is idle -->  
  <item name="cpu.idle">2.2</item>  
  <!-- Current at each CPU speed, as per 'cpu.speeds' -->  
  <array name="cpu.active">//各個cpu頻段的功耗  
      <value>54</value>  
      <value>63</value>  
      <value>72</value>  
      <value>80</value>  
      <value>90</value>  
      <value>100</value>  
      <value>109</value>  
      <value>115</value>  
      <value>121</value>  
      <value>127</value>  
      <value>135</value>  
  </array>  
  <!-- This is the battery capacity in mAh (measured at nominal voltage) -->  
  <item name="battery.capacity">2000</item>  
</device>  
  • BatteryStats.java 抽象類implements Parcelable(android 序列化)
/**
 * 統計電池各部件使用情況,時間以微秒為單位
 * A class providing access to battery usage statistics, including information on
 * wakelocks, processes, packages, and services.  All times are represented in microseconds
 * except where indicated otherwise. 
 * @hide
 */
public abstract class BatteryStats implements Parcelable {
    private static final String TAG = "BatteryStats";

    private static final boolean LOCAL_LOGV = false;

   /** @hide */
    public static final String SERVICE_NAME = "batterystats";
    ....
}
  • BatteryStatsHelper.java Android BatteryStatsHelper深入理解(and5.1)
    作用:主要是統計各個應用,多使用者的每個使用者,以及藍芽,螢幕等耗電統計
    BatteryStatsHelper 的例項在PowerUsageBase類初始化:
@Override
    public void onAttach(Activity activity) {
       super.onAttach(activity);
       mUm = (UserManager) activity.getSystemService(Context.USER_SERVICE);
       mStatsHelper = new BatteryStatsHelper(activity, true);//例項化
   }

BatteryStatsHelper.java 類內部方法

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)); //aidl服務 SERVICE_NAME = "batterystats";;
    mPowerProfile = new PowerProfile(mContext);  //從power_profile.xml讀取的各個器件的電源消耗引數具體檢視上面詳解這個類的連結
} 

在PowerUsageSummary類分析獲取App各部件執行時間 呼叫BatteryStats stats = mStatsHelper.getStats();

public BatteryStats getStats() {
       if (mStats == null) {
            load(); //如果mStats為空 呼叫load方法否則返回mStats
        }
        return mStats;
    }

private void load() {
        if (mBatteryInfo == null) {//mBatteryInfo空判斷在Create裡面初始化了
            return;
        }
        mStats = getStats(mBatteryInfo); //呼叫getStats,返回BatteryStatsImpl實現類
        if (mCollectBatteryBroadcast) {
            mBatteryBroadcast = mContext.registerReceiver(null,
                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));//註冊監聽電量改變廣播
        }
    }

private static BatteryStatsImpl getStats(IBatteryStats service) {
        try {
            ParcelFileDescriptor pfd = service.getStatisticsStream();//BatteryStatsService服務類:收集所有消耗電池資訊 呼叫updateExternalStatsSync()從外部獲取資料來源(無線控制器,bluetooth晶片組)和更新batterystats資訊。
            if (pfd != null) {
                try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {//下面讀取序列化反序列化獲取batterystats資料
                    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();
    }

接下來就是refreshState更新電池最新狀態的,statsType是指充電狀態還是非充電狀態,asUsers指的是userId(多使用者)

public void refreshStats(int statsType, List<UserHandle> asUsers) {  
    final int n = asUsers.size();  
    SparseArray<UserHandle> users = new SparseArray<UserHandle>(n);  
    for (int i = 0; i < n; ++i) {  
        UserHandle userHandle = asUsers.get(i);  
        users.put(userHandle.getIdentifier(), userHandle);  
    }  
    refreshStats(statsType, users);  
}  

/** 
 * Refreshes the power usage list. 
 */  
public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) {  
    refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,  
            SystemClock.uptimeMillis() * 1000);  
} 

refreshStats方法:processAppUsage()計算每個uid的耗電情況, processMiscUsage();//計算比如螢幕、wifi、藍芽等耗電

 public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
           long rawUptimeUs) {
        ...
        ...
        ...
        processAppUsage(asUsers);//計算每個uid的耗電情況包含:CPU Wakelock WIFI 藍芽 感測器 Camera FlashLight MobileRadio
        ...
        ...
        processMiscUsage();//計算比如螢幕、wifi、藍芽等耗電
        ...
        ...
        }

processAppUsage(asUsers){
 mCpuPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);//每個App Cpu耗電量計算
 mWakelockPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);//每個App Wakelock耗電量計算
 mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);//每個App MobileRadio耗電量計算
 mWifiPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);//每個App WIFI耗電量計算
 mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);//每個App 藍芽耗電量計算
 mSensorPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);//每個App 感測器耗電量計算
 mCameraPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);//每個App Camera耗電量計算
 mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtimeUs, mRawUptimeUs, mStatsType);//每個App Flashlight耗電量計算
 }

 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();
     }
   }
  • BatteryEntry.java

看原始碼解釋:相當於bean耗電量資料(package name , icon , iconId; // For passing to the detail screen.)
內部維護一個 NameAndIconLoader執行緒去載入BatteryEntry資料。

/**
41 * Wraps the power usage data of a BatterySipper with information about package name
42 * and icon image.
43 */

建構函式統計硬體軟體App資訊:

 public BatteryEntry(Context context, Handler handler, UserManager um, BatterySipper sipper) {
130        sHandler = handler;
131        this.context = context;
132        this.sipper = sipper;
133        switch (sipper.drainType) {
134            case IDLE:
135                name = context.getResources().getString(R.string.power_idle);
136                iconId = R.drawable.ic_settings_phone_idle;
137                break;
138            case CELL:
139                name = context.getResources().getString(R.string.power_cell);
140                iconId = R.drawable.ic_settings_cell_standby;
141                break;
142            case PHONE://通話
143                name = context.getResources().getString(R.string.power_phone);
144                iconId = R.drawable.ic_settings_voice_calls;
145                break;
146            case WIFI://wifi
147                name = context.getResources().getString(R.string.power_wifi);
148                iconId = R.drawable.ic_settings_wireless;
149                break;
150            case BLUETOOTH://藍芽
151                name = context.getResources().getString(R.string.power_bluetooth);
152                iconId = R.drawable.ic_settings_bluetooth;
153                break;
154            case SCREEN://螢幕
155                name = context.getResources().getString(R.string.power_screen);
156                iconId = R.drawable.ic_settings_display;
157                break;
158            case FLASHLIGHT:
159                name = context.getResources().getString(R.string.power_flashlight);
160                iconId = R.drawable.ic_settings_display;
161                break;
162            case APP:
163                name = sipper.packageWithHighestDrain;
164                break;
165            case USER: {
166                UserInfo info = um.getUserInfo(sipper.userId);
167                if (info != null) {
168                    icon = Utils.getUserIcon(context, um, info);
169                    name = Utils.getUserLabel(context, info);
170                } else {
171                    icon = null;
172                    name = context.getResources().getString(
173                            R.string.running_process_item_removed_user_label);
174                }
175            } break;
176            case UNACCOUNTED:
177                name = context.getResources().getString(R.string.power_unaccounted);
178                iconId = R.drawable.ic_power_system;
179                break;
180            case OVERCOUNTED:
181                name = context.getResources().getString(R.string.power_overcounted);
182                iconId = R.drawable.ic_power_system;
183                break;
184            case CAMERA:
185                name = context.getResources().getString(R.string.power_camera);
186                iconId = R.drawable.ic_settings_camera;
187                break;
188        }
189        if (iconId > 0) {
190            icon = context.getDrawable(iconId);
191        }
192        if ((name == null || iconId == 0) && this.sipper.uidObj != null) {
193            getQuickNameIconForUid(this.sipper.uidObj.getUid());
194        }
195    }

總結:

1.Android電量消耗統計在:BatteryStatsHelper類
2.getPowerProfile() 獲取PowerProfile,PowerProfile類是(讀取power_profile.xml檔案)獲取Android各部件電流值
3.getStats()獲取BatteryStats(App各部件執行時間微秒為單位),通過BatteryStatsService服務類:收集所有消耗電池資訊 呼叫updateExternalStatsSync()從外部獲取資料來源(無線控制器,bluetooth晶片組)和更新batterystats資訊。
4.refreshState更新電池最新狀態
- processAppUsage() :App耗電量統計
- processMiscUsage() :硬體耗電量統計