SystemUI bug案例
1、没有收到通知
每次版本升级是的时候,容易出现这个问题,要搞清楚这个问题,需要你明白通知发送的流程。![在这里插入图片描述]
从这个张时序,可以知道有没有发通知到framework这边来,要看看NotificationManagerService的enqueueNotificationInternal的方法有没有日志打印处理,如果有,不会打印包名、id和notification,所以我们可以通过这个方法知道,通知有没有从系统发出来。
2、修改状态栏4G图标
要想修改4G图标,就需要知道它的初始化流程,知道它的图片是在那个地方设置的。流程图如下:
从数据信号的获取与显示流程中,可以谁知道TelephonyIcons是配置4g图片的地方,所以我们只有把对应图片资源资源id赋值给对应的变量即可。当流程走到MobileSignalController的notifyListeners()方法时调用了getIcons()这个方法,它是mCurrentState.iconGroup获取图片的,所以我们应该再了解一下mCurrentState是怎么初始化的。mCurrentState是在
public SignalController(String tag, Context context, int type, CallbackHandler callbackHandler,
NetworkControllerImpl networkController) {
mTag = TAG + "." + tag;
mNetworkController = networkController;
mTransportType = type;
mContext = context;
mCallbackHandler = callbackHandler;
mCurrentState = cleanState();
mLastState = cleanState();
if (RECORD_HISTORY) {
mHistory = new State[HISTORY_SIZE];
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistory[i] = cleanState();
}
}
}
初始化的,而cleanState()是一个抽象方法,由它的子类实现,我们再看看MobileSignalController是怎么实现的
@Override
protected MobileState cleanState() {
return new MobileState();
}
MobileState 是SignalController.State的子类。那么它的数据是怎么初始化的呢?mCurrentState.iconGroup的值是在两个地方被初始化的,一个是在MobileSignalController的构造函数中:
public MobileSignalController(Context context, Config config, boolean hasMobileData,
TelephonyManager phone, CallbackHandler callbackHandler,
NetworkControllerImpl networkController, SubscriptionInfo info,
SubscriptionDefaults defaults, Looper receiverLooper) {
……
mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons;
……
}
一个是在updateTelephony()方法中
private final void updateTelephony() {
……
MobileIconGroup nr5GIconGroup = getNr5GIconGroup();
if (nr5GIconGroup != null) {
mCurrentState.iconGroup = nr5GIconGroup;
} else if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
} else {
mCurrentState.iconGroup = mDefaultIcons;
}
mCurrentState.dataConnected = mCurrentState.connected
&& mDataState == TelephonyManager.DATA_CONNECTED;
mCurrentState.roaming = isRoaming();
if (isCarrierNetworkChangeActive()) {
mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
} else if (isDataDisabled() && !mConfig.alwaysShowDataRatIcon) {
if (mSubscriptionInfo.getSubscriptionId()
!= mDefaults.getDefaultDataSubId()) {
mCurrentState.iconGroup = TelephonyIcons.NOT_DEFAULT_DATA;
} else {
mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
}
}
……
}
从方法中我们可以看出一般都是从TelephonyIcons获取的,那么nr5GIconGroup和mDefaultIcons是在哪里初始化的?
nr5GIconGroup是从nr5GIconMap中获取的,在NetworkControllerImpl的add5GIconMapping()方法中被赋值的
static void add5GIconMapping(String keyValuePair, Config config) {
……
if (NR_STATUS_STRING_TO_INDEX.containsKey(key)
&& TelephonyIcons.ICON_NAME_TO_ICON.containsKey(value)) {
config.nr5GIconMap.put(
NR_STATUS_STRING_TO_INDEX.get(key),
TelephonyIcons.ICON_NAME_TO_ICON.get(value));
}
}
所以还是通过TelephonyIcons赋值的。
在看看mDefaultIcons是在哪里初始化的,是在mapIconSets()中被初始化的。
private void mapIconSets() {
……
if (!mConfig.showAtLeast3G) {
……
mDefaultIcons = TelephonyIcons.G;
} else {
……
mDefaultIcons = TelephonyIcons.THREE_G;
}
……
}
总结如果想修改4G图片,只需要修改TelephonyIcons类中对4G图片的引用。
3、添加或修改导航栏图标
添加和修改导航栏图片,需要了解导航栏的初始化流程
然后是NavigationBarInflaterView的初始化
状态栏的布局初始化,其实是在NavigationBarInflaterView里面进行的,在构造方法中会调用createInflaters()创建两个横竖布局填充器,在onFinishInflate()中,会调用inflateChildren()将横布局navigation_layout和竖向布局navigation_layout_vertical填充进来。调用clearViews()清除之前的子view的缓存。调用getDefaultLayout()获取系统配置字符串,在这个方法中,我们可以添加或者修改导航栏的图标。
protected String getDefaultLayout() {
final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
? R.string.config_navBarLayoutHandle
: mOverviewProxyService.shouldShowSwipeUpUI()
? R.string.config_navBarLayoutQuickstep
: R.string.config_navBarLayout;
return getContext().getString(defaultResource);
}
字符串配置:
<!-- Nav bar button default ordering/layout -->
<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
<string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string>
<string name="config_navBarLayoutHandle" translatable="false">back[40AC];home_handle;ime_switcher[40AC]</string>
修改这些字符串,或者添加字符串配置,然后在代码中解析自己的配置,这样就可以做到“添加或修改导航栏图标” ,但是这仅仅只是修改了一部分代码,还有其它地方需要修改。需要继续看下面的流程。在inflateLayout()方法中将这个字符串切割成三部分,然后调用inflateButtons()生成布局(其实就是按钮)。
private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
boolean start) {
for (int i = 0; i < buttons.length; i++) {
inflateButton(buttons[i], parent, landscape, start);
}
}
每一个子view都是通过inflateButton()生成的。
protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
boolean start) {
LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
View v = createView(buttonSpec, parent, inflater);
if (v == null) return null;
v = applySize(v, buttonSpec, landscape, start);
parent.addView(v);
addToDispatchers(v);
View lastView = landscape ? mLastLandscape : mLastPortrait;
View accessibilityView = v;
if (v instanceof ReverseRelativeLayout) {
accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
}
if (lastView != null) {
accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
}
if (landscape) {
mLastLandscape = accessibilityView;
} else {
mLastPortrait = accessibilityView;
}
return v;
}
我们可以看到调用了createView()这个方法生成了一个view,我们在看看createView()这个方法。
private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
View v = null;
String button = extractButton(buttonSpec);
if (LEFT.equals(button)) {
button = extractButton(NAVSPACE);
} else if (RIGHT.equals(button)) {
button = extractButton(MENU_IME_ROTATE);
}
if (HOME.equals(button)) {
v = inflater.inflate(R.layout.home, parent, false);
} else if (BACK.equals(button)) {
v = inflater.inflate(R.layout.back, parent, false);
} else if (RECENT.equals(button)) {
v = inflater.inflate(R.layout.recent_apps, parent, false);
} else if (MENU_IME_ROTATE.equals(button)) {
v = inflater.inflate(R.layout.menu_ime, parent, false);
} else if (NAVSPACE.equals(button)) {
v = inflater.inflate(R.layout.nav_key_space, parent, false);
} else if (CLIPBOARD.equals(button)) {
v = inflater.inflate(R.layout.clipboard, parent, false);
} else if (CONTEXTUAL.equals(button)) {
v = inflater.inflate(R.layout.contextual, parent, false);
} else if (HOME_HANDLE.equals(button)) {
v = inflater.inflate(R.layout.home_handle, parent, false);
} else if (IME_SWITCHER.equals(button)) {
v = inflater.inflate(R.layout.ime_switcher, parent, false);
} else if (button.startsWith(KEY)) {
String uri = extractImage(button);
int code = extractKeycode(button);
v = inflater.inflate(R.layout.custom_key, parent, false);
((KeyButtonView) v).setCode(code);
if (uri != null) {
if (uri.contains(":")) {
((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
} else if (uri.contains("/")) {
int index = uri.indexOf('/');
String pkg = uri.substring(0, index);
int id = Integer.parseInt(uri.substring(index + 1));
((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
}
}
}
return v;
}
在这个方法中,我们可以看到解析buttonSpec这个字符串,然后根据字符串去填充不同的布局,因此我们要在这个地方定义自己的业务字符串,新建填充的布局,才能够做到“添加或修改导航栏图标”。
4、设置的音量没有变化
要想知道设置的音量为什么没有变化,需要了解音量的设置流程:
用户按了音频按钮后,音量对话框会显示出来或者调解音量。PhoneWindow的onKeyDown()方法会被调用,接着调用MediaSessionManager的dispatchVolumeKeyEventAsSystemService()来分发事件。调用MediaSessionService.SessionManagerImpl的dispatchVolumeKeyEvent()方法会在debug模式下,打印包名、pid、uid、stream、musicOnly这些信息,这样我们可以通过日志知道Android的framework层有没有收到事件。还有一个地方,它会收到底层更新音量的广播。VolumeDialogControllerImpl的onReceive()中收到android.media.VOLUME_CHANGED_ACTION,查看有没有相关的日子打印出来。