Android8.1 SystemUI源码分析之 电池时钟刷新
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),构造方法主要做了如下几个操作
- 初始化电池等级icon,对应的drawable为 batterymeterdrawablebase,packages\apps\settingslib\src\com\android\settingslib\graph\batterymeterdrawablebase.java 将电池等级添加到父布局中
- 设置 settings.system.show_battery_percent 监听,当用户点击了显示电量百分比开关,则调用 updateshowpercent()方法在电池等级前添加电量百分比
- 通过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。
参考文章
上一篇: python异步存储数据详解
下一篇: 测试你的心灵 看看你能发现什么