Android過度繪制自動化代碼實現

Android 過度繪制指的是在屏幕某個像素在同一幀的時間內被繪制多次(超過一次),嚴重的過度繪制會浪費cpu及gpu資源導致性能問題。Google編輯精選對App頁面的過度繪制有要求,因此需要對所有的頁面進行過度繪制測試。在日常版本測試過程中完全依靠手工來測試頁面是否存在過度繪制問題,然而App不同的頁面有接近80個,全部手工測試耗時費力。所以,在日常版本測試接手策略中隻針對新增頁面進行過度繪制手工測試。因此,如何高效的進行全部頁面過度繪制測試,並真正納入到版本測試流程中,從而保證app所有頁面不存在嚴重的過度繪制問題。

影響

不符合編輯精選對app無可挑剔的質量的要求 在低端機器上,常見的比如listView上下滑動,過度繪制的情況下,同一幀的時間內被繪制多次可能出現丟幀導致出現卡頓,或者跳躍感很明顯。

發現方式

手工的測試方式

在日常測試過程中按照以下步驟打開Show GPU Overrdraw的選項來測試當前頁面是否存在過度繪制:

設置 -> 開發者選項 -> 調試GPU過度繪制 -> 顯示GPU過度繪制

由於App不同的頁面較多,全部手工測試耗時費力,因此需要考慮能否自動化來實現

自動化實現過度繪制測試

實現方案調研

實時獲取過度繪制overdrawCounter值

通過閱讀Android 源碼,在查看過度繪制實現代碼/frameworks/base/libs/hwui/OpenGLRenderer.cpp,瞭解到其實現原理:


void OpenGLRenderer::renderOverdraw() {
        ......

        // 1x overdraw
        mCaches.stencil.enableDebugTest(2);
        drawColor(mCaches.getOverdrawColor(1), SkXfermode::kSrcOver_Mode);

        // 2x overdraw
        mCaches.stencil.enableDebugTest(3);
        drawColor(mCaches.getOverdrawColor(2), SkXfermode::kSrcOver_Mode);

        // 3x overdraw
        mCaches.stencil.enableDebugTest(4);
        drawColor(mCaches.getOverdrawColor(3), SkXfermode::kSrcOver_Mode);

        // 4x overdraw and higher
        mCaches.stencil.enableDebugTest(4, true);
        drawColor(mCaches.getOverdrawColor(4), SkXfermode::kSrcOver_Mode);

        mCaches.stencil.disable();
    }
}

void OpenGLRenderer::countOverdraw() {
    size_t count = mWidth * mHeight;
    uint32_t* buffer = new uint32_t[count];
    glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]);

    size_t total = 0;
    for (size_t i = 0; i < count; i++) {
        total += buffer[i] & 0xff;
    }

    mOverdraw = total / float(count);

    delete[] buffer;
}

通過drawColor函數在Android 每個view繪制完成後,根據像素點重繪次數,重新在界面上繪制不同顏色來辨識該像素點的過度繪制情況。同時在這裡有一個countOverdraw數值,我們可以該指標來度量Overdraw程度。
由於此處代碼屬於c的代碼,無法獲取到該數值,繼續查看調用發現Framework/base/core/Java/android/view/HardwareRender.java中有使用到該數值。

private void drawOverdrawCounter(HardwareCanvas canvas, float overdraw, float density) {
            final String text = String.format(\"%.2fx\", overdraw);
            final Paint paint = setupPaint(density);
            // HSBtoColor will clamp the values in the 0..1 range
            paint.setColor(Color.HSBtoColor(0.28f - 0.28f * overdraw / 3.5f, 0.8f, 1.0f));
            canvas.drawText(text, density * 4.0f, mHeight - paint.getFontMetrics().bottom, paint);
}

基於以上源碼分析,我們可以通過hook來得到OverdrawCounter來度量Overdraw程度。

切換debugGPUoverdraw模式

該函數需要在設備的Setting中debugGPUoverdraw切換Show Overdraw Counter模式下,才能夠執行。Android源碼中切換debugGPUoverdraw代碼如下:

private void writeDebugHwOverdrawOptions(Object newValue) {
   SystemProperties.set(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY,
           newValue == null ? "" : newValue.toString());
   pokeSystemProperties();
   updateDebugHwOverdrawOptions();
}

由於上面的代碼中有Android 系統隱藏的API,無法直接調用,但可以通過反射實現該功能,具體實現代碼如下:

public void writeDebugHwOverdrawOptions() {

        Class clz = null;
        try {
            clz = Class.forName("android.os.ServiceManager");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Method method = null;
        Method methodcheckService = null;
        try {
            method = clz.getMethod("listServices");
            methodcheckService = clz.getMethod("checkService", String.class);

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        String[] services = null;
        try {
            services = (String[]) method.invoke(clz);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        Parcel data = null;
        for (String service : services) {
            IBinder obj = null;
            try {
                obj = (IBinder) methodcheckService.invoke(clz, service);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            if (obj != null) {
                data = Parcel.obtain();
                try {
                    obj.transact(SYSPROPS_TRANSACTION, data, null, 0);
                } catch (RemoteException e) {
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            data.recycle();
        }
    }

參照Android源碼中Setting中的debugGPUoverdraw 切換選項的執行過程,通過修改系統屬性debug.hwui.overdraw為空/count/show,在執行通知系統中所有service的函數,從而實現切換選項。同時可以避免一直開啟過度繪制,導致手機會有卡頓情況。

但是drawOverdrawCounter函數僅在Android 4.4.4源碼中有實現,在Android 5.0之後就被去掉瞭,該方案僅能夠在Android 4.4.4上實現。通過實際對比,發現過度繪制在不同操作系統版本上表現一致,因此該方案依舊可行。

具體實現

實時獲取當前頁面過度繪制OverdrawCounter值

新建一個Android 4.4.4 模擬器,並安裝好Xposed框架 通過hook系統函數android.view.HardwareCanvas的內部類android.view.HardwareRenderer$GlRenderer的方法drawOverdrawCounter來獲取過度繪制的Counter值 將該值實時保存在/sdcard/overDraw.txt中,可以通過adb -s deviceid shell cat /sdcard/overDraw.txt獲取該值

過度繪制UI自動化

結合appium的android ui自動化,當每個case進入到指定的頁面,進行收集過度繪制數值及截圖,最後進行數據展示。當進入到指定頁面,打開debugGPUoverdraw的Count模式,得到OverdrawCounter,如果OverdrawCounter大於3,則認為overdraw瞭,切換為show模式,截圖並保存在out/目錄下。然後關閉debugGPUoverdraw,避免一直開啟導致性能問題影響case成功率。執行最後所有case執行完後生成html報告,點擊對應柱狀圖跳轉到對應的截圖

UI執行到指定頁面 切換到count模式,獲取OverdrawCounter值 如果OverdrawCounter值大於3,切換show模式,截圖 關閉debugGPUoverdraw,用例結束

bug解決辦法

在res/layout/shipping_package_header.xml,去掉瞭android:background=”@color/white”多餘的背景色。

     android:id="@+id/ll_shipping_package"
     android:layout_width="match_parent"
     android:layout_height="match_parent" 
-    android:background="@color/white"
     android:orientation="vertical">
     
        
   

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *