SystemUI源碼分析相關文章html
Android8.1 SystemUI源碼分析之 Notification流程java
分析以前再貼一下 StatusBar 相關類圖
linux
從上篇的分析獲得電池圖標對應的佈局爲 SystemUI\src\com\android\systemui\BatteryMeterView.javaandroid
先從構造方法入手架構
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,從上面的構造方法能夠看出,咱們看到的電池圖標是由兩部分組成的,app
電量百分比(TextView)和電池等級(ImageView),構造方法主要作了以下幾個操做ide
在 PhoneStatusBarView 中添加了DarkReceiver監聽,最終調用到 BatteryMeterView 的onDarkChanged()方法函數
修改百分比的字體顏色和電池等級的畫筆顏色和背景顏色oop
////// 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。
參考文章