/
ActivityFrameMetrics.java
159 lines (129 loc) · 5.96 KB
/
ActivityFrameMetrics.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package com.frogermcs.activityframemetrics;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.FrameMetrics;
import android.view.Window;
import java.util.HashMap;
import java.util.Map;
/**
* Created by froger_mcs on 14/11/2016.
*/
public class ActivityFrameMetrics implements Application.ActivityLifecycleCallbacks {
private static final float DEFAULT_WARNING_LEVEL_MS = 17.f;
private static final float DEFAULT_ERROR_LEVEL_MS = 34.f;
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
startFrameMetrics(activity);
}
@Override
public void onActivityPaused(Activity activity) {
stopFrameMetrics(activity);
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
private float warningLevelMs;
private float errorLevelMs;
private boolean showWarning;
private boolean showError;
private Map<String, Window.OnFrameMetricsAvailableListener> frameMetricsAvailableListenerMap = new HashMap<>();
private ActivityFrameMetrics() {
}
@TargetApi(Build.VERSION_CODES.N)
public void startFrameMetrics(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
final String activityName = activity.getClass().getSimpleName();
Window.OnFrameMetricsAvailableListener listener = new Window.OnFrameMetricsAvailableListener() {
private int allFrames = 0;
private int jankyFrames = 0;
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
FrameMetrics frameMetricsCopy = new FrameMetrics(frameMetrics);
allFrames++;
float totalDurationMs = (float) (0.000001 * frameMetricsCopy.getMetric(FrameMetrics.TOTAL_DURATION));
if (totalDurationMs > warningLevelMs) {
jankyFrames++;
String msg = String.format("Janky frame detected on %s with total duration: %.2fms\n", activityName, totalDurationMs);
float layoutMeasureDurationMs = (float) (0.000001 * frameMetricsCopy.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION));
float drawDurationMs = (float) (0.000001 * frameMetricsCopy.getMetric(FrameMetrics.DRAW_DURATION));
float gpuCommandMs = (float) (0.000001 * frameMetricsCopy.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION));
float othersMs = totalDurationMs - layoutMeasureDurationMs - drawDurationMs - gpuCommandMs;
float jankyPercent = (float) jankyFrames / allFrames * 100;
msg += String.format("Layout/measure: %.2fms, draw:%.2fms, gpuCommand:%.2fms others:%.2fms\n",
layoutMeasureDurationMs, drawDurationMs, gpuCommandMs, othersMs);
msg += "Janky frames: " + jankyFrames + "/" + allFrames + "(" + jankyPercent + "%)";
if (showWarning && totalDurationMs > errorLevelMs) {
Log.e("FrameMetrics", msg);
} else if (showError) {
Log.w("FrameMetrics", msg);
}
}
}
};
activity.getWindow().addOnFrameMetricsAvailableListener(listener, new Handler());
frameMetricsAvailableListenerMap.put(activityName, listener);
} else {
Log.w("FrameMetrics", "FrameMetrics can work only with Android SDK 24 (Nougat) and higher");
}
}
@TargetApi(Build.VERSION_CODES.N)
public void stopFrameMetrics(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String activityName = activity.getClass().getName();
Window.OnFrameMetricsAvailableListener onFrameMetricsAvailableListener = frameMetricsAvailableListenerMap.get(activityName);
if (onFrameMetricsAvailableListener != null) {
activity.getWindow().removeOnFrameMetricsAvailableListener(onFrameMetricsAvailableListener);
frameMetricsAvailableListenerMap.remove(activityName);
}
}
}
public static class Builder {
private float warningLevelMs = DEFAULT_WARNING_LEVEL_MS;
private float errorLevelMs = DEFAULT_ERROR_LEVEL_MS;
private boolean showWarnings = true;
private boolean showErrors = true;
public Builder() {
}
public Builder warningLevelMs(float warningLevelMs) {
this.warningLevelMs = warningLevelMs;
return this;
}
public Builder errorLevelMs(float errorLevelMs) {
this.errorLevelMs = errorLevelMs;
return this;
}
public Builder showWarnings(boolean show) {
this.showWarnings = show;
return this;
}
public Builder showErrors(boolean show) {
this.showErrors = show;
return this;
}
public ActivityFrameMetrics build() {
ActivityFrameMetrics activityFrameMetrics = new ActivityFrameMetrics();
activityFrameMetrics.warningLevelMs = this.warningLevelMs;
activityFrameMetrics.errorLevelMs = this.errorLevelMs;
activityFrameMetrics.showError = this.showErrors;
activityFrameMetrics.showWarning = this.showWarnings;
return activityFrameMetrics;
}
}
}