Skip to content

Commit 038b75e

Browse files
committed
Lottie: Add dynamic trace names for doFrame and playAnimation
Enrich Lottie trace slices with composition source names (resource entry name or asset filename) for easier identification in Perfetto traces. Changes: - Thread sourceName from LottieCompositionFactory into LottieComposition for both raw resource and asset loading paths. - Cache and use dynamic trace names in LottieValueAnimator.doFrame() and playAnimation() using Lottie's L trace API. - Add setEnableLightTracing() builder API in LottieConfig to enable lightweight per-frame trace slices independently from the heavy setEnableSystraceMarkers option. This light tracing is safe to be always enabled in production with near zero performance overhead. Bug: 520507665 Bug: 431188311 Test: Build success Flag: EXEMPT DEBUG Change-Id: I0c98012a1c531aa15b1057b1b1abf50a1a6c64ef
1 parent 05ea92e commit 038b75e

6 files changed

Lines changed: 117 additions & 12 deletions

File tree

lottie/src/main/java/com/airbnb/lottie/L.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class L {
2424
public static final String TAG = "LOTTIE";
2525

2626
private static boolean traceEnabled = false;
27+
private static boolean lightTracingEnabled = false;
2728
private static boolean networkCacheEnabled = true;
2829
private static boolean disablePathInterpolatorCache = false;
2930
private static AsyncUpdates defaultAsyncUpdates = AsyncUpdates.AUTOMATIC;
@@ -53,19 +54,40 @@ public static boolean isTraceEnabled(){
5354
return traceEnabled;
5455
}
5556

57+
/**
58+
* Enable light tracing.
59+
*
60+
* Unlike {@link #setTraceEnabled}, this does not trace every layer draw.
61+
* It is safe to always enable this in production with near zero performance issues.
62+
*/
63+
public static void setLightTracingEnabled(boolean enabled) {
64+
lightTracingEnabled = enabled;
65+
if (lightTracingEnabled && lottieTrace == null) {
66+
lottieTrace = new ThreadLocal<>();
67+
}
68+
}
69+
70+
public static boolean isLightTracingEnabled() {
71+
return lightTracingEnabled;
72+
}
73+
74+
public static boolean isAnyTracingEnabled() {
75+
return traceEnabled || lightTracingEnabled;
76+
}
77+
5678
public static void setNetworkCacheEnabled(boolean enabled) {
5779
networkCacheEnabled = enabled;
5880
}
5981

6082
public static void beginSection(String section) {
61-
if (!traceEnabled) {
83+
if (!isAnyTracingEnabled()) {
6284
return;
6385
}
6486
getTrace().beginSection(section);
6587
}
6688

6789
public static float endSection(String section) {
68-
if (!traceEnabled) {
90+
if (!isAnyTracingEnabled()) {
6991
return 0;
7092
}
7193
return getTrace().endSection(section);

lottie/src/main/java/com/airbnb/lottie/Lottie.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public static void initialize(@NonNull final LottieConfig lottieConfig) {
1919
L.setFetcher(lottieConfig.networkFetcher);
2020
L.setCacheProvider(lottieConfig.cacheProvider);
2121
L.setTraceEnabled(lottieConfig.enableSystraceMarkers);
22+
L.setLightTracingEnabled(lottieConfig.enableLightTracing);
2223
L.setNetworkCacheEnabled(lottieConfig.enableNetworkCache);
2324
L.setDisablePathInterpolatorCache(lottieConfig.disablePathInterpolatorCache);
2425
L.setDefaultAsyncUpdates(lottieConfig.defaultAsyncUpdates);

lottie/src/main/java/com/airbnb/lottie/LottieComposition.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ public class LottieComposition {
7171
*/
7272
private int maskAndMatteCount = 0;
7373

74+
/**
75+
* The source name of this composition (resource entry name or asset filename).
76+
* Used for trace identification.
77+
*/
78+
@Nullable
79+
private String sourceName;
80+
7481
private int unscaledWidth;
7582
private int unscaledHeight;
7683

@@ -245,6 +252,23 @@ public int getUnscaledHeight() {
245252
return unscaledHeight;
246253
}
247254

255+
/**
256+
* Set the source name for this composition, used for trace identification.
257+
* This is typically the resource entry name or asset filename.
258+
*/
259+
public void setSourceName(@Nullable String sourceName) {
260+
this.sourceName = sourceName;
261+
}
262+
263+
/**
264+
* Returns the source name of this composition (e.g., the resource entry name or asset filename).
265+
* May be null if the composition was not loaded via a named source.
266+
*/
267+
@Nullable
268+
public String getSourceName() {
269+
return sourceName;
270+
}
271+
248272
@NonNull
249273
@Override
250274
public String toString() {

lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ public static LottieResult<LottieComposition> fromAssetSync(Context context, Str
252252
return new LottieResult<>(cachedComposition);
253253
}
254254
try {
255-
return fromInputStreamSync(context, context.getAssets().open(fileName), cacheKey);
255+
LottieResult<LottieComposition> result = fromInputStreamSync(context, context.getAssets().open(fileName), cacheKey);
256+
if (result.getValue() != null) {
257+
result.getValue().setSourceName(fileName);
258+
}
259+
return result;
256260
} catch (IOException e) {
257261
return new LottieResult<>(e);
258262
}
@@ -366,17 +370,27 @@ public static LottieResult<LottieComposition> fromRawResSync(Context context, @R
366370
}
367371
try {
368372
BufferedSource source = Okio.buffer(source(context.getResources().openRawResource(rawRes)));
373+
LottieResult<LottieComposition> result;
369374
if (isZipCompressed(source)) {
370-
return fromZipStreamSync(context, new ZipInputStream(source.inputStream()), cacheKey);
375+
result = fromZipStreamSync(context, new ZipInputStream(source.inputStream()), cacheKey);
371376
} else if (isGzipCompressed(source)) {
372377
try {
373-
return fromJsonInputStreamSync(new GZIPInputStream(source.inputStream()), cacheKey);
378+
result = fromJsonInputStreamSync(new GZIPInputStream(source.inputStream()), cacheKey);
374379
} catch (IOException e) {
375380
// This shouldn't happen because we check the header for magic bytes.
376381
return new LottieResult<>(e);
377382
}
383+
} else {
384+
result = fromJsonReaderSync(JsonReader.of(source), cacheKey);
378385
}
379-
return fromJsonReaderSync(JsonReader.of(source), cacheKey);
386+
if (result.getValue() != null) {
387+
try {
388+
result.getValue().setSourceName(context.getResources().getResourceEntryName(rawRes));
389+
} catch (Resources.NotFoundException e) {
390+
// Ignore — sourceName is best-effort.
391+
}
392+
}
393+
return result;
380394
} catch (Resources.NotFoundException e) {
381395
return new LottieResult<>(e);
382396
}

lottie/src/main/java/com/airbnb/lottie/LottieConfig.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,19 @@ public class LottieConfig {
2020
@Nullable final LottieNetworkFetcher networkFetcher;
2121
@Nullable final LottieNetworkCacheProvider cacheProvider;
2222
final boolean enableSystraceMarkers;
23+
final boolean enableLightTracing;
2324
final boolean enableNetworkCache;
2425
final boolean disablePathInterpolatorCache;
2526
final AsyncUpdates defaultAsyncUpdates;
2627
final ReducedMotionOption reducedMotionOption;
2728

2829
private LottieConfig(@Nullable LottieNetworkFetcher networkFetcher, @Nullable LottieNetworkCacheProvider cacheProvider,
29-
boolean enableSystraceMarkers, boolean enableNetworkCache, boolean disablePathInterpolatorCache,
30+
boolean enableSystraceMarkers, boolean enableLightTracing, boolean enableNetworkCache, boolean disablePathInterpolatorCache,
3031
AsyncUpdates defaultAsyncUpdates, ReducedMotionOption reducedMotionOption) {
3132
this.networkFetcher = networkFetcher;
3233
this.cacheProvider = cacheProvider;
3334
this.enableSystraceMarkers = enableSystraceMarkers;
35+
this.enableLightTracing = enableLightTracing;
3436
this.enableNetworkCache = enableNetworkCache;
3537
this.disablePathInterpolatorCache = disablePathInterpolatorCache;
3638
this.defaultAsyncUpdates = defaultAsyncUpdates;
@@ -44,6 +46,7 @@ public static final class Builder {
4446
@Nullable
4547
private LottieNetworkCacheProvider cacheProvider;
4648
private boolean enableSystraceMarkers = false;
49+
private boolean enableLightTracing = false;
4750
private boolean enableNetworkCache = true;
4851
private boolean disablePathInterpolatorCache = false;
4952
private AsyncUpdates defaultAsyncUpdates = AsyncUpdates.AUTOMATIC;
@@ -113,6 +116,18 @@ public Builder setEnableSystraceMarkers(boolean enable) {
113116
return this;
114117
}
115118

119+
/**
120+
* Enable light tracing.
121+
*
122+
* Unlike {@link #setEnableSystraceMarkers}, this does not trace every layer draw.
123+
* It has near zero performance overhead and is safe to be always enabled in production.
124+
*/
125+
@NonNull
126+
public Builder setEnableLightTracing(boolean enable) {
127+
enableLightTracing = enable;
128+
return this;
129+
}
130+
116131
/**
117132
* Disable this if you want to completely disable internal Lottie cache for retrieving network animations.
118133
* Internal network cache is enabled by default.
@@ -164,7 +179,7 @@ public Builder setReducedMotionOption(ReducedMotionOption reducedMotionOption) {
164179

165180
@NonNull
166181
public LottieConfig build() {
167-
return new LottieConfig(networkFetcher, cacheProvider, enableSystraceMarkers, enableNetworkCache, disablePathInterpolatorCache,
182+
return new LottieConfig(networkFetcher, cacheProvider, enableSystraceMarkers, enableLightTracing, enableNetworkCache, disablePathInterpolatorCache,
168183
defaultAsyncUpdates, reducedMotionOption);
169184
}
170185
}

lottie/src/main/java/com/airbnb/lottie/utils/LottieValueAnimator.java

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class LottieValueAnimator extends BaseLottieAnimator implements Choreogra
2929
@Nullable private LottieComposition composition;
3030
@VisibleForTesting protected boolean running = false;
3131
private boolean useCompositionFrameRate = false;
32+
private String cachedTraceName;
3233

3334
public LottieValueAnimator() {
3435
}
@@ -84,14 +85,31 @@ public void setUseCompositionFrameRate(boolean useCompositionFrameRate) {
8485
this.useCompositionFrameRate = useCompositionFrameRate;
8586
}
8687

88+
private String getDoFrameTraceName() {
89+
if (cachedTraceName != null) return cachedTraceName;
90+
if (composition != null && composition.getSourceName() != null) {
91+
cachedTraceName = "LottieValueAnimator#doFrame[" + composition.getSourceName() + "]";
92+
}
93+
return cachedTraceName != null ? cachedTraceName : "LottieValueAnimator#doFrame";
94+
}
95+
96+
private String getPlayAnimationTraceSectionName() {
97+
String src = (composition != null && composition.getSourceName() != null)
98+
? composition.getSourceName() : "unknown";
99+
return "LottiePlayAnimation[" + src + "]";
100+
}
101+
87102
@Override public void doFrame(long frameTimeNanos) {
88103
postFrameCallback();
89104
if (composition == null || !isRunning()) {
90105
return;
91106
}
92107

93-
if (L.isTraceEnabled()) {
94-
L.beginSection("LottieValueAnimator#doFrame");
108+
boolean isTracing = L.isAnyTracingEnabled();
109+
String traceSectionName = null;
110+
if (isTracing) {
111+
traceSectionName = getDoFrameTraceName();
112+
L.beginSection(traceSectionName);
95113
}
96114
long timeSinceFrame = lastFrameTimeNs == 0 ? 0 : frameTimeNanos - lastFrameTimeNs;
97115
float frameDuration = getFrameDurationNs();
@@ -130,8 +148,8 @@ public void setUseCompositionFrameRate(boolean useCompositionFrameRate) {
130148
}
131149

132150
verifyFrame();
133-
if (L.isTraceEnabled()) {
134-
L.endSection("LottieValueAnimator#doFrame");
151+
if (isTracing) {
152+
L.endSection(traceSectionName);
135153
}
136154
}
137155

@@ -152,12 +170,14 @@ public void clearComposition() {
152170
this.composition = null;
153171
minFrame = Integer.MIN_VALUE;
154172
maxFrame = Integer.MAX_VALUE;
173+
cachedTraceName = null;
155174
}
156175

157176
public void setComposition(LottieComposition composition) {
158177
// Because the initial composition is loaded async, the first min/max frame may be set
159178
boolean keepMinAndMaxFrames = this.composition == null;
160179
this.composition = composition;
180+
cachedTraceName = null;
161181

162182
if (keepMinAndMaxFrames) {
163183
setMinAndMaxFrames(
@@ -232,12 +252,21 @@ public float getSpeed() {
232252

233253
@MainThread
234254
public void playAnimation() {
255+
boolean isTracing = L.isAnyTracingEnabled();
256+
String traceSectionName = null;
257+
if (isTracing) {
258+
traceSectionName = getPlayAnimationTraceSectionName();
259+
L.beginSection(traceSectionName);
260+
}
235261
running = true;
236262
notifyStart(isReversed());
237263
setFrame((int) (isReversed() ? getMaxFrame() : getMinFrame()));
238264
lastFrameTimeNs = 0;
239265
repeatCount = 0;
240266
postFrameCallback();
267+
if (isTracing) {
268+
L.endSection(traceSectionName);
269+
}
241270
}
242271

243272
@MainThread

0 commit comments

Comments
 (0)