电池图标显示
电池图标是SystemUI显示中不可缺少的一部分,它显示在SystemUI的电池和信号组合区域。
从布局来看,电池的显示属于status_bar.xml,包含了system_icons.xml布局
<includelayout="@layout/system_icons"/>
从这里可以看出,电池图标和信号图标属于同一个区域,所以这一块一般是两个布局,单卡和双卡,我们可以根据对应的机型选择msim_system_icons.xml或者system_icons.xml
电池图标的部分:
<com.android.systemui.BatteryMeterViewandroid:id="@+id/battery"
android:layout_height="14.5dp"
android:layout_width="9.5dp"
android:layout_marginBottom="@dimen/battery_margin_bottom"
android:visibility="gone"/>
电池电量百分比的部分:
<com.android.systemui.BatteryLevelTextViewandroid:id="@+id/battery_level_text"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:gravity="center"
android:layout_gravity="center_vertical"
android:textColor="#ffffff"
android:textSize="@dimen/battery_level_text_size"
android:layout_marginStart="7dp"/>
电池图标具体的实现显示和更新分别在两个自定义View中实现:BatteryMeterView和BatteryLevelTextView
在原生的实现中: BatteryMeterView这个类来监听电池状态的变化的,是一个被BatteryController类所管理的ImageView。 BatteryController通过监听android.intent.action.BATTERY_CHANGED广播以从 BetteryService中获取电量信息,并根据电量信息画一个电池图标在画布上。原生的方法不需要去替换图片。
但有时候我们有需求去用图片来显示更新电池图标,最常见的比如有一套五格信号图标和充电图标,替换android原生的画出来的电池图标,我们可以将BatteryMeterView设置未GONE,写一个自定义的ImageView如下:
<com.android.systemui.BatteryIconViewandroid:id="@+id/battery_icons"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginBottom="@dimen/battery_margin_bottom"
android:scaleType="fitCenter"/>
自定义View,我们需要一个广播接收者来监听Intent.ACTION_BATTERY_CHANGED,用来获取电池的状态
private classBatteryStatuteBroadcastReceiver extendsBroadcastReceiver {
public static final int UNKNOWN_LEVEL = -1;
int level = UNKNOWN_LEVEL;
int plugType;
booleanplugged;
int health;
int status;
String technology;
int voltage;
int temperature;
@Override
public void onReceive(Context context, Intentintent) {
final String action = intent.getAction();
if (action.equals(Intent.ACTION_BATTERY_CHANGED)){
level = (int)(100f
* intent.getIntExtra(BatteryManager.EXTRA_LEVEL,0)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE,100));
plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED,0);
plugged = plugType != 0;
health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,
BatteryManager.BATTERY_HEALTH_UNKNOWN);
status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
BatteryManager.BATTERY_STATUS_UNKNOWN);
technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE,0);
temperature =intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
updateIcons();
postInvalidate();
}
}
}
这个Receiver只能动态进行注册,在窗口加载时注册,在窗口销毁时解注册
@Override
public voidonAttachedToWindow() {
super.onAttachedToWindow();
IntentFilterfilter = newIntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
finalIntent sticky =getContext().registerReceiver(mReceiver,filter);
if (sticky !=null) {
mReceiver.onReceive(getContext(),sticky);
}
mBatteryController.addStateChangedCallback(this);
}
@Override
public voidonDetachedFromWindow() {
super.onDetachedFromWindow();
getContext().unregisterReceiver(mReceiver);
mBatteryController.removeStateChangedCallback(this);
}
我们在接收到电池变化的广播时,进行updateIcon,选择需要的图标组,这里我们可以用if else语句,也可以用level-list进行选择,使用if else语句繁琐且效率底下,比如五格电池显示,在不同的电量区间显示不同的电池图标。一个比较好的方式是使用level-list
步骤
1. 自定义一个ImageView,在ondraw中实现图片资源的替换
2. 在构造函数中对ImageView进行设置资源
setImageResource(R.drawable.battery_level_list);
3. battery_level_list是一个level-list,定义如下
<?xmlversion="1.0"encoding="utf-8"?>
<level-listxmlns:android="http://schemas.android.com/apk/res/android">
<itemandroid:maxLevel="15"android:drawable="@drawable/stat_sys_battery_1"/>
<itemandroid:maxLevel="49"android:drawable="@drawable/stat_sys_battery_2"/>
<itemandroid:maxLevel="75"android:drawable="@drawable/stat_sys_battery_3"/>
<itemandroid:maxLevel="95"android:drawable="@drawable/stat_sys_battery_4"/>
<itemandroid:maxLevel="100"android:drawable="@drawable/stat_sys_battery_5"/>
</level-list>
4. 其中maxLevel表示最大值,也就是说0-15用的是第一个资源,一个level是50的话,就会从第一个开始匹配,匹配到第三个的时候满足条件,所以就显示三格信号,还有minLevel表示最小值,二者结合可以确定一个level范围
5. 以电池图标显示为例,收到电池状态改变的广播后,获取电池电量百分比,设置相应的level
6. 在ondraw中,获取图片资源,在画布中显示即可
Drawable drawable = getDrawable();
BatteryStatuteBroadcastReceiverreceiver = mReceiver;
final int level = receiver.level;
drawable.setLevel(level);
Bitmapb = ((BitmapDrawable) drawable).getBitmap();
Bitmapbitmap = b.copy(Bitmap.Config.ARGB_8888,true);
canvas.drawBitmap(bitmap, 0, 0, null);
}
public voidupdateIcons() {
if (mTracker.status ==BatteryManager.BATTERY_STATUS_CHARGING&&mTracker.level != 100) {
setImageResource(R.drawable.battery_chargeing_level_list);
} else {
setImageResource(R.drawable.battery_level_list);
}
}
电池信息管理
我们的操作只是浮于表面,根据收到的广播,根据广播中携带的几个状态进行简单的判断显示,底层电池的状态如何改变,有什么测试方法,如何发送的Framework状态,Framework层又是如何打包发送电池状态改变的广播,需要我们再进行探索
Android电池管理的在上层功能其实很简单:无非是1.监测电池电量的变化并更新显示界面,2.监听进入充电状态和退出充电状态消息并更新界面
android系统电池部分的驱动程序,继承了传统linux系统下的Power Supply驱动程序架构,Battery驱动程序通过Power Supply驱动程序生成相应的sys文件系统,从而向用户空间提供电池各种属性的接口,然后遍历整个文件夹,查找各个能源供应设备的各种属性
Android的Linux 内核中的电池驱动会提供如下sysfs接口给framework:
/sys/class/power_supply/ac/onlineAC 电源连接状态
/sys/class/power_supply/usb/onlineUSB电源连接状态
/sys/class/power_supply/battery/status充电状态
/sys/class/power_supply/battery/health电池状态
/sys/class/power_supply/battery/present使用状态
/sys/class/power_supply/battery/capacity电池 level
/sys/class/power_supply/battery/batt_vol电池电压
/sys/class/power_supply/battery/batt_temp电池温度
/sys/class/power_supply/battery/technology电池技术
当供电设备的状态发生变化时,driver会更新这些文件,然后通过jni中的本地方法android_server_BatteryService_update向java层发送信息。当监听到power_supply变化的消息后, nativeUpdate函数就会重新读取以上sysfs文件获得当前状态。
而在用户层则是在BatteryService.java中通过广播的方式将如下一些电池相关的属性上报给上层app使用。
frameworks/base/services/java/com/android/server/BatteryService.java
BatteryService 在SystemServer.java 中创建,BatteryService是在系统启动的时候就跑起来的,为电池及充电相关的服务,主要作了如下几件事情: 监听 UEvent、读取sysfs 中的状态 、发出广播Intent.ACTION_BATTERY_CHANGED通知上层
底层上报电池变化的方式一:UEventObserver
在BatteryService定义了UEventObserver, uevent是Linux内核用来向用户空间主动上报事件的机制
private final UEventObservermInvalidChargerObserver =newUEventObserver() {
@Override
public voidonUEvent(UEventObserver.UEvent event) {
final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
synchronized (mLock) {
if (mInvalidCharger != invalidCharger) {
mInvalidCharger =invalidCharger;
}
}
}
};
}
监听invalid_charger这个属性,当有状态变化时就去用onUEvent去处理
if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
mInvalidChargerObserver.startObserving(
"DEVPATH=/devices/virtual/switch/invalid_charger");
}
UEvent的实现分为三层,API+JNI+HAL暂且不细表
我们可以看到上面的实现方式只监听了一种状态,其他的从JNI_OnLoad()得来
底层上报电池变化的方式二:通过AIDL,battery配置改变的时候,调用update
private final class BatteryListener extends IBatteryPropertiesListener.Stub {
@Override
public voidbatteryPropertiesChanged(BatteryProperties props) {
final long identity= Binder.clearCallingIdentity();
try {
BatteryService.this.update(props);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
调用到processValuesLocked(),然后发送广播给关关系它的应用sendIntentLocked(),通过广播Intent.ACTION_BATTERY_CHANGED,将电池状态、电池电量、电池工艺等属性打包,发送给其它的使用者,也就是说,只要 在app程序里监听了Intent.ACTION_BATTERY_CHANGED这个广播,就能获取到电池的各种状态属性
应用如果想要接收到BatteryService发送出来的电池信息,则需要注册一个Intent为
Intent.ACTION_BATTERY_CHANGED的BroadcastReceiver,电池相关的属性就可以直接读到
附:电池相关的测试知识
◆驱动路径
/sys/class/power_supply/battery/status
◆读取电池的状态
cat /sys/class/power_supply/battery/uevent
◆模拟环境,取出电池配置文件
adb pull /system/etc/thermal-engine-8909.conf
[BATT_ID_MONITOR]
algo_type monitor
sensor batt_id
sampling 1000
thresholds 14000
thresholds_clr 12000
actions battery
action_info 3
adb remount
adb push thermal-engine-8909.conf /system/etc/.
adb reboot
◆得到当前电池的温度,得到有背光和没有背光的温度区间
adb shell cat /sys/bus/spmi/devices/qpnp-vadc-*/batt_id