欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android8.1 SystemUI源码分析之 电池时钟刷新

程序员文章站 2023-04-06 18:50:19
SystemUI源码分析相关文章 "Android8.1 SystemUI源码分析之 Notification流程" 分析之前再贴一下 StatusBar 相关类图 电池图标刷新 从上篇的分析得到电池图标对应的布局为 SystemUI\src\com\android\systemui\Battery ......

systemui源码分析相关文章

android8.1 systemui源码分析之 notification流程

分析之前再贴一下 statusbar 相关类图
Android8.1 SystemUI源码分析之 电池时钟刷新

电池图标刷新

从上篇的分析得到电池图标对应的布局为 systemui\src\com\android\systemui\batterymeterview.java

先从构造方法入手

public batterymeterview(context context, attributeset attrs, int defstyle) {
    super(context, attrs, defstyle);

    setorientation(linearlayout.horizontal);
    setgravity(gravity.center_vertical | gravity.start);

    typedarray atts = context.obtainstyledattributes(attrs, r.styleable.batterymeterview,
            defstyle, 0);
    final int framecolor = atts.getcolor(r.styleable.batterymeterview_framecolor,
            context.getcolor(r.color.meter_background_color));
    mdrawable = new batterymeterdrawablebase(context, framecolor);
    atts.recycle();

    msettingobserver = new settingobserver(new handler(context.getmainlooper()));

    mslotbattery = context.getstring(
            com.android.internal.r.string.status_bar_battery);
    mbatteryiconview = new imageview(context);
    mbatteryiconview.setimagedrawable(mdrawable);
    final marginlayoutparams mlp = new marginlayoutparams(
            getresources().getdimensionpixelsize(r.dimen.status_bar_battery_icon_width),
            getresources().getdimensionpixelsize(r.dimen.status_bar_battery_icon_height));
    mlp.setmargins(0, 0, 0,
            getresources().getdimensionpixeloffset(r.dimen.battery_margin_bottom));
    addview(mbatteryiconview, mlp);

    updateshowpercent();

    context dualtonedarktheme = new contextthemewrapper(context,
            utils.getthemeattr(context, r.attr.darkicontheme));
    context dualtonelighttheme = new contextthemewrapper(context,
            utils.getthemeattr(context, r.attr.lighticontheme));
    mdarkmodebackgroundcolor = utils.getcolorattr(dualtonedarktheme, r.attr.backgroundcolor);
    mdarkmodefillcolor = utils.getcolorattr(dualtonedarktheme, r.attr.fillcolor);
    mlightmodebackgroundcolor = utils.getcolorattr(dualtonelighttheme, r.attr.backgroundcolor);
    mlightmodefillcolor = utils.getcolorattr(dualtonelighttheme, r.attr.fillcolor);

    // init to not dark at all.
    ondarkchanged(new rect(), 0, darkicondispatcher.default_icon_tint);
    musertracker = new currentusertracker(mcontext) {
        @override
        public void onuserswitched(int newuserid) {
            muser = newuserid;
            getcontext().getcontentresolver().unregistercontentobserver(msettingobserver);
            getcontext().getcontentresolver().registercontentobserver(
                    settings.system.geturifor(show_battery_percent), false, msettingobserver,
                    newuserid);
        }
    };
}

先说下 batterymeterview 继承自 linearlayout,从上面的构造方法可以看出,我们看到的电池图标是由两部分组成的,

电量百分比(textview)和电池等级(imageview),构造方法主要做了如下几个操作

  1. 初始化电池等级icon,对应的drawable为 batterymeterdrawablebase,packages\apps\settingslib\src\com\android\settingslib\graph\batterymeterdrawablebase.java 将电池等级添加到父布局中
  2. 设置 settings.system.show_battery_percent 监听,当用户点击了显示电量百分比开关,则调用 updateshowpercent()方法在电池等级前添加电量百分比
  3. 通过ondarkchanged()设置默认的电池布局的主题色,当状态栏主题发生改变时,电池布局会做相应的更换(亮色和暗色切换)

在 phonestatusbarview 中添加了darkreceiver监听,最终调用到 batterymeterview 的ondarkchanged()方法

修改百分比的字体颜色和电池等级的画笔颜色和背景颜色

////// phonestatusbarview
@override
protected void onattachedtowindow() {
    super.onattachedtowindow();
    // always have battery meters in the status bar observe the dark/light modes.
    dependency.get(darkicondispatcher.class).adddarkreceiver(mbattery);
}

@override
protected void ondetachedfromwindow() {
    super.ondetachedfromwindow();
    dependency.get(darkicondispatcher.class).removedarkreceiver(mbattery);
}

/////batterymeterview
public void ondarkchanged(rect area, float darkintensity, int tint) {
    mdarkintensity = darkintensity;
    float intensity = darkicondispatcher.isinarea(area, this) ? darkintensity : 0;
    int foreground = getcolorfordarkintensity(intensity, mlightmodefillcolor,
            mdarkmodefillcolor);
    int background = getcolorfordarkintensity(intensity, mlightmodebackgroundcolor,
            mdarkmodebackgroundcolor);
    mdrawable.setcolors(foreground, background);
    settextcolor(foreground);
}

batterymeterdrawablebase 是一个自定义 drawable,通过path来绘制电池图标,感兴趣的可自行研究

batterymeterview 中添加了电量改变监听,来看下 onbatterylevelchanged()

 @override
public void onbatterylevelchanged(int level, boolean pluggedin, boolean charging) {
    mdrawable.setbatterylevel(level);
    // m: in case battery protection, it stop charging, but still plugged, it will
    // also wrongly show the charging icon.
    mdrawable.setcharging(pluggedin && charging);
    mlevel = level;
    updatepercenttext();
    setcontentdescription(
            getcontext().getstring(charging ? r.string.accessibility_battery_level_charging
                    : r.string.accessibility_battery_level, level));
}

@override
public void onpowersavechanged(boolean ispowersave) {
    mdrawable.setpowersave(ispowersave);
}

setbatterylevel()根据当前 level/100f 计算百分比绘制path,setcharging()是否绘制充电中闪电形状图标

电池状态改变流程

我们都知道电池状态改变是通过广播的方式接受的(intent.action_battery_changed),搜索找到 batterycontrollerimpl

systemui\src\com\android\systemui\statusbar\policy\batterycontrollerimpl.java

 @override
public void onreceive(final context context, intent intent) {
    final string action = intent.getaction();
    if (action.equals(intent.action_battery_changed)) {
        if (mtestmode && !intent.getbooleanextra("testmode", false)) return;
        mhasreceivedbattery = true;
        mlevel = (int)(100f
                * intent.getintextra(batterymanager.extra_level, 0)
                / intent.getintextra(batterymanager.extra_scale, 100));
        mpluggedin = intent.getintextra(batterymanager.extra_plugged, 0) != 0;

        final int status = intent.getintextra(batterymanager.extra_status,
                batterymanager.battery_status_unknown);
        mcharged = status == batterymanager.battery_status_full;
        mcharging = mcharged || status == batterymanager.battery_status_charging;

        firebatterylevelchanged();
    }

    .......
}


 protected void firebatterylevelchanged() {
    synchronized (mchangecallbacks) {
        final int n = mchangecallbacks.size();
        for (int i = 0; i < n; i++) {
            mchangecallbacks.get(i).onbatterylevelchanged(mlevel, mpluggedin, mcharging);
        }
    }
}

收到广播后通过 firebatterylevelchanged() 遍历回调监听,将状态参数发送。 batterymeterview实现了 batterystatechangecallback,

收到改变监听 onbatterylevelchanged()

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\core\java\com\android\server\batteryservice.java

batteryservice 在systemserver.java 中创建,batteryservice 是在系统启动的时候就跑起来的,

为电池及充电相关的服务,主要作了如下几件事情: 监听 uevent、读取sysfs 中的状态 、发出广播 intent.action_battery_changed 通知上层

batteryservice 的 start()中注册 batterylistener,当battery配置改变的时候,调用 update()

private final class batterylistener extends ibatterypropertieslistener.stub {
    @override public void batterypropertieschanged(batteryproperties props) {
        final long identity = binder.clearcallingidentity();
        try {
            batteryservice.this.update(props);
        } finally {
            binder.restorecallingidentity(identity);
        }
   }
}

private void update(batteryproperties props) {
    synchronized (mlock) {
        if (!mupdatesstopped) {
            mbatteryprops = props;
            // process the new values.
            processvalueslocked(false);
        } else {
            mlastbatteryprops.set(props);
        }
    }
}


 private void processvalueslocked(boolean force) {
    boolean logoutlier = false;
    long dischargeduration = 0;
    ...

    sendintentlocked();
    .....
}

//发送 action_battery_changed 广播
private void sendintentlocked() {
    //  pack up the values and broadcast them to everyone
    final intent intent = new intent(intent.action_battery_changed);
    intent.addflags(intent.flag_receiver_registered_only
            | intent.flag_receiver_replace_pending);

    int icon = geticonlocked(mbatteryprops.batterylevel);

    intent.putextra(batterymanager.extra_sequence, msequence);
    intent.putextra(batterymanager.extra_status, mbatteryprops.batterystatus);
    intent.putextra(batterymanager.extra_health, mbatteryprops.batteryhealth);
    intent.putextra(batterymanager.extra_present, mbatteryprops.batterypresent);
    intent.putextra(batterymanager.extra_level, mbatteryprops.batterylevel);
    intent.putextra(batterymanager.extra_scale, battery_scale);
    intent.putextra(batterymanager.extra_icon_small, icon);
    intent.putextra(batterymanager.extra_plugged, mplugtype);
    intent.putextra(batterymanager.extra_voltage, mbatteryprops.batteryvoltage);
    intent.putextra(batterymanager.extra_temperature, mbatteryprops.batterytemperature);
    intent.putextra(batterymanager.extra_technology, mbatteryprops.batterytechnology);
    intent.putextra(batterymanager.extra_invalid_charger, minvalidcharger);
    intent.putextra(batterymanager.extra_max_charging_current, mbatteryprops.maxchargingcurrent);
    intent.putextra(batterymanager.extra_max_charging_voltage, mbatteryprops.maxchargingvoltage);
    intent.putextra(batterymanager.extra_charge_counter, mbatteryprops.batterychargecounter);

    mhandler.post(new runnable() {
        @override
        public void run() {
            activitymanager.broadcaststickyintent(intent, userhandle.user_all);
        }
    });
}

读取电池状态 cat /sys/class/power_supply/battery/uevent

时钟图标刷新

从 status_bar.xml 中看到时钟是一个自定义view, com.android.systemui.statusbar.policy.clock

查看 clock 源码知道继承自 textview,时间内容更新通过settext(),通过监听如下5种广播 修改时间显示

@override
protected void onattachedtowindow() {
    super.onattachedtowindow();

    if (!mattached) {
        mattached = true;
        intentfilter filter = new intentfilter();

        filter.addaction(intent.action_time_tick);
        filter.addaction(intent.action_time_changed);
        filter.addaction(intent.action_timezone_changed);
        filter.addaction(intent.action_configuration_changed);
        filter.addaction(intent.action_user_switched);

        getcontext().registerreceiverasuser(mintentreceiver, userhandle.all, filter,
                null, dependency.get(dependency.time_tick_handler));
        dependency.get(tunerservice.class).addtunable(this, clock_seconds,
                statusbariconcontroller.icon_blacklist);
        sysuiserviceprovider.getcomponent(getcontext(), commandqueue.class).addcallbacks(this);
        if (mshowdark) {
            dependency.get(darkicondispatcher.class).adddarkreceiver(this);
        }
    }

    // note: it's safe to do these after registering the receiver since the receiver always runs
    // in the main thread, therefore the receiver can't run before this method returns.

    // the time zone may have changed while the receiver wasn't registered, so update the time
    mcalendar = calendar.getinstance(timezone.getdefault());

    // make sure we update to the current time
    updateclock();
    updateshowseconds();
}

可以看到 mintentreceiver 监听了5种类型的action

intent.action_time_tick 时钟频率,1分钟一次

intent.action_time_changed 时钟改变,用户在设置中修改了设置时间选项

intent.action_timezone_changed 时区改变,用户在设置中修改了选择时区

intent.action_configuration_changed 系统配置改变,如修改系统语言、系统屏幕方向发生改变

intent.action_user_switched 切换用户,机主或其它访客之间切换

我们看到系统设置界面中的 使用24小时制 开关,点击后时间会立马改变显示,就是通过发送 action_time_changed 广播,

携带 extra_time_pref_24_hour_format 参数 ,下面是核心代码

vendor\mediatek\proprietary\packages\apps\mtksettings\src\com\android\settings\datetime\timeformatpreferencecontroller.java

private void set24hour(boolean is24hour) {
    settings.system.putstring(mcontext.getcontentresolver(),
            settings.system.time_12_24,
            is24hour ? hours_24 : hours_12);
}

private void timeupdated(boolean is24hour) {
    intent timechanged = new intent(intent.action_time_changed);
    int timeformatpreference =
            is24hour ? intent.extra_time_pref_value_use_24_hour
                    : intent.extra_time_pref_value_use_12_hour;
    timechanged.putextra(intent.extra_time_pref_24_hour_format, timeformatpreference);
    mcontext.sendbroadcast(timechanged);
}

回到 clock.java 中,发现 extra_time_pref_24_hour_format 并没有被用上,继续深究代码

 final void updateclock() {
    if (mdemomode) return;
    mcalendar.settimeinmillis(system.currenttimemillis());
    settext(getsmalltime());
    setcontentdescription(mcontentdescriptionformat.format(mcalendar.gettime()));
}

收到广播最终都会调用 updateclock(),可以看到真正设置时间是通过 getsmalltime() 这个核心方法

private final charsequence getsmalltime() {
    context context = getcontext();
    boolean is24 = dateformat.is24hourformat(context, activitymanager.getcurrentuser());
    localedata d = localedata.get(context.getresources().getconfiguration().locale);

    final char magic1 = '\uef00';
    final char magic2 = '\uef01';

    simpledateformat sdf;
    string format = mshowseconds
            ? is24 ? d.timeformat_hms : d.timeformat_hms
            : is24 ? d.timeformat_hm : d.timeformat_hm;
    if (!format.equals(mclockformatstring)) {
        mcontentdescriptionformat = new simpledateformat(format);
        /*
         * search for an unquoted "a" in the format string, so we can
         * add dummy characters around it to let us find it again after
         * formatting and change its size.
         */
        if (mampmstyle != am_pm_style_normal) {
            int a = -1;
            boolean quoted = false;
            for (int i = 0; i < format.length(); i++) {
                char c = format.charat(i);

                if (c == '\'') {
                    quoted = !quoted;
                }
                if (!quoted && c == 'a') {
                    a = i;
                    break;
                }
            }

            if (a >= 0) {
                // move a back so any whitespace before am/pm is also in the alternate size.
                final int b = a;
                while (a > 0 && character.iswhitespace(format.charat(a-1))) {
                    a--;
                }
                format = format.substring(0, a) + magic1 + format.substring(a, b)
                    + "a" + magic2 + format.substring(b + 1);
            }
        }
        mclockformat = sdf = new simpledateformat(format);
        mclockformatstring = format;
    } else {
        sdf = mclockformat;
    }
    string result = sdf.format(mcalendar.gettime());

    if (mampmstyle != am_pm_style_normal) {
        int magic1 = result.indexof(magic1);
        int magic2 = result.indexof(magic2);
        if (magic1 >= 0 && magic2 > magic1) {
            spannablestringbuilder formatted = new spannablestringbuilder(result);
            if (mampmstyle == am_pm_style_gone) {
                formatted.delete(magic1, magic2+1);
            } else {
                if (mampmstyle == am_pm_style_small) {
                    characterstyle style = new relativesizespan(0.7f);
                    formatted.setspan(style, magic1, magic2,
                                      spannable.span_exclusive_inclusive);
                }
                formatted.delete(magic2, magic2 + 1);
                formatted.delete(magic1, magic1 + 1);
            }
            return formatted;
        }
    }

    return result;

}

方法有点长,我们挑主要的分析一下,dateformat.is24hourformat() 最终通过读取 settings.system.time_12_24值,

这个值正好在上面的 timeformatpreferencecontroller 中点击24小时开关是改变,如果这个值为null,则通过获取本地时间

local 来获取当前时间格式,如果等于24则返回true,该方法的源码可在as中点进去查看,此处就不贴了。

localedata 是一个时间格式管理类,在 dateutils.java 和 simpledateformat.java 中都频繁使用

接下来获取到的 format 为 d.timeformat_hm, 设置给 simpledateformat(d.timeformat_hm)

string result = sdf.format(mcalendar.gettime());就是当前需要显示的时间,此处还需要做一下格式化

mampmstyle 是通过构造函数自定义属性赋值的,xml中并没有赋值,取默认值 am_pm_style_gone,走这段代码

formatted.delete(magic1, magic2+1); 去除多余的 '\uef00' 和 '\uef01',最终显示的就是 formatted。

参考文章

https://blog.csdn.net/w1107101310/article/details/80211885