SystemUI源码分析相关文章
Android8.1 MTK平台 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。
参考文章
https://blog.csdn.net/weilaideta/article/details/51760434