05
2017
10

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">
     <LinearLayout
上一篇:Python GUI编程(Tkinter)创建一个GUI程序 下一篇:(一)安卓框架搭建之项目分层、主题、gradle基本配置