45

I was pretty excited to see how easy it is to set up Google Analytics with my app, but the lack of documentation has me sitting with a few questions. The only information that I can find is right from the documentation here, which only looks at reporting PageViews and Events from one Activity. I want to report PageViews and Events across multiple Activities in my app.

Right now in the onCreate() of all of my activities, I am calling:

    tracker = GoogleAnalyticsTracker.getInstance();
    tracker.start("UA-xxxxxxxxx", this);

And in the onDestroy() of all of my activities:

    tracker.stop();

I then track PageViews and Events as needed, and Dispatch them along with another HTTP request I am performing. But I'm not so sure this is the best way. Should I be calling start() and stop() in each activity, or should I only call start() and stop() in my main launcher activity?

Aurora
  • 4,384
  • 3
  • 34
  • 44

7 Answers7

79

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;
    }
}
emmby
  • 99,783
  • 65
  • 191
  • 249
  • Thanks for letting me finally close this question - this kind of full explanation is exactly what I needed. – Aurora Jun 06 '11 at 16:34
  • 2
    Yeah, reading that post made me go through and completely rewrite how I use the Analytics api in my app. Kudos, though I secretly loathe you for bursting my artificial visit count bubble. – Pedantic Jun 07 '11 at 04:09
  • 4
    Your reference counting solution works fine if the user always goes all the way back to close the app. However, backgrounding it and coming back to it would still count as one visit. If you'd like to track those as two separate visits (which I did), you can exploit the fact that when going A->B B's onResume is called before A's onStop. Therefore, increment the counter in onResume, decrement in onStop. When the counter is 0, your app is no longer visible. – Delyan Jul 29 '11 at 09:46
  • Should Activities that need tracking implemented extend GoogleAnalyticsActivity so that this code is called when super.onCreate(), super.onResume(), and super.onStop() are called? – dbaugh Aug 31 '11 at 17:42
  • @Delyan Your idea is fine however be aware - when using TabHost with multiple activities/tabs, your counting solution will give wrong results - instead of OnStop, OnPause will be called. Navigating between tabs will increase counter without decreasing it. – jki Apr 23 '12 at 08:49
  • 5
    For anybody else that comes across this - the above solution works really well, but both the `start` and `stop` methods of `GoogleAnalyticsTracker` are deprecated (Eclipse will throw warnings) - just replace with `startNewSession` and `stopSession` in `GoogleAnalyticsSessionManager.incrementActivityCount`, respectively. – momo Jul 14 '12 at 16:25
  • This answer wants me to be able to fav them. Thanks! Works like charm. – Stefan Hoth Oct 04 '12 at 00:08
  • Looks like EasyTracker is no longer available in Google Analytics v4. I do not see it in the Google Play Services library project. – Jo Jo Jul 21 '14 at 22:24
  • Why not keep a single instance of the tracker in the application level and use it everywhere and destroy it there as well? – CodeMonkey May 13 '15 at 10:51
17

The SDK now has a external library which takes care of all of this. Its called EasyTracker. You can just import it and extend the provided Activity or ListActivity, create a string resource with your code and you are done.

sebastianf182
  • 9,844
  • 3
  • 34
  • 66
  • 1
    Awesome! For those interested, here is the blog post: http://analytics.blogspot.com/2011/12/google-analytics-enhancements-for.html – Ryan R Jan 04 '12 at 18:21
5

The tracker will only track the activity where it's executed. So, why don't you subclass an Activity which start it every time on onCreate:

public class GAnalyticsActivity extends Activity{

    public void onCreate(Bundle icicle){
        super.onCreate(icile);
        tracker = GoogleAnalyticsTracker.getInstance();
        tracker.start("UA-xxxxxxxxx", this);
    }

    // same for on destroy
}

Then, you extends that class for every activity you use:

public class YourActivity extends GAnalyticsActivity{
    public void onCreate(Bundle icicle){
        super.onCreate(icile);
        // whatever you do here you can be sure 
        // that the tracker has already been started
    }
}
Cristian
  • 198,401
  • 62
  • 356
  • 264
  • 1
    This is a snazzy idea, however one of my activities is extending ListActivity, while the others are just extending Activity. – Aurora Jul 09 '10 at 21:49
  • If you did want to go with this sort of approach, it should be fairly easy to replace the ListActivity with a standard activity. Its basically just an Activity with a ListView. Otherwise, just calling start and stop manually on the activities you are interested in should be fine. – Cheryl Simon Jul 09 '10 at 22:28
  • 1
    I guess a better extension to my question is, what does start() and stop() do? One of my guesses is that they deal with opening and closing the database that the tracker saves the events and pageviews before sending them, but if they are doing more intensive things than that, do I really want to be calling start() and stop() all the time? I'm curious as to what other people who are using Analytics in their android app are doing. – Aurora Jul 09 '10 at 22:35
  • Answering my secondary question: via the ReadMe.txt in the download for the GoogleAnalytics Jar file: "An Analytics tracking 'visit' is defined as the events and page views generated between calls to start() and stop() on the GoogleAnalyticsTracker. Every time start() is called a new visit is started. You should call stop() on GoogleAnalyticsTracker when your application is closing." So it looks like if you call start() and stop() only in your main launcher activity, that means each launch is a 'visit'. Multiple calls to start() stop() mean multiple 'visits' per app launch. – Aurora Jul 09 '10 at 22:54
  • 3
    Now that time has passed, I can see the results in Google Analytics. I am only calling start() and stop() in my main activity. Each launch of the application represents a 'visit'. If you want a unique 'visit' on each Activity, then the solution Cristian C. provides would probably work. – Aurora Jul 12 '10 at 17:58
  • Thanks for sharing your experience :D – Cristian Jul 13 '10 at 02:57
1

You will need something like this: http://mufumbo.wordpress.com/2011/06/13/google-analytics-lags-on-android-how-to-make-it-responsive/

That's on the previous version and used to work very well. Now I'm in the same struggle as you, as V2 doesn't seems to be very consistent.

Rafael Sanches
  • 1,823
  • 21
  • 28
1

The approach I am using is to use a Bound Service (I happen to be using one already so was spared the creation of extra boiler plate code.)

A Bound Service will only last as long as there are Activities bound to it. All the activities in my app bind to this service, so it lasts only as long as the user is actively using my application - therefore very much a real 'session'.

I start the tracker with a singleton instance of Application which I have extended and added a static getInstance() method to retrieve the instance:

// Non-relevant code removed

public IBinder onBind(Intent intent) {
    tracker = GoogleAnalyticsTracker.getInstance();
    tracker.startNewSession(PROPERTY_ID, MyApp.getInstance());
}


public boolean onUnbind(Intent intent) {
    tracker.stopSession();
}

See: http://developer.android.com/guide/topics/fundamentals/bound-services.html

evaneus
  • 759
  • 6
  • 9
1

I did a time based split between visits in my app, working like this:

I've build a wrapper singleton Tracker object for the GoogleAnalyticsTracker where i keep the last time something got tracked. If that time's more then x seconds i treat it as a new visit.

Of course this is only useful if you track everything in your app, and may not be the best solution in every situation, works well for my app though.

It only supports trackPageView, but setCustomVar and trackEvent should be easily implemented..

Anywhere you need to track something just add the line:

    Tracker.getInstance(getApplicationContext()).trackPageView("/HelloPage");

I usually do it in the onResume of an activity

Tracker gist

fasmide
  • 149
  • 1
  • 11
  • Thanks - this is an old question of mine, but I like how it's becoming a repository for strategies on tackling this issue. :D – Aurora Sep 27 '11 at 19:45
0

I wonder if this is something that could be done using AOP.

Android can only use compile-time AOP methods so maybe something like AspectJ?

There's a little more info on using AspectJ in Android in this thread. The main issue being that you would still need to declare on classes you own.

Community
  • 1
  • 1
Ben Neill
  • 2,811
  • 2
  • 22
  • 20