Android系统应用---SystemUI之三:状态栏电池图标的显示和Android电池管理的探讨

本文详细探讨了Android系统UI中电池图标的显示原理,包括 BatteryMeterView 和 BatteryLevelTextView 的作用,以及如何通过自定义 ImageView 实现电池图标显示。同时,文章也介绍了电池信息管理,讲解了Android电池状态的监测、更新界面的方法,以及底层电池驱动通过sysfs接口向用户空间提供电池状态的机制,并提到了通过监听UEvent和AIDL接口获取电池变化的相关知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


电池图标显示

电池图标是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();

        BatteryStatuteBroadcastReceiverreceivermReceiver;

        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

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值