1. 程式人生 > >SystemUI下的快速設定面板顯示異常

SystemUI下的快速設定面板顯示異常

前言:堅持自己能堅持的,成為自己想成為的,相信時間的累積是有效果的。

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一邊網上查資料。感謝roserose0002Cynthia&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();
      //  }

至於為什麼橫屏切換時導致計算錯誤還要進一步找原因。
最終的圖是這樣的這裡寫圖片描述

謝謝。