2023年7月15日发(作者:)
Android8.1SystemUI源码分析之电池时钟刷新SystemUI源码分析相关⽂章分析之前再贴⼀下 StatusBar 相关类图电池图标刷新从上篇的分析得到电池图标对应的布局为 先从构造⽅法⼊⼿public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setOrientation(NTAL); setGravity(_VERTICAL | ); TypedArray atts = StyledAttributes(attrs, yMeterView, defStyle, 0); final int frameColor = or(yMeterView_frameColor, or(_background_color)); mDrawable = new BatteryMeterDrawableBase(context, frameColor); e(); mSettingObserver = new SettingObserver(new Handler(nLooper())); mSlotBattery = ing( _bar_battery); mBatteryIconView = new ImageView(context); geDrawable(mDrawable); final MarginLayoutParams mlp = new MarginLayoutParams( getResources().getDimensionPixelSize(_bar_battery_icon_width), getResources().getDimensionPixelSize(_bar_battery_icon_height)); gins(0, 0, 0, getResources().getDimensionPixelOffset(y_margin_bottom)); addView(mBatteryIconView, mlp); updateShowPercent(); Context dualToneDarkTheme = new ContextThemeWrapper(context, meAttr(context, onTheme)); Context dualToneLightTheme = new ContextThemeWrapper(context, meAttr(context, conTheme)); mDarkModeBackgroundColor = orAttr(dualToneDarkTheme, oundColor); mDarkModeFillColor = orAttr(dualToneDarkTheme, lor); mLightModeBackgroundColor = orAttr(dualToneLightTheme, oundColor); mLightModeFillColor = orAttr(dualToneLightTheme, lor); // Init to not dark at all. onDarkChanged(new Rect(), 0, T_ICON_TINT); mUserTracker = new CurrentUserTracker(mContext) { @Override public void onUserSwitched(int newUserId) { mUser = newUserId; getContext().getContentResolver().unregisterContentObserver(mSettingObserver); getContext().getContentResolver().registerContentObserver( For(SHOW_BATTERY_PERCENT), false, mSettingObserver, newUserId); } };}先说下 BatteryMeterView 继承⾃ LinearLayout,从上⾯的构造⽅法可以看出,我们看到的电池图标是由两部分组成的,电量百分⽐(TextView)和电池等级(ImageView),构造⽅法主要做了如下⼏个操作初始化电池等级icon,对应的drawable为 BatteryMeterDrawableBase,packagesappsS 将电池等级添加到⽗布局中设置 _BATTERY_PERCENT 监听,当⽤户点击了显⽰电量百分⽐开关,则调⽤ updateShowPercent()⽅法在电池等级前添加电量百分⽐通过onDarkChanged()设置默认的电池布局的主题⾊,当状态栏主题发⽣改变时,电池布局会做相应的更换(亮⾊和暗⾊切换)在 PhoneStatusBarView 中添加了DarkReceiver监听,最终调⽤到 BatteryMeterView 的onDarkChanged()⽅法修改百分⽐的字体颜⾊和电池等级的画笔颜⾊和背景颜⾊////// PhoneStatusBarView@Overrideprotected void onAttachedToWindow() { chedToWindow(); // Always have Battery meters in the status bar observe the dark/light modes. ().addDarkReceiver(mBattery);}@Overrideprotected void onDetachedFromWindow() { chedFromWindow(); ().removeDarkReceiver(mBattery);}/////BatteryMeterViewpublic void onDarkChanged(Rect area, float darkIntensity, int tint) { mDarkIntensity = darkIntensity; float intensity = ea(area, this) ? darkIntensity : 0; int foreground = getColorForDarkIntensity(intensity, mLightModeFillColor, mDarkModeFillColor); int background = getColorForDarkIntensity(intensity, mLightModeBackgroundColor, mDarkModeBackgroundColor); ors(foreground, background); setTextColor(foreground);}BatteryMeterDrawableBase 是⼀个⾃定义 Drawable,通过path来绘制电池图标,感兴趣的可⾃⾏研究BatteryMeterView 中添加了电量改变监听,来看下 onBatteryLevelChanged() @Overridepublic void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { teryLevel(level); // M: In case battery protection, it stop charging, but still plugged, it will // also wrongly show the charging icon. rging(pluggedIn && charging); mLevel = level; updatePercentText(); setContentDescription( getContext().getString(charging ? ibility_battery_level_charging : ibility_battery_level, level));}@Overridepublic void onPowerSaveChanged(boolean isPowerSave) { erSave(isPowerSave);}setBatteryLevel()根据当前 level/100f 计算百分⽐绘制path,setCharging()是否绘制充电中闪电形状图标电池状态改变流程我们都知道电池状态改变是通过⼴播的⽅式接受的(_BATTERY_CHANGED),搜索找到 BatteryControllerImplSy @Overridepublic void onReceive(final Context context, Intent intent) { final String action = ion(); if ((_BATTERY_CHANGED)) { if (mTestmode && !leanExtra("testmode", false)) return; mHasReceivedBattery = true; mLevel = (int)(100f * Extra(_LEVEL, 0) / Extra(_SCALE, 100)); mPluggedIn = Extra(_PLUGGED, 0) != 0; final int status = Extra(_STATUS, Y_STATUS_UNKNOWN); mCharged = status == Y_STATUS_FULL; mCharging = mCharged || status == Y_STATUS_CHARGING; fireBatteryLevelChanged(); } .......} protected void fireBatteryLevelChanged() { synchronized (mChangeCallbacks) { final int N = (); for (int i = 0; i < N; i++) { (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⽂件获得当前状态。⽽在⽤户层则是在 中通过⼴播的⽅式将电池相关的属性上报给上层app使⽤。tteryService 在 中创建,BatteryService 是在系统启动的时候就跑起来的,为电池及充电相关的服务,主要作了如下⼏件事情: 监听 UEvent、读取sysfs 中的状态 、发出⼴播 _BATTERY_CHANGED 通知上层BatteryService 的 start()中注册 BatteryListener,当battery配置改变的时候,调⽤ update()private final class BatteryListener extends { @Override public void batteryPropertiesChanged(BatteryProperties props) { final long identity = allingIdentity(); try { (props); } finally { eCallingIdentity(identity); } }}private void update(BatteryProperties props) { synchronized (mLock) { if (!mUpdatesStopped) { if (!mUpdatesStopped) { mBatteryProps = props; // Process the new values. processValuesLocked(false); } else { (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(_BATTERY_CHANGED); gs(_RECEIVER_REGISTERED_ONLY | _RECEIVER_REPLACE_PENDING); int icon = getIconLocked(yLevel); ra(_SEQUENCE, mSequence); ra(_STATUS, yStatus); ra(_HEALTH, yHealth); ra(_PRESENT, yPresent); ra(_LEVEL, yLevel); ra(_SCALE, BATTERY_SCALE); ra(_ICON_SMALL, icon); ra(_PLUGGED, mPlugType); ra(_VOLTAGE, yVoltage); ra(_TEMPERATURE, yTemperature); ra(_TECHNOLOGY, yTechnology); ra(_INVALID_CHARGER, mInvalidCharger); ra(_MAX_CHARGING_CURRENT, rgingCurrent); ra(_MAX_CHARGING_VOLTAGE, rgingVoltage); ra(_CHARGE_COUNTER, yChargeCounter); (new Runnable() { @Override public void run() { astStickyIntent(intent, _ALL); } });}读取电池状态 cat /sys/class/power_supply/battery/uevent时钟图标刷新从 status_ 中看到时钟是⼀个⾃定义view, 查看 Clock 源码知道继承⾃ TextView,时间内容更新通过setText(),通过监听如下5种⼴播 修改时间显⽰@Overrideprotected void onAttachedToWindow() { chedToWindow(); if (!mAttached) { mAttached = true; IntentFilter filter = new IntentFilter(); ion(_TIME_TICK); ion(_TIME_CHANGED); ion(_TIMEZONE_CHANGED); ion(_CONFIGURATION_CHANGED); ion(_USER_SWITCHED); getContext().registerReceiverAsUser(mIntentReceiver, , filter, null, (_TICK_HANDLER)); ().addTunable(this, CLOCK_SECONDS, _BLACKLIST); ponent(getContext(), ).addCallbacks(this); if (mShowDark) { ().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 = tance(ault()); // Make sure we update to the current time updateClock(); updateShowSeconds();}}可以看到 mIntentReceiver 监听了5种类型的_TIME_TICK 时钟频率,1分钟⼀次_TIME_CHANGED 时钟改变,⽤户在设置中修改了设置时间选项_TIMEZONE_CHANGED 时区改变,⽤户在设置中修改了选择时区_CONFIGURATION_CHANGED 系统配置改变,如修改系统语⾔、系统屏幕⽅向发⽣改变_USER_SWITCHED 切换⽤户,机主或其它访客之间切换我们看到系统设置界⾯中的 使⽤24⼩时制 开关,点击后时间会⽴马改变显⽰,就是通过发送 ACTION_TIME_CHANGED ⼴播,携带 EXTRA_TIME_PREF_24_HOUR_FORMAT 参数 ,下⾯是核⼼代码vendormediatekproprietarypackagesappsMtkSettivate void set24Hour(boolean is24Hour) { ing(tentResolver(), _12_24, is24Hour ? HOURS_24 : HOURS_12);}private void timeUpdated(boolean is24Hour) { Intent timeChanged = new Intent(_TIME_CHANGED); int timeFormatPreference = is24Hour ? _TIME_PREF_VALUE_USE_24_HOUR : _TIME_PREF_VALUE_USE_12_HOUR; ra(_TIME_PREF_24_HOUR_FORMAT, timeFormatPreference); oadcast(timeChanged);}回到 中,发现 EXTRA_TIME_PREF_24_HOUR_FORMAT 并没有被⽤上,继续深究代码 final void updateClock() { if (mDemoMode) return; eInMillis(tTimeMillis()); setText(getSmallTime()); setContentDescription((e()));}收到⼴播最终都会调⽤ updateClock(),可以看到真正设置时间是通过 getSmallTime() 这个核⼼⽅法private final CharSequence getSmallTime() { Context context = getContext(); boolean is24 = 24HourFormat(context, rentUser()); LocaleData d = (ources().getConfiguration().locale); final char MAGIC1 = 'uEF00'; final char MAGIC2 = 'uEF01'; SimpleDateFormat sdf; String format = mShowSeconds is24 rmat_Hms : rmat_hms : is24 ? rmat_Hm : rmat_hm; if (!(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 < (); i++) { char c = (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 && espace((a-1))) { a--; } format = ing(0, a) + MAGIC1 + ing(a, b) + "a" + MAGIC2 + ing(b + 1); } } mClockFormat = sdf = new SimpleDateFormat(format); mClockFormatString = format; } else { sdf = mClockFormat; } String result = (e()); if (mAmPmStyle != AM_PM_STYLE_NORMAL) { int magic1 = f(MAGIC1); int magic2 = f(MAGIC2); if (magic1 >= 0 && magic2 > magic1) { SpannableStringBuilder formatted = new SpannableStringBuilder(result); if (mAmPmStyle == AM_PM_STYLE_GONE) { (magic1, magic2+1); (magic1, magic2+1); } else { if (mAmPmStyle == AM_PM_STYLE_SMALL) { CharacterStyle style = new RelativeSizeSpan(0.7f); n(style, magic1, magic2, _EXCLUSIVE_INCLUSIVE); } (magic2, magic2 + 1); (magic1, magic1 + 1); } return formatted; } } return result;}⽅法有点长,我们挑主要的分析⼀下,24HourFormat() 最终通过读取 _12_24值,这个值正好在上⾯的 TimeFormatPreferenceController 中点击24⼩时开关是改变,如果这个值为null,则通过获取本地时间Local 来获取当前时间格式,如果等于24则返回true,该⽅法的源码可在AS中点进去查看,此处就不贴了。LocaleData 是⼀个时间格式管理类,在 和 中都频繁使⽤接下来获取到的 format 为 rmat_Hm, 设置给 SimpleDateFormat(rmat_Hm)String result = (e());就是当前需要显⽰的时间,此处还需要做⼀下格式化mAmPmStyle 是通过构造函数⾃定义属性赋值的,xml中并没有赋值,取默认值 AM_PM_STYLE_GONE,⾛这段代码(magic1, magic2+1); 去除多余的 'uEF00' 和 'uEF01',最终显⽰的就是 formatted。参考⽂章
发布者:admin,转转请注明出处:http://www.yc00.com/web/1689431223a247172.html
评论列表(0条)