前言:坚持自己能坚持的,成为自己想成为的,相信时间的累积是有效果的。
Step1复现
这个bug困扰了快一周了,今天终于突然来了灵感解决掉了,记录之。
测试:
General description:
The notification bar display incorrect when edit quick settings.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Reproducibility:
10/10
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Precondition:
Turn on auto-rotate
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Step:
1.Homescreen->Settings->Display->Rotate dut to landscape mode->View->Display size->Change size then change to default size->Back to home screen->Rotate dut to portrait mode->Pull down notification bar->EDIT
2.Check the screen display.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Actual result:
The notification bar display incorrect when edit quick settings.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Expect result:
The notification bar display correct when edit quick settings.
效果图如下:
Step2知识回顾
**A、横竖屏:**
通过这个bug的复现流程,横屏模式下设置 displaySize ,嗯,涉及到android横竖屏切换方面的知识,这方面的知识很薄弱,于是一边解bug一边网上查资料。感谢roserose0002,Cynthia&Sky二位的精彩分析,下面我结合他们的博客做个自己的理解和学习。
当我们在清单文件中不添加关于横竖屏的配置信息时,横竖屏切换时会有一个Activity的销毁和重新创建的过程,一般的项目都会通过在清单文件中配置相关的信息通过执行onConfigureChanged方法来做一些处理。
即:
<uses-permission
Android:name="android.permission.CHANGE_CONFIGURATION"></uses-permission>
允许改变配置信息,系统允许我们通过重写activity中的onConfigurationChanged方法来捕获和修改某些配置信息。
另外在我们想配置的相关的Activity需要添加一个节点
<activity android:name=".recents.RecentsActivity"
android:label="@string/accessibility_desc_recent_apps"
android:exported="false"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
android:screenOrientation="behind"
android:resizeableActivity="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:theme="@style/RecentsTheme.Wallpaper">
<intent-filter>
<action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
</intent-filter>
</activity>
它规定了可以再程序中捕获的事件类型。实际上它有这些字段
修改字体时会调用fontScale,修改display size会调用densityDpi。
B、页面和源代码的对应(MTK)
这个不是什么干货,相当于自己加深印象了,
a、在SystemUI下的qs文件夹下,QSPanel.java(View that represents the quick settings tile panel)
/** View that represents the quick settings tile panel. **/
public class QSPanel extends LinearLayout implements Tunable, Callback {
......
public QSPanel(Context context) {
this(context, null);
}
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setOrientation(VERTICAL);
mBrightnessView = LayoutInflater.from(context).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
addView(mBrightnessView);
mQuickSettingsPlugin = PluginManager.getQuickSettingsPlugin(mContext);
mQuickSettingsPlugin.addOpViews(this);
setupTileLayout();
mFooter = new QSFooter(this, context);
addView(mFooter.getView());
updateResources();
mBrightnessController = new BrightnessController(getContext(),
(ImageView) findViewById(R.id.brightness_icon),
(ToggleSlider) findViewById(R.id.brightness_slider));
}
......
}
再看看哪里调用了这个控件,在Qs_panel.xml
<com.android.systemui.qs.QSContainer
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/quick_settings_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/qs_background_primary"
android:clipToPadding="false"
android:clipChildren="false"
android:elevation="4dp">
<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel"
android:background="#0000"
android:layout_marginTop="@dimen/status_bar_header_height"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp" />
<include layout="@layout/quick_status_bar_expanded_header" />
<include android:id="@+id/qs_detail" layout="@layout/qs_detail" />
<include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
android:visibility="gone" />
</com.android.systemui.qs.QSContainer>
QSPanel属于QSContainer的子控件。效果图是这样的:
QSPanel,点击edit
mEditText = (TextView) findViewById(android.R.id.edit);
mEditText.setOnClickListener(view ->
mHost.startRunnableDismissingKeyguard(() -> showEdit(view)));
private void showEdit(final View v) {
v.post(new Runnable() {
@Override
public void run() {
if (mCustomizePanel != null) {
if (!mCustomizePanel.isCustomizing()) {
int[] loc = new int[2];
v.getLocationInWindow(loc);
int x = loc[0];
int y = loc[1];
mCustomizePanel.show(x, y);
}
}
}
});
}
执行mCustomizePanel.show(x, y);方法,这个类的描述是
/**
* Allows full-screen customization of QS, through show() and hide().
*
* This adds itself to the status bar window, so it can appear on top of quick settings and
* *someday* do fancy animations to get into/out of it.
show 或者 hide详情,是不是这个类的configuration变化,导致quicksetting显示异常
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
View navBackdrop = findViewById(R.id.nav_bar_background);
if (navBackdrop != null) {
boolean shouldShow = newConfig.smallestScreenWidthDp >= 600
|| newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
navBackdrop.setVisibility(shouldShow ? View.VISIBLE : View.GONE);
}
}
事实上QSCustomizer.java的这个方法比较简单,就是在实现父类的同时加上了自己navBackdrop 视图。
在这里里面我尝试reloadwitdth是没用的
private void reloadWidth(View view) {
LayoutParams params = (LayoutParams) view.getLayoutParams();
params.width = getContext().getResources().getDimensionPixelSize(
R.dimen.notification_panel_width);
view.setLayoutParams(params);
}
Step3 找源头
偶然发现了这个类
/**
* Custom {@link FrameLayout} that re-inflates when changes to {@link Configuration} happen.
* Currently supports changes to density and locale.
*/
public class AutoReinflateContainer extends FrameLayout {
......
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
boolean shouldInflateLayout = false;
final int density = newConfig.densityDpi;
if (density != mDensity) {
mDensity = density;
shouldInflateLayout = true;
}
final LocaleList localeList = newConfig.getLocales();
if (localeList != mLocaleList) {
mLocaleList = localeList;
shouldInflateLayout = true;
}
if (shouldInflateLayout) {
inflateLayout();
}
}
private void inflateLayout() {
removeAllViews();
LayoutInflater.from(getContext()).inflate(mLayout, this);
final int N = mInflateListeners.size();
for (int i = 0; i < N; i++) {
mInflateListeners.get(i).onInflated(getChildAt(0));
}
}
......
}
当density 和locale变化时会重新加载view,所以我强行的去掉if,只要配置信息变化就加在view,这个是治标不治本的。
// if (shouldInflateLayout) {
inflateLayout();
// }
至于为什么横屏切换时导致计算错误还要进一步找原因。
最终的图是这样的
谢谢。