Skip to content

Commit

Permalink
Initial refactoring of Activity callbacks (#476)
Browse files Browse the repository at this point in the history
* introduce DefaultingActivityLifecycleCallbacks and factor out much of the guts of ActivityCallbacks into other units.

* decouple startup timer from ActivityCallbacks

* relax type

* decouple fragment callback registration from activity callbacks.

* fix up tests

* add test

* add VSLBTest

* add Pre29VSLBTest

* add test for ActivityTracerCache

* spotless

* separate install steps for activity/lifecycle instrumentation

* code review comments
  • Loading branch information
breedx-splk authored Feb 17, 2023
1 parent f192ef0 commit 6d38904
Show file tree
Hide file tree
Showing 18 changed files with 814 additions and 268 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,175 +17,110 @@
package com.splunk.rum;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import io.opentelemetry.api.trace.Tracer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import io.opentelemetry.rum.internal.DefaultingActivityLifecycleCallbacks;

class ActivityCallbacks implements Application.ActivityLifecycleCallbacks {
class ActivityCallbacks implements DefaultingActivityLifecycleCallbacks {

private final Map<String, ActivityTracer> tracersByActivityClassName = new HashMap<>();
private final AtomicReference<String> initialAppActivity = new AtomicReference<>();
private final Tracer tracer;
private final VisibleScreenTracker visibleScreenTracker;
private final AppStartupTimer startupTimer;
private final ActivityTracerCache tracers;

ActivityCallbacks(
Tracer tracer,
VisibleScreenTracker visibleScreenTracker,
AppStartupTimer startupTimer) {
this.tracer = tracer;
this.visibleScreenTracker = visibleScreenTracker;
this.startupTimer = startupTimer;
ActivityCallbacks(ActivityTracerCache tracers) {
this.tracers = tracers;
}

@Override
public void onActivityPreCreated(
@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
getTracer(activity).startActivityCreation().addEvent("activityPreCreated");

if (activity instanceof FragmentActivity) {
FragmentManager fragmentManager =
((FragmentActivity) activity).getSupportFragmentManager();
fragmentManager.registerFragmentLifecycleCallbacks(
new RumFragmentLifecycleCallbacks(tracer, visibleScreenTracker), true);
}
tracers.startActivityCreation(activity).addEvent("activityPreCreated");
}

@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
startupTimer.startUiInit();
addEvent(activity, "activityCreated");
tracers.addEvent(activity, "activityCreated");
}

@Override
public void onActivityPostCreated(
@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
addEvent(activity, "activityPostCreated");
tracers.addEvent(activity, "activityPostCreated");
}

@Override
public void onActivityPreStarted(@NonNull Activity activity) {
getTracer(activity)
.initiateRestartSpanIfNecessary(tracersByActivityClassName.size() > 1)
.addEvent("activityPreStarted");
tracers.initiateRestartSpanIfNecessary(activity).addEvent("activityPreStarted");
}

@Override
public void onActivityStarted(@NonNull Activity activity) {
addEvent(activity, "activityStarted");
tracers.addEvent(activity, "activityStarted");
}

@Override
public void onActivityPostStarted(@NonNull Activity activity) {
addEvent(activity, "activityPostStarted");
tracers.addEvent(activity, "activityPostStarted");
}

@Override
public void onActivityPreResumed(@NonNull Activity activity) {
getTracer(activity).startSpanIfNoneInProgress("Resumed").addEvent("activityPreResumed");
tracers.startSpanIfNoneInProgress(activity, "Resumed").addEvent("activityPreResumed");
}

@Override
public void onActivityResumed(@NonNull Activity activity) {
addEvent(activity, "activityResumed");
tracers.addEvent(activity, "activityResumed");
}

@Override
public void onActivityPostResumed(@NonNull Activity activity) {
getTracer(activity)
.addEvent("activityPostResumed")
tracers.addEvent(activity, "activityPostResumed")
.addPreviousScreenAttribute()
.endSpanForActivityResumed();
visibleScreenTracker.activityResumed(activity);
}

@Override
public void onActivityPrePaused(@NonNull Activity activity) {
getTracer(activity).startSpanIfNoneInProgress("Paused").addEvent("activityPrePaused");
visibleScreenTracker.activityPaused(activity);
tracers.startSpanIfNoneInProgress(activity, "Paused").addEvent("activityPrePaused");
}

@Override
public void onActivityPaused(@NonNull Activity activity) {
addEvent(activity, "activityPaused");
tracers.addEvent(activity, "activityPaused");
}

@Override
public void onActivityPostPaused(@NonNull Activity activity) {
getTracer(activity).addEvent("activityPostPaused").endActiveSpan();
tracers.addEvent(activity, "activityPostPaused").endActiveSpan();
}

@Override
public void onActivityPreStopped(@NonNull Activity activity) {
getTracer(activity).startSpanIfNoneInProgress("Stopped").addEvent("activityPreStopped");
tracers.startSpanIfNoneInProgress(activity, "Stopped").addEvent("activityPreStopped");
}

@Override
public void onActivityStopped(@NonNull Activity activity) {
addEvent(activity, "activityStopped");
tracers.addEvent(activity, "activityStopped");
}

@Override
public void onActivityPostStopped(@NonNull Activity activity) {
getTracer(activity).addEvent("activityPostStopped").endActiveSpan();
}

@Override
public void onActivityPreSaveInstanceState(
@NonNull Activity activity, @NonNull Bundle outState) {
// todo: add event
}

@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
// todo: add event
}

@Override
public void onActivityPostSaveInstanceState(
@NonNull Activity activity, @NonNull Bundle outState) {
// todo: add event
tracers.addEvent(activity, "activityPostStopped").endActiveSpan();
}

@Override
public void onActivityPreDestroyed(@NonNull Activity activity) {
getTracer(activity).startSpanIfNoneInProgress("Destroyed").addEvent("activityPreDestroyed");
tracers.startSpanIfNoneInProgress(activity, "Destroyed").addEvent("activityPreDestroyed");
}

@Override
public void onActivityDestroyed(@NonNull Activity activity) {
addEvent(activity, "activityDestroyed");
tracers.addEvent(activity, "activityDestroyed");
}

@Override
public void onActivityPostDestroyed(@NonNull Activity activity) {
getTracer(activity).addEvent("activityPostDestroyed").endActiveSpan();
}

private void addEvent(@NonNull Activity activity, String eventName) {
getTracer(activity).addEvent(eventName);
}

private ActivityTracer getTracer(Activity activity) {
ActivityTracer activityTracer =
tracersByActivityClassName.get(activity.getClass().getName());
if (activityTracer == null) {
activityTracer =
new ActivityTracer(
activity,
initialAppActivity,
tracer,
visibleScreenTracker,
startupTimer);
tracersByActivityClassName.put(activity.getClass().getName(), activityTracer);
}
return activityTracer;
tracers.addEvent(activity, "activityPostDestroyed").endActiveSpan();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ class ActivityTracer {
this.activeSpan = new ActiveSpan(visibleScreenTracker);
}

ActivityTracer startSpanIfNoneInProgress(String action) {
ActivityTracer startSpanIfNoneInProgress(String spanName) {
if (activeSpan.spanInProgress()) {
return this;
}
activeSpan.startSpan(() -> createSpan(action));
activeSpan.startSpan(() -> createSpan(spanName));
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.splunk.rum;

import android.app.Activity;
import androidx.annotation.VisibleForTesting;
import io.opentelemetry.api.trace.Tracer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

/**
* Encapsulates the fact that we have an ActivityTracer instance per Activity class, and provides
* convenience methods for adding events and starting spans.
*/
class ActivityTracerCache {

private final Map<String, ActivityTracer> tracersByActivityClassName = new HashMap<>();

private final Function<Activity, ActivityTracer> tracerFactory;

public ActivityTracerCache(
Tracer tracer,
VisibleScreenTracker visibleScreenTracker,
AppStartupTimer startupTimer) {
this(tracer, visibleScreenTracker, new AtomicReference<>(), startupTimer);
}

@VisibleForTesting
ActivityTracerCache(
Tracer tracer,
VisibleScreenTracker visibleScreenTracker,
AtomicReference<String> initialAppActivity,
AppStartupTimer startupTimer) {
this(
activity ->
new ActivityTracer(
activity,
initialAppActivity,
tracer,
visibleScreenTracker,
startupTimer));
}

@VisibleForTesting
ActivityTracerCache(Function<Activity, ActivityTracer> tracerFactory) {
this.tracerFactory = tracerFactory;
}

ActivityTracer addEvent(Activity activity, String eventName) {
return getTracer(activity).addEvent(eventName);
}

ActivityTracer startSpanIfNoneInProgress(Activity activity, String spanName) {
return getTracer(activity).startSpanIfNoneInProgress(spanName);
}

ActivityTracer initiateRestartSpanIfNecessary(Activity activity) {
boolean isMultiActivityApp = tracersByActivityClassName.size() > 1;
return getTracer(activity).initiateRestartSpanIfNecessary(isMultiActivityApp);
}

ActivityTracer startActivityCreation(Activity activity) {
return getTracer(activity).startActivityCreation();
}

private ActivityTracer getTracer(Activity activity) {
ActivityTracer activityTracer =
tracersByActivityClassName.get(activity.getClass().getName());
if (activityTracer == null) {
activityTracer = tracerFactory.apply(activity);
tracersByActivityClassName.put(activity.getClass().getName(), activityTracer);
}
return activityTracer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@

package com.splunk.rum;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.rum.internal.DefaultingActivityLifecycleCallbacks;
import io.opentelemetry.sdk.common.Clock;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -60,8 +65,19 @@ Span start(Tracer tracer) {
return appStart;
}

/** Creates a lifecycle listener that starts the UI init when an activity is created. */
Application.ActivityLifecycleCallbacks createLifecycleCallback() {
return new DefaultingActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(
@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
startUiInit();
}
};
}

/** Called when Activity is created. */
void startUiInit() {
private void startUiInit() {
if (uiInitStarted || isStartedFromBackground) {
return;
}
Expand Down
Loading

0 comments on commit 6d38904

Please sign in to comment.