GT 源码:https://github.com/TencentOpen/GT
在 GT 工具的插件中有一个可以测试应用流畅度的插件,通过其源码,去了解下 GT 是如何去测试流畅度的。
插件流畅度调试
的源码位于:
https://github.com/TencentOpen/GT/tree/master/android/src/com/tencent/wstt/gt/plugin/smtools
先看界面上几个按钮监听事件的代码:
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.
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());
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...