使用腾讯GT测试Android app流畅度

日期 2016-04-24
使用腾讯GT测试Android app流畅度

首先简单介绍下检测流畅度的原理:
大家在看logcat的时候是不是有时候会看到这样的log

Skipped 30 frames! The application may be doing too much work on its main thread.

输出这段log的是android.view.Choreographer

private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt("debug.choreographer.skipwarning", 30);

void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}

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.");
}
}
}

//...
}

也就是当跳帧数大于设置的SKIPPED_FRAME_WARNING_LIMIT 值时会在当前进程输出这个log.
这也是google在android 4.1中加入的vsync垂直同步特性,防止界面过于卡顿。
那么我们只要捕获这个log提取出skippedFrames 不就可以知道界面是否卡顿了吗?
而且如果可以把SKIPPED_FRAME_WARNING_LIMIT 设置为1的话更是可以非常精准的知道跳过了多少帧!

首先非常感谢腾讯开源了 TencentOpen/GT,现在我们来看看GT是怎么做到的:
1.在com.tencent.wstt.gt.plugin.smtools.SMActivity 中我们看到几条读写prop的命令

>

getprop debug.choreographer.skipwarning      //读取
setprop debug.choreographer.skipwarning 1 //修改
setprop debug.choreographer.skipwarning 30
setprop ctl.restart surfaceflinger; setprop ctl.restart zygote //重启

显然,当设置为1后重启,SKIPPED_FRAME_WARNING_LIMIT 值就修改了,可能会有很多log输出。

注意:getprop不需要权限但是setprop需要root权限,而且SKIPPED_FRAME_WARNING_LIMIT
静态常量所以会在重启后生效。

2.然后在com.tencent.wstt.gt.plugin.smtools.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;
}
//root情况下会得到全局log,需要判断是否是指定进程输出的,
int pID = LogLine.newLogLine(line, false).getProcessId();
if (pID != pid){
continue;
}

//简单粗暴的提取出skippedFrames...
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 -v time Choreographer:I *:S 这条命令只输出
Log.i("Choreographer","") 的log

3.最后在com.tencent.wstt.gt.plugin.smtools.SMDataService 中计算出处理的流畅度的。


整个过程也不是非常复杂但是需要root权限有点坑,其实核心的2个步骤是修改
SKIPPED_FRAME_WARNING_LIMIT 和 读取logcat,其中读取自己logcat是不需要root权限的,但是怎么修改
SKIPPED_FRAME_WARNING_LIMIT 呢,反射吗?这可是static final 的啊,在jvm里面确实不可修改,但是在android里面呢?试试吧


static {
try {
Field field = Choreographer.class.getDeclaredField("SKIPPED_FRAME_WARNING_LIMIT");
field.setAccessible(true);
field.set(Choreographer.class,1);
Log.e("test", "static initializer --> "+field.get(Choreographer.class));
} catch (Throwable e) {
e.printStackTrace();
}
}

你没看错,其实是可以修改的!当然只是修改自己进程的,但这就够了。
还有一个问题的怎么在A应用中读取B应用的logcat呢?不错,设置相同的android:sharedUserId就可以了。
说了这么多,来试试!
TencentOpen/GT 原项目没有提供依赖jar,这里我添加好了,并添加了build.gradle方便编译
项目地址https://github.com/8enet/GT/tree/master/android
没有修改任何代码,可以自行根据说明添加jar包,那么现在只需要通过以下步骤即可免root检测应用流畅度了

  1. 在自己开发的应用中通过反射修改SKIPPED_FRAME_WARNING_LIMIT 并且设置
    android:sharedUserId
  2. 手动编译GT项目,设置和自己应用一样的android:sharedUserId
    ok,安装后即可检测流畅度了。