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);
                    }
                }
            });
        }
    }
相关推荐
H1006 分钟前
重构(二)
android·重构
拓端研究室17 分钟前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
zhangphil1 小时前
Android简洁缩放Matrix实现图像马赛克,Kotlin
android·kotlin
m0_512744641 小时前
极客大挑战2024-web-wp(详细)
android·前端
lw向北.1 小时前
Qt For Android之环境搭建(Qt 5.12.11 Qt下载SDK的处理方案)
android·开发语言·qt
不爱学习的啊Biao2 小时前
【13】MySQL如何选择合适的索引?
android·数据库·mysql
Clockwiseee2 小时前
PHP伪协议总结
android·开发语言·php
mmsx9 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
众拾达人12 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌12 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习