Android BatteryManager的使用及BatteryService源码分析

当需要监控系统电量时,用 BatteryManager 来实现。

参考官网 监控电池电量和充电状态

获取电池信息

通过监听 Intent.ACTION_BATTERY_CHANGED 广播实现,在广播接收器中获取电池信息。

这是个粘性广播,即使过了广播发出的时间点后再注册广播接收器,也可以收到上一个广播消息。

按照我的 Demo ,没有触发电量变化,直接打开这个页面就可以收到广播。

复制代码
public class BatteryActivity extends AppCompatActivity {

    private final static String TAG = BatteryActivity.class.getSimpleName();
    private BatteryReceiver receiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_battery);
        Objects.requireNonNull(getSupportActionBar()).setTitle(TAG);

        receiver = new BatteryReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
        registerReceiver(receiver, filter);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unregisterReceiver(receiver);
    }

    private static class BatteryReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
                int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
                int temperature =  intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
                int status  = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 0);
                int health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, 0);
                int pluggen = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
            }
        }
    }
}

电量

电量百分比 = 当前电量 / 电池总量

复制代码
int level =  intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
int batteryPct = level * 100 / scale;

电池温度

模拟器获取的值是 250

复制代码
int temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);

电池健康

复制代码
int health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, 0);

使用模拟器测试,

得到

电池健康
Unknown 1
Good 2
Overheat 3
Dead 4
Overvoltage 5
Failed 6

看 BatteryManager 源码,

复制代码
	import android.hardware.health.V1_0.Constants;

    // values for "health" field in the ACTION_BATTERY_CHANGED Intent
    public static final int BATTERY_HEALTH_UNKNOWN = Constants.BATTERY_HEALTH_UNKNOWN; //1
    public static final int BATTERY_HEALTH_GOOD = Constants.BATTERY_HEALTH_GOOD; //2
    public static final int BATTERY_HEALTH_OVERHEAT = Constants.BATTERY_HEALTH_OVERHEAT; //3
    public static final int BATTERY_HEALTH_DEAD = Constants.BATTERY_HEALTH_DEAD; //4
    public static final int BATTERY_HEALTH_OVER_VOLTAGE = Constants.BATTERY_HEALTH_OVER_VOLTAGE; // 5
    public static final int BATTERY_HEALTH_UNSPECIFIED_FAILURE = Constants.BATTERY_HEALTH_UNSPECIFIED_FAILURE; //6
    public static final int BATTERY_HEALTH_COLD = Constants.BATTERY_HEALTH_COLD; // 7

追踪到 frameworks/native/services/batteryservice/include/batteryservice/BatteryServiceConstants.h

复制代码
enum {
    BATTERY_STATUS_UNKNOWN = 1,
    BATTERY_STATUS_CHARGING = 2,
    BATTERY_STATUS_DISCHARGING = 3,
    BATTERY_STATUS_NOT_CHARGING = 4,
    BATTERY_STATUS_FULL = 5,
};

enum {
    BATTERY_HEALTH_UNKNOWN = 1,
    BATTERY_HEALTH_GOOD = 2,
    BATTERY_HEALTH_OVERHEAT = 3,
    BATTERY_HEALTH_DEAD = 4,
    BATTERY_HEALTH_OVER_VOLTAGE = 5,
    BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6,
    BATTERY_HEALTH_COLD = 7,
};

电池状态 & 充电类型

电池状态

判断是否在充电,

复制代码
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 0);

看 BatteryManager 源码,

复制代码
	// values for "status" field in the ACTION_BATTERY_CHANGED Intent
    public static final int BATTERY_STATUS_UNKNOWN = Constants.BATTERY_STATUS_UNKNOWN;//1
    public static final int BATTERY_STATUS_CHARGING = Constants.BATTERY_STATUS_CHARGING;//2
    public static final int BATTERY_STATUS_DISCHARGING = Constants.BATTERY_STATUS_DISCHARGING;//3
    public static final int BATTERY_STATUS_NOT_CHARGING = Constants.BATTERY_STATUS_NOT_CHARGING;//4
    public static final int BATTERY_STATUS_FULL = Constants.BATTERY_STATUS_FULL;//5

充电类型

判断充电类型, 交流电、USB充电、无线充电。

复制代码
int pluggen = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);

模拟器测试,None = 0 ,AC charger = 1 。

看 BatteryManager 源码,

复制代码
	// values of the "plugged" field in the ACTION_BATTERY_CHANGED intent.
    // These must be powers of 2.
    /** Power source is an AC charger. */
    public static final int BATTERY_PLUGGED_AC = OsProtoEnums.BATTERY_PLUGGED_AC; // = 1
    /** Power source is a USB port. */
    public static final int BATTERY_PLUGGED_USB = OsProtoEnums.BATTERY_PLUGGED_USB; // = 2
    /** Power source is wireless. */
    public static final int BATTERY_PLUGGED_WIRELESS = OsProtoEnums.BATTERY_PLUGGED_WIRELESS; // = 4

监听低电量

当电池点亮过低、电池电量充足会发出这两个广播。

按照官方建议,在电量低时应停用所有后台更新,此时应用应尽量避免进行后台操作。

复制代码
	/**
     * Broadcast Action:  Indicates low battery condition on the device.
     * This broadcast corresponds to the "Low battery warning" system dialog.
     *
     * <p class="note">This is a protected intent that can only be sent
     * by the system.
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
    /**
     * Broadcast Action:  Indicates the battery is now okay after being low.
     * This will be sent after {@link #ACTION_BATTERY_LOW} once the battery has
     * gone back up to an okay state.
     *
     * <p class="note">This is a protected intent that can only be sent
     * by the system.
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_BATTERY_OKAY = "android.intent.action.BATTERY_OKAY";

按照官网静态广播的写法,没有接收到 ,

复制代码
		<receiver
            android:name=".battery.BatteryReceiver"
            android:enabled="true"
            android:exported="true">

            <intent-filter>
                <action android:name="android.intent.action.BATTERY_LOW"/>
                <action android:name="android.intent.action.BATTERY_OKAY"/>
            </intent-filter>
        </receiver>

用动态广播的写法可以接收到,

复制代码
receiver = new BatteryReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(Intent.ACTION_BATTERY_LOW);
filter.addAction(Intent.ACTION_BATTERY_OKAY);
registerReceiver(receiver, filter);

用模拟器测试 ,

电量 15% 触发 Intent.ACTION_BATTERY_LOW ,电量 20% 触发 Intent.ACTION_BATTERY_OKAY

BatteryService源码分析

基于安卓11。

源码在 frameworks/base/services/core/java/com/android/server/BatteryService.java

对应的阈值都是定义在 frameworks$/base/core/res/res/values/config.xml , 厂商如需定制就改这里。

初始化

BatteryService 继承 SystemService ,

构造函数中

  • 初始化 Handler ;
  • 通过 LightsManager 实例化 Led ;
  • 获取各个阈值;
  • 监测 invalid_charger 。

onStart() 中

  • 注册电池健康回调,收到回调消息会调用 update(android.hardware.health.V2_1.HealthInfo info) 方法更新电池信息;
  • 初始化 BatteryPropertiesRegistrar 、BinderService 、LocalService。

onBootPhase(int phase) 中

  • 监听 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL 的变化,有变化时调用 updateBatteryWarningLevelLocked() 方法更新一次电池信息。

  • 调用 updateBatteryWarningLevelLocked() 方法更新一次电池信息。

    public final class BatteryService extends SystemService {

    复制代码
      //...
      public BatteryService(Context context) {
          super(context);
    
          mContext = context;
          mHandler = new Handler(true /*async*/);
          mLed = new Led(context, getLocalService(LightsManager.class));
          mBatteryStats = BatteryStatsService.getService();
          mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
    
          mCriticalBatteryLevel = mContext.getResources().getInteger(
                  com.android.internal.R.integer.config_criticalBatteryWarningLevel);
          mLowBatteryWarningLevel = mContext.getResources().getInteger(
                  com.android.internal.R.integer.config_lowBatteryWarningLevel);
          mLowBatteryCloseWarningLevel = mLowBatteryWarningLevel + mContext.getResources().getInteger(
                  com.android.internal.R.integer.config_lowBatteryCloseWarningBump);
          mShutdownBatteryTemperature = mContext.getResources().getInteger(
                  com.android.internal.R.integer.config_shutdownBatteryTemperature);
    
          mBatteryLevelsEventQueue = new ArrayDeque<>();
          mMetricsLogger = new MetricsLogger();
    
          // watch for invalid charger messages if the invalid_charger switch exists
          if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
              UEventObserver invalidChargerObserver = new UEventObserver() {
                  @Override
                  public void onUEvent(UEvent event) {
                      final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
                      synchronized (mLock) {
                          if (mInvalidCharger != invalidCharger) {
                              mInvalidCharger = invalidCharger;
                          }
                      }
                  }
              };
              invalidChargerObserver.startObserving(
                      "DEVPATH=/devices/virtual/switch/invalid_charger");
          }
      }
      //...
      @Override
      public void onStart() {
          registerHealthCallback();
    
          mBinderService = new BinderService();
          publishBinderService("battery", mBinderService);
          mBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();
          publishBinderService("batteryproperties", mBatteryPropertiesRegistrar);
          publishLocalService(BatteryManagerInternal.class, new LocalService());
      }
    
      @Override
      public void onBootPhase(int phase) {
          if (phase == PHASE_ACTIVITY_MANAGER_READY) {
              // check our power situation now that it is safe to display the shutdown dialog.
              synchronized (mLock) {
                  ContentObserver obs = new ContentObserver(mHandler) {
                      @Override
                      public void onChange(boolean selfChange) {
                          synchronized (mLock) {
                              updateBatteryWarningLevelLocked();
                          }
                      }
                  };
                  final ContentResolver resolver = mContext.getContentResolver();
                  resolver.registerContentObserver(Settings.Global.getUriFor(
                          Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
                          false, obs, UserHandle.USER_ALL);
                  updateBatteryWarningLevelLocked();
              }
          }
      }
      //...
      private void updateBatteryWarningLevelLocked() {
          final ContentResolver resolver = mContext.getContentResolver();
          int defWarnLevel = mContext.getResources().getInteger(
                  com.android.internal.R.integer.config_lowBatteryWarningLevel);
          mLowBatteryWarningLevel = Settings.Global.getInt(resolver,
                  Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel);
          if (mLowBatteryWarningLevel == 0) {
              mLowBatteryWarningLevel = defWarnLevel;
          }
          if (mLowBatteryWarningLevel < mCriticalBatteryLevel) {
              mLowBatteryWarningLevel = mCriticalBatteryLevel;
          }
          mLowBatteryCloseWarningLevel = mLowBatteryWarningLevel + mContext.getResources().getInteger(
                  com.android.internal.R.integer.config_lowBatteryCloseWarningBump);
          processValuesLocked(true);
      }
    
      private void processValuesLocked(boolean force) {
      	//...
      	shutdownIfNoPowerLocked();
          shutdownIfOverTempLocked();
          // ...
    
      	if (force || (mHealthInfo.batteryStatus != mLastBatteryStatus ||
                  mHealthInfo.batteryHealth != mLastBatteryHealth ||
                  mHealthInfo.batteryPresent != mLastBatteryPresent ||
                  mHealthInfo.batteryLevel != mLastBatteryLevel ||
                  mPlugType != mLastPlugType ||
                  mHealthInfo.batteryVoltage != mLastBatteryVoltage ||
                  mHealthInfo.batteryTemperature != mLastBatteryTemperature ||
                  mHealthInfo.maxChargingCurrent != mLastMaxChargingCurrent ||
                  mHealthInfo.maxChargingVoltage != mLastMaxChargingVoltage ||
                  mHealthInfo.batteryChargeCounter != mLastChargeCounter ||
                  mInvalidCharger != mLastInvalidCharger)) {
      			
      			// ...
      			mLastBatteryStatus = mHealthInfo.batteryStatus;
              	mLastBatteryHealth = mHealthInfo.batteryHealth;
             		mLastBatteryPresent = mHealthInfo.batteryPresent;
              	mLastBatteryLevel = mHealthInfo.batteryLevel;
              	mLastPlugType = mPlugType;
              	mLastBatteryVoltage = mHealthInfo.batteryVoltage;
              	mLastBatteryTemperature = mHealthInfo.batteryTemperature;
              	mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrent;
              	mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltage;
              	mLastChargeCounter = mHealthInfo.batteryChargeCounter;
              	mLastBatteryLevelCritical = mBatteryLevelCritical;
              	mLastInvalidCharger = mInvalidCharger;
      	}
    
      }

    }

低电量广播

电量 15% 触发 Intent.ACTION_BATTERY_LOW ,电量 20% 触发 Intent.ACTION_BATTERY_OKAY

复制代码
<integer name="config_lowBatteryWarningLevel">15</integer>
<integer name="config_lowBatteryCloseWarningBump">5</integer>

逻辑很清楚,

当电量低于 mLowBatteryWarningLevel 就发出广播 Intent.ACTION_BATTERY_LOW

当电量高于 mLowBatteryCloseWarningLevel 就发出广播 Intent.ACTION_BATTERY_OKAY

复制代码
	private int mLowBatteryWarningLevel;
	private int mLowBatteryCloseWarningLevel;
	
	// ...

	public BatteryService(Context context) {
		// ...
		mLowBatteryWarningLevel = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_lowBatteryWarningLevel);
    	mLowBatteryCloseWarningLevel = mLowBatteryWarningLevel + mContext.getResources().getInteger(
                com.android.internal.R.integer.config_lowBatteryCloseWarningBump);
		// ...
	}
	

	// ...
		if (shouldSendBatteryLowLocked()) {
                mSentLowBatteryBroadcast = true;
                final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_LOW);
                statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
                    }
                });
            } else if (mSentLowBatteryBroadcast &&
                    mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
                mSentLowBatteryBroadcast = false;
                final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
                statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
                    }
                });
            }

	// ...
	private boolean shouldSendBatteryLowLocked() {
        final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
        final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;

        /* The ACTION_BATTERY_LOW broadcast is sent in these situations:
         * - is just un-plugged (previously was plugged) and battery level is
         *   less than or equal to WARNING, or
         * - is not plugged and battery level falls to WARNING boundary
         *   (becomes <= mLowBatteryWarningLevel).
         */
        return !plugged
                && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
                && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
                && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
    }

超低电量关机

BatteryService.java 里,

复制代码
	//...
	shutdownIfNoPowerLocked();
	
	// ...
	private boolean shouldShutdownLocked() {
        if (mHealthInfo2p1.batteryCapacityLevel != BatteryCapacityLevel.UNSUPPORTED) {
            return (mHealthInfo2p1.batteryCapacityLevel == BatteryCapacityLevel.CRITICAL);
        }
        if (mHealthInfo.batteryLevel > 0) {
            return false;
        }

        // Battery-less devices should not shutdown.
        if (!mHealthInfo.batteryPresent) {
            return false;
        }

        // If battery state is not CHARGING, shutdown.
        // - If battery present and state == unknown, this is an unexpected error state.
        // - If level <= 0 and state == full, this is also an unexpected state
        // - All other states (NOT_CHARGING, DISCHARGING) means it is not charging.
        return mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_CHARGING;
    }

	private void shutdownIfNoPowerLocked() {
        // shut down gracefully if our battery is critically low and we are not powered.
        // wait until the system has booted before attempting to display the shutdown dialog.
        if (shouldShutdownLocked()) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (mActivityManagerInternal.isSystemReady()) {
                        Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                        intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
                        intent.putExtra(Intent.EXTRA_REASON,
                                PowerManager.SHUTDOWN_LOW_BATTERY);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                    }
                }
            });
        }
    }

电量过低时发出 Intent.ACTION_REQUEST_SHUTDOWN 广播,

frameworks/base/core/java/com/android/internal/app/ShutdownActivity.java 处理广播

frameworks/base/core/res/AndroidManifest.xml

复制代码
	<activity android:name="com.android.internal.app.ShutdownActivity"
            android:permission="android.permission.SHUTDOWN"
            android:theme="@style/Theme.NoDisplay"
            android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="com.android.internal.intent.action.REQUEST_SHUTDOWN" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.REBOOT" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

frameworks/base/core/java/com/android/internal/app/ShutdownActivity.java

复制代码
public class ShutdownActivity extends Activity {

    private static final String TAG = "ShutdownActivity";
    private boolean mReboot;
    private boolean mConfirm;
    private boolean mUserRequested;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = getIntent();
        mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());
        mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
        mUserRequested = intent.getBooleanExtra(Intent.EXTRA_USER_REQUESTED_SHUTDOWN, false);
        final String reason = mUserRequested
                ? PowerManager.SHUTDOWN_USER_REQUESTED
                : intent.getStringExtra(Intent.EXTRA_REASON);
        Slog.i(TAG, "onCreate(): confirm=" + mConfirm);

        Thread thr = new Thread("ShutdownActivity") {
            @Override
            public void run() {
                IPowerManager pm = IPowerManager.Stub.asInterface(
                        ServiceManager.getService(Context.POWER_SERVICE));
                try {
                    if (mReboot) {
                        pm.reboot(mConfirm, null, false);
                    } else {
                        pm.shutdown(mConfirm, reason, false);
                    }
                } catch (RemoteException e) {
                }
            }
        };
        thr.start();
        finish();
        // Wait for us to tell the power manager to shutdown.
        try {
            thr.join();
        } catch (InterruptedException e) {
        }
    }
}

高温关机

复制代码
<integer name="config_shutdownBatteryTemperature">680</integer>

超出这个定义的温度就关机,前文模拟器中获取的温度是 250 ,还是很安全的。

复制代码
		private int mShutdownBatteryTemperature;
		
		//...
		public BatteryService(Context context) {
			// ...
			mShutdownBatteryTemperature = mContext.getResources().getInteger(
                	com.android.internal.R.integer.config_shutdownBatteryTemperature);
			// ...
		}
		

		// ...
		shutdownIfOverTempLocked();
		// ...

		private void shutdownIfOverTempLocked() {
        // shut down gracefully if temperature is too high (> 68.0C by default)
        // wait until the system has booted before attempting to display the
        // shutdown dialog.
        if (mHealthInfo.batteryTemperature > mShutdownBatteryTemperature) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (mActivityManagerInternal.isSystemReady()) {
                        Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                        intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
                        intent.putExtra(Intent.EXTRA_REASON,
                                PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                    }
                }
            });
        }
    }
相关推荐
幻雨様1 小时前
UE5多人MOBA+GAS 45、制作冲刺技能
android·ue5
Jerry说前后端2 小时前
Android 数据可视化开发:从技术选型到性能优化
android·信息可视化·性能优化
Meteors.3 小时前
Android约束布局(ConstraintLayout)常用属性
android
alexhilton4 小时前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
whysqwhw8 小时前
安卓图片性能优化技巧
android
风往哪边走8 小时前
自定义底部筛选弹框
android
Yyyy4829 小时前
MyCAT基础概念
android
Android轮子哥9 小时前
尝试解决 Android 适配最后一公里
android
雨白10 小时前
OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度
android
风往哪边走11 小时前
自定义仿日历组件弹框
android