Google Analytics in Android App - Dealing with Multiple Activities

Google Analytics in Android app - dealing with multiple activities

The problem with calling start()/stop() in every activity (as suggested by Christian) is that it results in a new "visit" for every activity your user navigates to. If this is okay for your usage, then that's fine, however, it's not the way most people expect visits to work. For example, this would make comparing android numbers to web or iphone numbers very difficult, since a "visit" on the web and iphone maps to a session, not a page/activity.

The problem with calling start()/stop() in your Application is that it results in unexpectedly long visits, since Android makes no guarantees to terminate the application after your last activity closes. In addition, if your app does anything with notifications or services, these background tasks can start up your app and result in "phantom" visits. UPDATE: stefano properly points out that onTerminate() is never called on a real device, so there's no obvious place to put the call to stop().

The problem with calling start()/stop() in a single "main" activity (as suggested by Aurora) is that there's no guarantee that the activity will stick around for the duration that your user is using your app. If the "main" activity is destroyed (say to free up memory), your subsequent attempts to write events to GA in other activities will fail because the session has been stopped.

In addition, there's a bug in Google Analytics up through at least version 1.2 that causes it to keep a strong reference to the context you pass in to start(), preventing it from ever getting garbage collected after its destroyed. Depending on the size of your context, this can be a sizable memory leak.

The memory leak is easy enough to fix, it can be solved by calling start() using the Application instead of the activity instance itself. The docs should probably be updated to reflect this.

eg. from inside your Activity:

// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", getApplication() );

instead of

// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", this ); // BAD

Regarding when to call start()/stop(), you can implement a sort of manual reference counting, incrementing a count for each call to Activity.onCreate() and decrementing for each onDestroy(), then calling GoogleAnalyticsTracker.stop() when the count reaches zero.

The new EasyTracker library from Google will take care of this for you.

Alternately, if you can't subclass the EasyTracker activities, you can implement this manually yourself in your own activity base class:

public abstract class GoogleAnalyticsActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance(getApplication()).incrementActivityCount();
}

@Override
protected void onResume() {
super.onResume();

// Example of how to track a pageview event
GoogleAnalyticsTracker.getInstance().trackPageView(getClass().getSimpleName());
}

@Override
protected void onDestroy() {
super.onDestroy();

// Purge analytics so they don't hold references to this activity
GoogleAnalyticsTracker.getInstance().dispatch();

// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance().decrementActivityCount();
}

}

public class GoogleAnalyticsSessionManager {
protected static GoogleAnalyticsSessionManager INSTANCE;

protected int activityCount = 0;
protected Integer dispatchIntervalSecs;
protected String apiKey;
protected Context context;

/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, Application context ) {
this.apiKey = apiKey;
this.context = context;
}

/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, int dispatchIntervalSecs, Application context ) {
this.apiKey = apiKey;
this.dispatchIntervalSecs = dispatchIntervalSecs;
this.context = context;
}

/**
* This should be called once in onCreate() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void incrementActivityCount() {
if( activityCount==0 )
if( dispatchIntervalSecs==null )
GoogleAnalyticsTracker.getInstance().start(apiKey,context);
else
GoogleAnalyticsTracker.getInstance().start(apiKey,dispatchIntervalSecs,context);

++activityCount;
}

/**
* This should be called once in onDestrkg() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void decrementActivityCount() {
activityCount = Math.max(activityCount-1, 0);

if( activityCount==0 )
GoogleAnalyticsTracker.getInstance().stop();
}

/**
* Get or create an instance of GoogleAnalyticsSessionManager
*/
public static GoogleAnalyticsSessionManager getInstance( Application application ) {
if( INSTANCE == null )
INSTANCE = new GoogleAnalyticsSessionManager( ... ,application);
return INSTANCE;
}

/**
* Only call this if you're sure an instance has been previously created using #getInstance(Application)
*/
public static GoogleAnalyticsSessionManager getInstance() {
return INSTANCE;
}
}

Android - Google Analytics with Multiple Activities

Turns out the solution linked above does work well, and the deprecated methods can be replaced as per my comment here.

Google Analytics for Android using multiple Activities

After studying the lifecycle of an Activity, I came to the following conclusion.

When switching from an Activity A to another Activity B, the onStop method of A is called AFTER the onStart method of B. What I then did was increasing a reference counter every time the (static) tracker is accessed in an onStart method. In the onStop method, I would first check whether the reference counter was 0, and stop the tracker if it was. At the end of the onStop method, I would decrease the reference counter.

This seems to be working quite well at this moment, and should also work when the application has multiple Activities that can act as an entry point.

Google analytic for multiple Android Activities

You should put those code inside Application object instead of configuring in each activity.

Example:

    public class AnalyticsSampleApp extends Application {

// The following line should be changed to include the correct property id.
private static final String PROPERTY_ID = "UA-XXXXX-Y";

public static int GENERAL_TRACKER = 0;

public enum TrackerName {
APP_TRACKER, // Tracker used only in this app.
GLOBAL_TRACKER, // Tracker used by all the apps from a company. eg: roll-up tracking.
ECOMMERCE_TRACKER, // Tracker used by all ecommerce transactions from a company.
}

HashMap<TrackerName, Tracker> mTrackers = new HashMap<TrackerName, Tracker>();

public AnalyticsSampleApp() {
super();
}

synchronized Tracker getTracker(TrackerName trackerId) {
if (!mTrackers.containsKey(trackerId)) {

GoogleAnalytics analytics = GoogleAnalytics.getInstance(this);
analytics.getLogger().setLogLevel(Logger.LogLevel.VERBOSE);
Tracker t = (trackerId == TrackerName.APP_TRACKER) ? analytics.newTracker(PROPERTY_ID)
: (trackerId == TrackerName.GLOBAL_TRACKER) ? analytics.newTracker(
R.xml.global_tracker)
: analytics.newTracker(R.xml.ecommerce_tracker);
t.enableAdvertisingIdCollection(true);
mTrackers.put(trackerId, t);
}
return mTrackers.get(trackerId);
}
}

Reference:

Kindly checkout the sample application provided. You may find it within your android sdk directory at following path:

<android-sdk-directory>/extras/google/google_play_services/analytics/mobileplayground

Documentation:

Google Analytics v4

How to implement Google Analytics V3 on multiple Activities?

I decided to build a test app and see what I could come up with for you. My test app consists of 3 total activities: Splash, Main Activity, and Secondary Activity.

Splash is set as the launcher & and main in the manifest, other 2 activities have no intent filters.

Just to ensure that this didn't necessarily hinge upon user activity but rather kicked off per activity creation, Splash creates a runnable that lasts 4 seconds before loading the first activity and finishing:

finish();
startActivity(new Intent(Splash.this, MainActivity.class));

The first activity loads and is just a blank screen with a button. On click, an intent is created for the secondary activity.

All three of these classes extend the TrackedActivity class, which uses Google's sample code.

Sure enough, I was seeing nothing in the real-time dashboard except com.test.testproject.SecondaryActivity.

Neither splash nor main were showing up.

Next step, turn on verbose logging in your analytics.xml file:

<!-- Enable Verbose Logging -->
<string name="ga_logLevel">verbose</string>

I did that and took a look at the log output, and was stumped to see that all 3 of my activities appeared to be logging correctly.

10-06 16:47:56.194: V/GAV3(7704): GET
/collect?v=1&ul=en-us&t=appview&ht=1381092218141&sr=720x1280&an=TestProject&tid=UA-xxxxxxxx&aid=com.test.testproject&cid=f0a09e69-67e8-4ac0-b4b7-748be827efa7&av=1.0&_u=.r8&_v=ma3.0.0&cd=com.test.testproject.MainActivity&qt=258067&z=25
HTTP/1.1

10-06 16:47:56.234: V/GAV3(7704): GET
/collect?v=1&ul=en-us&t=appview&ht=1381092220248&sr=720x1280&an=TestProject&tid=UA-xxxxxxxx&aid=com.test.testproject&cid=f0a09e69-67e8-4ac0-b4b7-748be827efa7&av=1.0&_u=.98&_v=ma3.0.0&cd=com.test.testproject.SecondaryActivity&qt=255994&z=26
HTTP/1.1

Of note, the hit stores were out of order:

10-06 16:51:05.008: V/GAV3(7704): Thread[GAThread,5,main]: Sending hit
to store PATH: https: PARAMS: v=1, ul=en-us, t=appview,
ht=1381092665018, sr=720x1280, an=TestProject, tid=UA-xxxxxxx,
aid=com.test.testproject, cid=f0a09e69-67e8-4ac0-b4b7-748be827efa7,
av=1.0, _u=.r98, cd=com.test.testproject.SecondaryActivity,

10-06 16:51:06.970: V/GAV3(7704): Thread[GAThread,5,main]: Sending hit
to store PATH: https: PARAMS: v=1, ul=en-us, t=appview,
ht=1381092666979, sr=720x1280, an=TestProject, tid=UA-xxxxxxx,
aid=com.test.testproject, cid=f0a09e69-67e8-4ac0-b4b7-748be827efa7,
av=1.0, _u=.98, cd=com.test.testproject.MainActivity,

Checking the real-time dashboard I continued to only see one activity, like so:
Checking the real-time dashboard I continued to only see one activity...

And then I noticed in the top right corner it had defaulted to filtering my resultset to only the 2nd activity!

Clearing the filter then showed me all three activities, like so:
All 3 activities shown in real-time

Stupid as it might be, ensure that google's dashboard is defaulting the filtering for you, I barely noticed it in the first place.

And if that doesn't help, post up a code sample so I can take a look at what's going on. If you want, I'm happy to bundle up this sample app so you can drop in your UA ID and try it out yourself.

Hope that helps.

Passing a service object from one activity to another

I did find this from google: http://developer.android.com/guide/faq/framework.html#3 . Creating a singleton class might be the way to go here. In case anyone else finds it useful



Related Topics



Leave a reply



Submit