Skip to content

Commit

Permalink
Use AndroidX lifecycle functions for App Fore/Backgrounded (#667)
Browse files Browse the repository at this point in the history
* init changes

* fix tests

* run formatter

* update build.gradle

* add test for unregister lifecycle methods
  • Loading branch information
prayansh committed Jun 8, 2020
1 parent a7bbb7d commit 37226c1
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 117 deletions.
3 changes: 3 additions & 0 deletions analytics/build.gradle
Expand Up @@ -6,6 +6,9 @@ apply from: rootProject.file('gradle/android.gradle')
dependencies {
api rootProject.ext.deps.supportAnnotations

implementation 'androidx.lifecycle:lifecycle-process:2.2.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0'

testImplementation 'junit:junit:4.12'
testImplementation('org.robolectric:robolectric:3.5') {
exclude group: 'commons-logging', module: 'commons-logging'
Expand Down
16 changes: 12 additions & 4 deletions analytics/src/main/java/com/segment/analytics/Analytics.java
Expand Up @@ -48,6 +48,8 @@
import android.os.Message;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.segment.analytics.integrations.AliasPayload;
import com.segment.analytics.integrations.BasePayload;
import com.segment.analytics.integrations.GroupPayload;
Expand Down Expand Up @@ -132,7 +134,8 @@ public void handleMessage(Message msg) {
final Cartographer cartographer;
private final ProjectSettings.Cache projectSettingsCache;
final Crypto crypto;
@Private final Application.ActivityLifecycleCallbacks activityLifecycleCallback;
@Private final AnalyticsActivityLifecycleCallbacks activityLifecycleCallback;
@Private final Lifecycle lifecycle;
ProjectSettings projectSettings; // todo: make final (non-final for testing).
@Private final String writeKey;
final int flushQueueSize;
Expand Down Expand Up @@ -229,7 +232,8 @@ public static void setSingletonInstance(Analytics analytics) {
Crypto crypto,
@NonNull List<Middleware> sourceMiddleware,
@NonNull Map<String, List<Middleware>> destinationMiddleware,
@NonNull final ValueMap defaultProjectSettings) {
@NonNull final ValueMap defaultProjectSettings,
@NonNull Lifecycle lifecycle) {
this.application = application;
this.networkExecutor = networkExecutor;
this.stats = stats;
Expand All @@ -251,6 +255,7 @@ public static void setSingletonInstance(Analytics analytics) {
this.crypto = crypto;
this.sourceMiddleware = sourceMiddleware;
this.destinationMiddleware = destinationMiddleware;
this.lifecycle = lifecycle;

namespaceSharedPreferences();

Expand Down Expand Up @@ -315,6 +320,7 @@ public void run() {
.build();

application.registerActivityLifecycleCallbacks(activityLifecycleCallback);
lifecycle.addObserver(activityLifecycleCallback);
}

@Private
Expand Down Expand Up @@ -965,6 +971,7 @@ public void shutdown() {
return;
}
application.unregisterActivityLifecycleCallbacks(activityLifecycleCallback);
lifecycle.removeObserver(activityLifecycleCallback);
// Only supplied by us for testing, so it's ok to shut it down. If we were to make this public,
// we'll have to add a check similar to that of AnalyticsNetworkExecutorService below.
analyticsExecutor.shutdown();
Expand Down Expand Up @@ -1375,7 +1382,7 @@ public Analytics build() {
if (executor == null) {
executor = Executors.newSingleThreadExecutor();
}

Lifecycle lifecycle = ProcessLifecycleOwner.get().getLifecycle();
return new Analytics(
application,
networkExecutor,
Expand All @@ -1402,7 +1409,8 @@ public Analytics build() {
crypto,
srcMiddleware,
destMiddleware,
defaultProjectSettings);
defaultProjectSettings,
lifecycle);
}
}

Expand Down
Expand Up @@ -29,11 +29,15 @@
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

class AnalyticsActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
class AnalyticsActivityLifecycleCallbacks
implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver {
private Analytics analytics;
private ExecutorService analyticsExecutor;
private Boolean shouldTrackApplicationLifecycleEvents;
Expand All @@ -44,7 +48,6 @@ class AnalyticsActivityLifecycleCallbacks implements Application.ActivityLifecyc

private AtomicBoolean trackedApplicationLifecycleEvents;
private AtomicInteger numberOfActivities;
private AtomicBoolean isChangingActivityConfigurations;
private AtomicBoolean firstLaunch;

private AnalyticsActivityLifecycleCallbacks(
Expand All @@ -57,7 +60,6 @@ private AnalyticsActivityLifecycleCallbacks(
PackageInfo packageInfo) {
this.trackedApplicationLifecycleEvents = new AtomicBoolean(false);
this.numberOfActivities = new AtomicInteger(1);
this.isChangingActivityConfigurations = new AtomicBoolean(false);
this.firstLaunch = new AtomicBoolean(false);
this.analytics = analytics;
this.analyticsExecutor = analyticsExecutor;
Expand All @@ -68,10 +70,29 @@ private AnalyticsActivityLifecycleCallbacks(
this.packageInfo = packageInfo;
}

@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
analytics.runOnMainThread(IntegrationOperation.onActivityCreated(activity, bundle));
public void onStop(@NonNull LifecycleOwner owner) {
// App in background
if (shouldTrackApplicationLifecycleEvents) {
analytics.track("Application Backgrounded");
}
}

public void onStart(@NonNull LifecycleOwner owner) {
// App in foreground
if (shouldTrackApplicationLifecycleEvents) {
Properties properties = new Properties();
if (firstLaunch.get()) {
properties
.putValue("version", packageInfo.versionName)
.putValue("build", String.valueOf(packageInfo.versionCode));
}
properties.putValue("from_background", !firstLaunch.getAndSet(false));
analytics.track("Application Opened", properties);
}
}

public void onCreate(@NonNull LifecycleOwner owner) {
// App created
if (!trackedApplicationLifecycleEvents.getAndSet(true)
&& shouldTrackApplicationLifecycleEvents) {
numberOfActivities.set(0);
Expand All @@ -87,28 +108,35 @@ public void run() {
}
});
}
}
}

if (!trackDeepLinks) {
return;
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
analytics.runOnMainThread(IntegrationOperation.onActivityCreated(activity, bundle));

Intent intent = activity.getIntent();
if (intent == null || intent.getData() == null) {
return;
}
if (trackDeepLinks) {
trackDeepLink(activity);
}
}

Properties properties = new Properties();
Uri uri = intent.getData();
for (String parameter : uri.getQueryParameterNames()) {
String value = uri.getQueryParameter(parameter);
if (value != null && !value.trim().isEmpty()) {
properties.put(parameter, value);
}
}
private void trackDeepLink(Activity activity) {
Intent intent = activity.getIntent();
if (intent == null || intent.getData() == null) {
return;
}

properties.put("url", uri.toString());
analytics.track("Deep Link Opened", properties);
Properties properties = new Properties();
Uri uri = intent.getData();
for (String parameter : uri.getQueryParameterNames()) {
String value = uri.getQueryParameter(parameter);
if (value != null && !value.trim().isEmpty()) {
properties.put(parameter, value);
}
}

properties.put("url", uri.toString());
analytics.track("Deep Link Opened", properties);
}

@Override
Expand All @@ -122,20 +150,6 @@ public void onActivityStarted(Activity activity) {
@Override
public void onActivityResumed(Activity activity) {
analytics.runOnMainThread(IntegrationOperation.onActivityResumed(activity));

if (shouldTrackApplicationLifecycleEvents
&& numberOfActivities.incrementAndGet() == 1
&& !isChangingActivityConfigurations.get()) {

Properties properties = new Properties();
if (firstLaunch.get()) {
properties
.putValue("version", packageInfo.versionName)
.putValue("build", String.valueOf(packageInfo.versionCode));
}
properties.putValue("from_background", !firstLaunch.getAndSet(false));
analytics.track("Application Opened", properties);
}
}

@Override
Expand All @@ -146,13 +160,6 @@ public void onActivityPaused(Activity activity) {
@Override
public void onActivityStopped(Activity activity) {
analytics.runOnMainThread(IntegrationOperation.onActivityStopped(activity));

isChangingActivityConfigurations.set(activity.isChangingConfigurations());
if (shouldTrackApplicationLifecycleEvents
&& numberOfActivities.decrementAndGet() == 0
&& !isChangingActivityConfigurations.get()) {
analytics.track("Application Backgrounded");
}
}

@Override
Expand Down

0 comments on commit 37226c1

Please sign in to comment.