当前位置: 移动技术网 > IT编程>移动开发>Android > Android8.1 SystemUI源码分析之 电池时钟刷新

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

2019年06月02日  | 移动技术网IT编程  | 我要评论

穿越网王之金色梦幻,浪漫满车王莎莎,新开热血江湖sf

systemui源码分析相关文章

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

分析之前再贴一下 statusbar 相关类图

电池图标刷新

从上篇的分析得到电池图标对应的布局为 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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网