我是靠谱客的博主 阳光故事,这篇文章主要介绍Android8.1 SystemUI源码分析之 电池时钟刷新电池图标刷新时钟图标刷新,现在分享给大家,希望可以做个参考。

SystemUI源码分析相关文章

Android8.1 SystemUI源码分析之 Notification流程

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

电池图标刷新

从上篇的分析得到电池图标对应的布局为 SystemUIsrccomandroidsystemuiBatteryMeterView.java

先从构造方法入手

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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,packagesappsSettingsLibsrccomandroidsettingslibgraphBatteryMeterDrawableBase.java 将电池等级添加到父布局中
  2. 设置 Settings.System.SHOW_BATTERY_PERCENT 监听,当用户点击了显示电量百分比开关,则调用 updateShowPercent()方法在电池等级前添加电量百分比
  3. 通过onDarkChanged()设置默认的电池布局的主题色,当状态栏主题发生改变时,电池布局会做相应的更换(亮色和暗色切换)

在 PhoneStatusBarView 中添加了DarkReceiver监听,最终调用到 BatteryMeterView 的onDarkChanged()方法

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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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()

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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

SystemUIsrccomandroidsystemuistatusbarpolicyBatteryControllerImpl.java

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@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使用。

frameworksbaseservicescorejavacomandroidserverBatteryService.java

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

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

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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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种广播 修改时间显示

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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 参数 ,下面是核心代码

vendormediatekproprietarypackagesappsMtkSettingssrccomandroidsettingsdatetimeTimeFormatPreferenceController.java

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 并没有被用上,继续深究代码

复制代码
1
2
3
4
5
6
7
final void updateClock() { if (mDemoMode) return; mCalendar.setTimeInMillis(System.currentTimeMillis()); setText(getSmallTime()); setContentDescription(mContentDescriptionFormat.format(mCalendar.getTime())); }

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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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/weilaideta/article/details/51760434

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

最后

以上就是阳光故事最近收集整理的关于Android8.1 SystemUI源码分析之 电池时钟刷新电池图标刷新时钟图标刷新的全部内容,更多相关Android8.1内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(63)

评论列表共有 0 条评论

立即
投稿
返回
顶部