当需要监控系统电量时,用 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.
*
* 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.
*
*
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 = 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
。
基于安卓11。
源码在 frameworks/base/services/core/java/com/android/server/BatteryService.java
,
对应的阈值都是定义在 frameworks$/base/core/res/res/values/config.xml
, 厂商如需定制就改这里。
BatteryService 继承 SystemService ,
构造函数中
onStart() 中
update(android.hardware.health.V2_1.HealthInfo info)
方法更新电池信息;onBootPhase(int phase) 中
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
。
15
5
逻辑很清楚,
当电量低于 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
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) {
}
}
}
680
超出这个定义的温度就关机,前文模拟器中获取的温度是 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);
}
}
});
}
}