GT源碼:https://github.com/TencentOpen/GT
一.流暢度模塊的代碼結構
流暢度插件總共就幾個類,其實處理方式也比較簡單粗暴,就是通過Choreographer輸出的log信息獲取跳幀數據。SMActivity.java為插件的入口類,你可以通過預設環境操作來實現log打印操作,然后通過SMLogService.java過濾出當前進程的丟幀值,最后由SMServiceHelper.java來進行數據處理。流暢度值為60減去1s內的跳幀數。
二.流暢度測試
1.簡要流程
- 執行setprop debug.choreographer.skipwarning 1
- 執行getprop debug.choreographer.skipwarning判斷,為1則可以進行測試
- 執行adb logcat -v time -s Choreographer:I *:S
- 過濾獲取當前pid丟幀值
- 數據處理得到sm值
2.代碼流程
-
執行setprop debug.choreographer.skipwarning 1
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(); } } };
-
執行getprop debug.choreographer.skipwarning判斷,為1則可以進行測試
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(); } } };
- 執行adb logcat -v time -s Choreographer:I *:S
-
過濾獲取當前pid丟幀值
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(); } }
-
數據處理得到sm值
騰訊這邊的處理方案是:當丟幀<60時,流暢度SM =60-frame; 當丟幀frame>60時,流暢度SM = 60-frame%60。不過這種處理方式是有問題的。在這里要先說下流暢度計算的原理:
VSync機制可以通過其Loop來了解當前App最高繪制能力,固定每隔16.6ms執行一次,這樣最高的刷新的幀率就控制在60FPS以內,Choreographer日志可以打印當前丟幀數,因此通過計算,得到當前APP的流暢度。
而計算這樣來計算可能會更加准確:
SM= 60-丟幀frame/每兩行同一線程的丟幀時間差(單位:s),如果只關心UI線程,那就只需要統計UI線程即可。
-
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); }