GT 源码:https://github.com/TencentOpen/GT

在 GT 工具的插件中有一个可以测试应用流畅度的插件,通过其源码,去了解下 GT 是如何去测试流畅度的。

GT

插件流畅度调试的源码位于:
https://github.com/TencentOpen/GT/tree/master/android/src/com/tencent/wstt/gt/plugin/smtools

SMActivity

先看界面上几个按钮监听事件的代码:

View.OnClickListener button_check_status = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            String cmd = "getprop debug.choreographer.skipwarning";
            ProcessBuilder execBuilder = new ProcessBuilder("sh", "-c", cmd);
            execBuilder.redirectErrorStream(true);
            try {
                TextView textview = (TextView) findViewById(R.id.textviewInformation);
                Process p = execBuilder.start();
                InputStream is = p.getInputStream();
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                Boolean flag = false;
                String line;
                while ((line = br.readLine()) != null) {
                    if (line.compareTo("1") == 0) {
                        flag = true;
                        break;
                    }
                }

                if (flag) {
                    textview.setText("OK");
                } else {
                    textview.setText("NOT OK");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
View.OnClickListener button_write_property = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            String cmd = "setprop debug.choreographer.skipwarning 1";
            ProcessBuilder execBuilder = new ProcessBuilder("su", "-c", cmd);
            execBuilder.redirectErrorStream(true);
            try {
                execBuilder.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
View.OnClickListener button_recover_property = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            String cmd = "setprop debug.choreographer.skipwarning 30";
            ProcessBuilder execBuilder = new ProcessBuilder("su", "-c", cmd);
            execBuilder.redirectErrorStream(true);
            try {
                execBuilder.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
View.OnClickListener button_restart = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            String cmd = "setprop ctl.restart surfaceflinger; setprop ctl.restart zygote";
            ProcessBuilder execBuilder = new ProcessBuilder("su", "-c", cmd);
            execBuilder.redirectErrorStream(true);
            try {
                execBuilder.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };

基本流程就是:检测 >> 更改 >> 重启 >> 恢复
检测的时候,执行了命令getprop debug.choreographer.skipwarning
只有在执行该命令后,输出内容为 1 的时候,才会显示OK,即表示此时可以进行流畅度的测试

这个值在默认情况下,获取到的是空值。为什么要设为 1,才表示环境 OK 呢?

这时候需要去看 Choreorgrapher 的源码:
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/Choreographer.java

先记住TAG的值:

private static final String TAG = "Choreographer";

从下面的代码中可以看到,虽然在默认情况下获取不到 skipwarning 的值,但其实系统以及默认设为了 30,这也是为什么在恢复按钮的监听代码中将其值设为 30 的原因。

// Set a limit to warn about skipped frames.
// Skipped frames imply jank.
private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
            "debug.choreographer.skipwarning", 30);

接下来看下面的代码:

long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
    final long skippedFrames = jitterNanos / mFrameIntervalNanos;
        if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
            Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
        }

其中

mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

由于SKIPPED_FRAME_WARNING_LIMIT的值默认为 30,因此在一般情况下,通过命令adb logcat - v time -s Choreographer,很少看到有日志信息的输出,因此需要将 SKIPPED_FRAME_WARNING_LIMIT 的值设为 1,这样可以保证只要有丢帧,jitterNanos >= mFrameIntervalNanos 的值就为 true,这样就会有日志信息输出。

在设置了 debug.choreographer.skipwarning 之后,需要重启Zygote,因此重启按钮的监听代码中使用了命令setprop ctl.restart surfaceflinger; setprop ctl.restart zygote进行重启。

另外通过代码

ProcessBuilder execBuilder = new ProcessBuilder("su", "-c", cmd);

执行这些命令都需要拥有 Root 权限!

都设置完成后就可以在 logcat 中看到有丢帧的日志信息输出:

[xuxu:~]$ adb logcat -c && adb logcat -v time -s Choreographer
--------- beginning of main
--------- beginning of system
04-30 17:45:52.927 I/Choreographer(25260): Skipped 3 frames!  The application may be doing too much work on its main thread.
04-30 17:45:53.742 I/Choreographer(25260): Skipped 12 frames!  The application may be doing too much work on its main thread.
04-30 17:45:53.866 I/Choreographer(25260): Skipped 6 frames!  The application may be doing too much work on its main thread.
04-30 17:45:53.931 I/Choreographer(25260): Skipped 2 frames!  The application may be doing too much work on its main thread.
04-30 17:45:54.108 I/Choreographer(25260): Skipped 10 frames!  The application may be doing too much work on its main thread.
04-30 17:45:54.488 I/Choreographer(25260): Skipped 21 frames!  The application may be doing too much work on its main thread.
04-30 17:45:54.576 I/Choreographer(25260): Skipped 4 frames!  The application may be doing too much work on its main thread.
04-30 17:45:54.644 I/Choreographer(25260): Skipped 3 frames!  The application may be doing too much work on its main thread.
04-30 17:45:54.910 I/Choreographer(25260): Skipped 1 frames!  The application may be doing too much work on its main thread.
04-30 17:45:58.390 I/Choreographer(25260): Skipped 1 frames!  The application may be doing too much work on its main thread.
04-30 17:45:59.501 I/Choreographer(25260): Skipped 2 frames!  The application may be doing too much work on its main thread.
04-30 17:45:59.634 I/Choreographer(25260): Skipped 7 frames!  The application may be doing too much work on its main thread.

SMLogService

protected void onHandleIntent(Intent intent) {
        try {

            String str = intent.getStringExtra("pid");
            int pid = Integer.parseInt(str);

            List<String> args = new ArrayList<String>(Arrays.asList("logcat", "-v", "time", "Choreographer:I", "*:S"));

            dumpLogcatProcess = RuntimeHelper.exec(args);
            reader = new BufferedReader(new InputStreamReader(dumpLogcatProcess.getInputStream()), 8192);

            String line;

            while ((line = reader.readLine()) != null && !killed) {

                // filter "The application may be doing too much work on its main thread."
                if (!line.contains("uch work on its main t")) {
                    continue;
                }
                int pID = LogLine.newLogLine(line, false).getProcessId();
                if (pID != pid){
                    continue;
                }

                line = line.substring(50, line.length() - 71);
                Integer value = Integer.parseInt(line.trim());

                SMServiceHelper.getInstance().dataQueue.offer(value);
            }
        } catch (IOException e) {
            Log.e(TAG, e.toString() + "unexpected exception");
        } finally {
            killProcess();
        }
    }

执行 logcat 命令获取 log 信息:

List<String> args = new ArrayList<String>(Arrays.asList("logcat", "-v", "time", "Choreographer:I", "*:S"));

log 信息内容为:

04-30 17:45:52.927 I/Choreographer(25260): Skipped 3 frames!  The application may be doing too much work on its main thread.

无聊的同学可以数下,截取的结果是什么。。

line = line.substring(50, line.length() - 71);
Integer value = Integer.parseInt(line.trim());

SMDataService

while (true) {
    if (pause) {
        break;
    }
    int x = count.getAndSet(0);
    // 卡顿大于60时,要将之前几次SM计数做修正
    if (x > 60) {
        int n = x / 60;
        int v = x % 60;
        TagTimeEntry tte = OpPerfBridge.getProfilerData(key);
        int len = tte.getRecordSize();
        // 补偿参数
        int p = n;
        //Math.min(len, n);
        /*
        * n > len是刚启动测试的情况,日志中的亡灵作祟,这种情况不做补偿;
        * 并且本次也记为60。本逻辑在两次测试间会清理数据的情况生效。
        */
        if (n > len) {
            globalClient.setOutPara(key, 60);
//          globalClient.setOutPara(SFKey, 0);
        } else {
            for (int i = 0; i < p; i++) {
            TimeEntry te = tte.getRecord(len - 1 - i);
            te.reduce = 0;
            }
        globalClient.setOutPara(key, v);
//      globalClient.setOutPara(SFKey, 60 - v);
        }
    } else {
        int sm = 60 - x;
        globalClient.setOutPara(key, sm);
//      globalClient.setOutPara(SFKey, x);
    }

这里面是对获取到的数据做一些修正。

end...


↙↙↙阅读原文可查看相关链接,并与作者交流