systemUI是android非常重要的一部分,我们先看systemUI的启动流程
SystemUI启动流程
手机开机的时候,通过驱动来启动SystemUIService,执行SystemUIApplication中的startServicesIfNeeded方法
/**
* Makes sure that all the SystemUI services are running. If they are already running, this is a
* no-op. This is needed to conditinally start all the services, as we only need to have it in
* the main process.
* <p>This method must only be called from the main thread.</p>
*/
public void startServicesIfNeeded() {
String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
startServicesIfNeeded(names);
}
/**
* Ensures that all the Secondary user SystemUI services are running. If they are already
* running, this is a no-op. This is needed to conditinally start all the services, as we only
* need to have it in the main process.
* <p>This method must only be called from the main thread.</p>
*/
void startSecondaryUserServicesIfNeeded() {
String[] names =
getResources().getStringArray(R.array.config_systemUIServiceComponentsPerUser);
startServicesIfNeeded(names);
}
private void startServicesIfNeeded(String[] services) {
if (mServicesStarted) {
return;
}
mServices = new SystemUI[services.length];
...
for (int i = 0; i < N; i++) {
String clsName = services[i];
if (DEBUG) Log.d(TAG, "loading: " + clsName);
log.traceBegin("StartServices" + clsName);
long ti = System.currentTimeMillis();
Class cls;
try {
cls = Class.forName(clsName);
mServices[i] = (SystemUI) cls.newInstance();
} catch(ClassNotFoundException ex){
throw new RuntimeException(ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
}
mServices[i].mContext = this;
mServices[i].mComponents = mComponents;
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
log.traceEnd();
// Warn if initialization of component takes too long
ti = System.currentTimeMillis() - ti;
if (ti > 1000) {
Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms");
}
if (mBootCompleted) {
mServices[i].onBootCompleted();
}
}
...
}
我们看这段源码可以看出通过startServicesIfNeeded来启动SystemUI的各个模块,因为SystemUI的各个模块都是继承的SystemUI类,例如音量控制模块,通过SystemUI的start方法进行启动
public class VolumeUI extends SystemUI
了解了启动流程后我们来研究SystemUI中最复杂也是最常用的一个模块,状态栏
状态栏
状态栏太过复杂,里面包含着很多个布局样式,如常规显示的状态栏,下拉后状态栏,锁屏后状态栏等,我们一个一个来,先研究常规显示的状态栏
布局页面是status_bar.xml
我们可以将常规显示的状态栏分为三部分左边的通知图标,系统图标和信号、电量和时间,从左往右看,先看通知图标
通知图标的监听类是在NotificationListener,在该类里通过onNotificationPosted 方法监听notification显示,onNotificationRemoved 方法监听移除
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
mPresenter.getHandler().post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
mEntryManager.removeKeyKeptForRemoteInput(key);
boolean isUpdate =
mEntryManager.getNotificationData().get(key) != null;
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since` we're not going to show them
// anyway. This is true also when the summary is canceled,
// because children are automatically canceled by NoMan in that case.
if (!ENABLE_CHILD_NOTIFICATIONS
&& mPresenter.getGroupManager().isChildInGroupWithSummary(sbn)) {
if (DEBUG) {
Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
}
// Remove existing notification to avoid stale data.
if (isUpdate) {
if (DEBUG) Log.d(TAG, "onNotificationPosted, removeNotification: " + sbn);
mEntryManager.removeNotification(key, rankingMap);
} else {
if (DEBUG) Log.d(TAG, "onNotificationPosted, updateRanking: " + sbn);
mEntryManager.getNotificationData()
.updateRanking(rankingMap);
}
return;
}
if (isUpdate) {
mEntryManager.updateNotification(sbn, rankingMap);
} else {
mEntryManager.addNotification(sbn, rankingMap);
}
});
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
mPresenter.getHandler().post(() -> {
mEntryManager.removeNotification(key, rankingMap);
});
}
}
获取通知信息后在NotificationIconAreaController类里的updateIconsForLayout方法中进行添加,移除,修改等操作
/**
* Updates the notification icons for a host layout. This will ensure that the notification
* host layout will have the same icons like the ones in here.
* @param function A function to look up an icon view based on an entry
* @param hostLayout which layout should be updated
* @param showAmbient should ambient notification icons be shown
* @param hideDismissed should dismissed icons be hidden
* @param hideRepliedMessages should messages that have been replied to be hidden
*/
private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function,
NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed,
boolean hideRepliedMessages) {
...
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
//删除不再显示的通知图标
hostLayout.removeView(toRemove.get(i));
}
final FrameLayout.LayoutParams params = generateIconLayoutParams();
for (int i = 0; i < toShow.size(); i++) {
StatusBarIconView v = toShow.get(i);
// The view might still be transiently added if it was just removed and added again
hostLayout.removeTransientView(v);
if (v.getParent() == null) {
if (hideDismissed) {
v.setOnDismissListener(mUpdateStatusBarIcons);
}
//添加显示的通知图标
hostLayout.addView(v, i, params);
}
}
...
}
//控制图标大小
@NonNull
private FrameLayout.LayoutParams generateIconLayoutParams() {
FrameLayout.LayoutParams fp = new FrameLayout.LayoutParams(
mIconSize + 2 * mIconHPadding, getHeight());
fp.gravity = Gravity.CENTER;
return fp;
}
图标间距是在dimens中修改status_bar_icon_padding来进行控制
第二部分系统图标,这块儿主要在PhoneStatusBarPolicy类中添加
系统预定好的icon统计在 frameworks/base/core/res/res/values/config.xml
我们先看布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/system_icons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<!-- 系统图标 -->
<com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:paddingEnd="@dimen/signal_cluster_battery_padding"
android:gravity="center_vertical"
android:orientation="horizontal" />
...
</LinearLayout>
在CollapsedStatusBarFragment中
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mStatusBar = (PhoneStatusBarView) view;
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) {
mStatusBar.go(savedInstanceState.getInt(EXTRA_PANEL_STATE));
}
mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
mDarkIconManager.setShouldLog(true);
//实例化控件布局
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
...
}
实例化布局的地方其实调用的是StatusBarIconControllerImpl中的addIconGroup方法
public void addIconGroup(IconManager group) {
mIconGroups.add(group);
List<Slot> allSlots = getSlots();
for (int i = 0; i < allSlots.size(); i++) {
Slot slot = allSlots.get(i);
List<StatusBarIconHolder> holders = slot.getHolderListInViewOrder();
boolean blocked = mIconBlacklist.contains(slot.getName());
for (StatusBarIconHolder holder : holders) {
int tag = holder.getTag();
int viewIndex = getViewIndex(getSlotIndex(slot.getName()), holder.getTag());
group.onIconAdded(viewIndex, slot.getName(), blocked, holder);
}
}
}
我们看PhoneStatusBarPolicy类
@VisibleForTesting
public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController) {
...
mIconController.setIcon(mSlotEmbms, R.drawable.freeme_stat_sys_embms, null);
//*/
mIconController.setIconVisibility(mSlotEmbms, false);
// Alarm clock
/*/ freeme.gouzhouping, 20180315. FreemeAppTheme, statusbar icons.
mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null);
/*/
mIconController.setIcon(mSlotAlarmClock, R.drawable.freeme_stat_sys_alarm, null);
//*/
mIconController.setIconVisibility(mSlotAlarmClock, false);
// zen
mIconController.setIcon(mSlotZen, R.drawable.stat_sys_zen_important, null);
mIconController.setIconVisibility(mSlotZen, false);
...
}
我们可以看见系统图标显示调用的的方法都是StatusBarIconControllerImpl中的,然后我们根据方法走,最终会发现走到了StatusBarIconController.IconManager里面
//分类并添加显示
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
switch (holder.getType()) {
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
case TYPE_WIFI:
return addSignalIcon(index, slot, holder.getWifiState());
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
}
return null;
}
@VisibleForTesting
protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
StatusBarIcon icon) {
StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
view.set(icon);
mGroup.addView(view, index, onCreateLayoutParams());
return view;
}
@VisibleForTesting
protected StatusBarWifiView addSignalIcon(int index, String slot, WifiIconState state) {
StatusBarWifiView view = onCreateStatusBarWifiView(slot);
view.applyWifiState(state);
mGroup.addView(view, index);
if (mIsInDemoMode) {
mDemoStatusIcons.addDemoWifiView(state);
}
return view;
}
@VisibleForTesting
protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
StatusBarMobileView view = onCreateStatusBarMobileView(slot);
view.applyMobileState(state);
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
mDemoStatusIcons.addMobileView(state);
}
return view;
}
第三部分信号、电量和时间
我们先说信号部分,显示信号的图标的控件跟显示系统图标的控件是一个,因为信号也算是系统图标
但是控制却不在PhoneStatusBarPolicy,而是在StatusBarSignalPolicy类中
public StatusBarSignalPolicy(Context context, StatusBarIconController iconController) {
mContext = context;
...
/// @ }
mIconController = iconController;
//网络控制器
mNetworkController = Dependency.get(NetworkController.class);
mSecurityController = Dependency.get(SecurityController.class);
mNetworkController.addCallback(this);
mSecurityController.addCallback(this);
}
给网络控制器NetworkController添加回调
public void addCallback(SignalCallback cb) {
if (CHATTY) {
Log.d(TAG, "addCallback, cb = " + cb + ", mCurrentSubscriptions = "
+ mCurrentSubscriptions);
}
cb.setSubs(mCurrentSubscriptions);
cb.setIsAirplaneMode(new IconState(mAirplaneMode,
TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
/// M: for ALPS02814831
//cb.setNoSims(mHasNoSubs, mSimDetected);
//wifi信号控制器
mWifiSignalController.notifyListeners(cb);
mEthernetSignalController.notifyListeners(cb);
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
//移动信号控制器
MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.notifyListeners(cb);
}
mCallbackHandler.setListening(cb, true);
/// M: for ALPS02814831,mutil-thread timing issue.
cb.setNoSims(mHasNoSubs, mSimDetected);
}
通过WifiSignalController获取wifi当前状态以及wifi信息,获取到数据后回调会StatusBarSignalPolicy
@Override
public void notifyListeners(SignalCallback callback) {
// only show wifi in the cluster if connected or if wifi-only
boolean wifiVisible = mCurrentState.enabled
&& (mCurrentState.connected || !mHasMobileData);
String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
String contentDescription = getStringIfExists(getContentDescription());
if (mCurrentState.inetCondition == 0) {
contentDescription += ("," + mContext.getString(R.string.data_connection_no_internet));
}
IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
contentDescription);
//将获取的数据进行回调
callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel);
}
@Override
public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
boolean activityIn, boolean activityOut, String description, boolean isTransient,
String statusLabel) {
boolean visible = statusIcon.visible && !mBlockWifi;
boolean in = activityIn && mActivityEnabled && visible;
boolean out = activityOut && mActivityEnabled && visible;
WifiIconState newState = mWifiIconState.copy();
newState.visible = visible;
newState.resId = statusIcon.icon;
newState.activityIn = in;
newState.activityOut = out;
newState.slot = mSlotWifi;
newState.airplaneSpacerVisible = mIsAirplaneMode;
newState.contentDescription = statusIcon.contentDescription;
MobileIconState first = getFirstMobileState();
newState.signalSpacerVisible = first != null && first.typeId != 0;
updateWifiIconWithState(newState);
mWifiIconState = newState;
}
private void updateWifiIconWithState(WifiIconState state) {
//根据当前wifi状态来判断是否显示wifi图标
if (state.visible && state.resId > 0) {
mIconController.setSignalIcon(mSlotWifi, state);
mIconController.setIconVisibility(mSlotWifi, true);
} else {
mIconController.setIconVisibility(mSlotWifi, false);
}
}
wifi的图标样式是由WifiIcons类里设置
电池的自定义控件是BatteryMeterView,但是我们看布局
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<com.android.systemui.BatteryMeterView android:id="@+id/battery"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:visibility="gone"
/>
</LinearLayout>
<include layout="@layout/freeme_status_bar_battery"/>
可以看见BatteryMeterView 已经隐藏了,所以显示的是自定义的电池,也就是布局freeme_status_bar_battery,根据布局里的id可以追踪到,控制布局的类,FreemeBatteryManager
private class BatteryObserver extends ContentObserver {
...
public BatteryObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
onChange(selfChange, null);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
//监听是否显示当前电量
if (selfChange) {
return;
}
mBackgroundHandler.post(mUpdatePercentRunnable);
}
...
}
private final Runnable mUpdatePercentRunnable = new Runnable() {
@Override
public void run() {
mMainHandler.post(()-> {
updateResolver();
update();
});
}
};
private void update() {
if (mIsValid) {
boolean showOutsidePercentView = (isShowFullCharged() && !mShowPercent)
|| (mShowPercent && !mShowPercentIn && (!NotchUtils.hasNotch() || mIsNotchStatusBar));
boolean showPercent = mShowPercent && mShowPercentIn;
boolean showInsideChargeView = mCharging && !showPercent;
boolean showInsideLevelView = !mCharging;
boolean showInsideChargeLevelView = mCharging;
mOutsidePercentView.setVisibility(showOutsidePercentView ? View.VISIBLE : View.GONE);
mInsidePercentView.setVisibility(showPercent ? View.VISIBLE : View.GONE);
mInsideChargeView.setVisibility(showInsideChargeView
&& (mLevel < 100) ? View.VISIBLE : View.GONE);
mInsideLevelView.setVisibility(showInsideLevelView ? View.VISIBLE : View.GONE);
mInsideChargeLevelView.setVisibility(showInsideChargeLevelView ? View.VISIBLE : View.GONE);
updatePercent();
updateLevel();
updateBorderViewDescription();
}
}
这部分就是监听是否显示电量百分比流程,先是在BatteryObserver 监听是否设置,然后再根据监听到的数据去改变对应的布局
mInsidePercentView这个控件便是显示再电池内的textview,如果想修改位置可以在布局中之间进行修改,如果修改字体颜色目前还没有很好的办法,因为常态显示下如果打开应用也会改变字体颜色
关于当前电量不足百分之二十的变色则是跟BatteryStateChangeCallback有关,BatteryStateChangeCallback提供了两个方法
//获取当前电量,每隔一个段时间便会获取一次
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
mLevel = level;
mCharging = charging;
mPlugged = pluggedIn;
mPercentColor = PercentColor.getColor(mPercentColors, mLevel);
update();
}
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
mIsPowerSave = isPowerSave;
for (BatteryView view : mBatteryViews) {
view.updatePowerSave();
}
}
关于电池内部充电时以及非充电时和电量低时的颜色修改是由drawable文件控制
mInsideLevelView.setImageResource(R.drawable.stat_sys_battery_new_svg);
mInsideChargeLevelView.setImageResource(R.drawable.stat_sys_battery_charge_new_svg);
mInsideChargeView.setImageResource(R.drawable.ic_statusbar_battery_charge);
修改对应的drawable文件就可以实现修改颜色
我们看时间,首先看布局
<!-- 系统图标区域 -->
<com.android.keyguard.AlphaOptimizedLinearLayout
android:id="@+id/system_icon_area"
android:layout_width="0dp"
android:layout_weight="1"
android:paddingStart="5dp"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical|end"
>
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical|end">
<!-- 网络信号 -->
<include
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:gravity="center_vertical|end"
layout="@layout/freeme_status_bar_network_speed"/>
<!-- 电池 -->
<include
android:layout_width="wrap_content"
android:layout_height="match_parent"
layout="@layout/system_icons" />
</LinearLayout>
<!-- 时间控件 -->
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_clock_end_padding"
android:paddingTop="@dimen/freeme_battery_clock_padding_top"
android:textSize="12dp"
systemui:amPmStyle="normal"
android:textStyle="bold"
android:gravity="center_vertical|end"
/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
Clock就是时间控件,想要修改字体大小或者颜色或者显示位置可以在布局文件中直接修改