Google Analytics for mobile apps in Unity

Google Analytics + Unity

There are several solutions out there for integrating Google Analytics into Unity apps. However, as far as I can tell they all wrap the good old web tracking service. The guys at Google have long ago tailored a more suitable solution for mobile platforms: Mobile App Analytics. It provides a few mobile-only features (e.g.: user engagement, crashes and exceptions, device info) and has the added benefit of being official. Google have released SDKs for Android and iOS. In this post I will demonstrate how to integrate the Android SDK v2 into Unity apps. Be patient, iOS is on the road map.

Setup your Analytics Account

First of all you need to have a Google Analytics account. To use Mobile App Analytics you also need to add to your account an app tracking property and a profile. When you’re done you shall be the proud owner of a Tracking ID. It should look something like this: UA-123456-7.

Create an Android Plugin

I already covered the subject of creating an Android plugin in another post. I will expand on it to keep this post short.

Get the Analytics SDK

Download the Android SDK. (At the time of writing the version was 2.0 beta 5 and the download page warned that the SDKs are under “active development”.)  After downloading the ZIP file, extract libGoogleAnalyticsV2.jar from it (or whatever the JAR file is called) and add it to your Eclipse project.

This includes copying it to the libs directory and adding it to the build path. You may not see the JAR file in Eclipse until you refresh (F5). To add the JAR file to the build path right click on it and select Build Path => Add to Build Path.

MainActivity.java

We will subclass UnityPlayerActivity and use the EasyTracker class:

package com.companyname.appname;

import android.util.Log;

import com.google.analytics.tracking.android.EasyTracker;
import com.unity3d.player.UnityPlayerActivity;

public class MainActivity extends UnityPlayerActivity {
    @Override
    protected void onStart() {
        super.onStart();
        Log.d("Unity", "Starting Analytics activity");
        EasyTracker.getInstance().activityStart(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d("Unity", "Stopping Analytics activity");
        EasyTracker.getInstance().activityStop(this);
    }

    public void sendView(String screenName) {
        Log.d("Unity", "Sending Analytics view: " + screenName);
        EasyTracker.getTracker().sendView(screenName);
    }

    public void sendEvent(String category, String action, String label, Long value) {
        // value is optional
        Log.d("Unity", "Sending Analytics event: " + category + ", " + action + ", " + label + ", " + value);
        EasyTracker.getTracker().sendEvent(category, action, label, value);
    }

    public void sendTiming(String category, long intervalInMilliseconds, String name, String label) {
        // name and label are optional
        Log.d("Unity", "Sending Analytics timing: " + category + ", " + intervalInMilliseconds + ", " + name + ", " + label);
        EasyTracker.getTracker().sendTiming(category, intervalInMilliseconds, name, label);
    }
}

The calls to activityStart and activityStop are fundamental. They frame the session, allowing tracking of users, demographics and uncaught exceptions. Calling activityStart also sets the context for later sendXxxx calls to work.

The sendViewsendEvent and sendTiming methods allow manual sending of specific app information (screens, events and timings appropriately). These will be called by Unity scripts. By the way, don’t use trackXxxx methods – they’re deprecated.

There’s more stuff you can track which I haven’t covered here such as: campaigns, caught exceptions and in-app payments. See the dev guide for information on those.

You may want to know that EasyTracker is a wrapper for two other classes: GoogleAnalytics and Tracker. It makes life easier, but leaves out a few advanced features.

A note about onStop: When I pressed the Home button, my app was paused and onStop was called. When I called Application.Quit, it wasn’t. I don’t think it matters, but if it does to you feel free to read about session management.

analytics.xml

This is the configuration file for EasyTracker. Here we’ll setup our app’s Tracking ID as well as some other parameters:

<!--?xml version="1.0" encoding="utf-8" ?-->

    <!--Tracking ID-->
    UA-123456-7

    <!--Enable automatic activity tracking-->
    true

    <!--Enable automatic exception tracking-->
    true

    <!--Enable debug mode-->
    true

    <!--Set dispatch period in seconds-->
    120

Some of these parameters are self explanatory. And you can read about them and others here. A few notes though:

  • ga_debug – Enables writing of debug information to the log. The default is false (disabled). This may be useful for debugging, but be prepared to see error messages even when everything’s working smoothly (examples below.)  The output will be printed to logcat using the tag GAV2. For example, you can read both Unity’s logs and Analytics with the following command:
    adb logcat Unity:V GAV2:V *:S
  • ga_dispatchPeriod – The dispatch period in seconds. The default is 1800 (30 minutes). Yup, that’s a lot! It’s only 2 minutes on iOS. Feel free to reduce it. Setting it to 0 (zero) is useful for debugging. (But I suggest you don’t leave it that way in release versions.) Even then you can only find real-time information in the “Real-time” section in Analytics dashboard. Everything else is updated daily.
  • ga_appVersion – Your app version. No, this is not a mistake – it’s not used in the file above. No reason to! It defaults to the version found in the package, which in turn is determined by the bundle version in Unity’s player settings.

The analytics.xml file should generally be placed in the res/values directory in the project. However, the location doesn’t matter much until we copy it into the Unity project later. (It will reside in Assets/Plugins/Android/res/values.)

The Unity App

In your Unity app you’d probably want to call sendXxxx methods to track user engagement. The following Boo code calls the Java methods:

ifdef UNITY_ANDROID:
    static def Call(methodName as string, *args):
        jc = AndroidJavaClass("com.unity3d.player.UnityPlayer")
        jo = jc.GetStatic[of AndroidJavaObject]("currentActivity")
        jo.Call(methodName, *args)

    static def SendView(screenName as string):
        Call("sendView", screenName)

    static def SendEvent(category as string, action as string, label as string, value as long):
        joValue = AndroidJavaObject("java.lang.Long", value)
        Call("sendEvent", category, action, label, joValue)

    static def SendEvent(category as string, action as string, label as string):
        Call("sendEvent", category, action, label, null)

    static def SendTiming(category as string, intervalInMilliseconds as long, name as string, label as string):
        Call("sendTiming", category, intervalInMilliseconds, name, label)

The SendEvent method’s value parameter is optional. So are SendTiming method’s name and label parameters.

Adding the Plugin

To integrate the plugin we created into Unity:

  1. Copy the JAR file we created earlier to Assets/Plugins/Android.
  2. Copy libGoogleAnalyticsV2.jar to Assets/Plugins/Android.
  3. Copy analytics.xml to Assets/Plugins/Android/res/values. Create the subdirectories if needed.
  4. Edit your AndroidManifest.xml file in Assets/Plugins/Android and add the INTERNET and ACCESS_NETWORK_STATE permissions (see below).
AndroidManifest.xml

See this post for more information about this file. Here I will only mention the additions required by Google Analytics:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  .....
</manifest>

Logcat

As I mentioned earlier it make take a while before you can see results online. So how can you tell if it’s working? Assuming you enabled debug mode (“ga_debug”) you should be able to get some feedback from the logs. However, some messages are quite confusing. Here are a few examples I encountered.

False error #1: Need to call initialize() and be in fallback mode to start dispatch

W/GAV2 ( 4211): Thread[main,5,main]: Need to call initialize() and be in fallback mode to start dispatch.
I/GAV2 ( 4211): Thread[main,5,main]: ExceptionReporter created, original handler is com.unity3d.player.ab

This error may appear when you call activityStart from your onStart method. I ignore it.

False error #2: connect: bindService returned false for Intent

I/GAV2 ( 3680): Thread[GAThread,5,main]: connecting to Analytics service
I/GAV2 ( 3680): Thread[GAThread,5,main]: connect: bindService returned false for Intent { act=com.google.android.gms.analytic
s.service.START (has extras) }
W/GAV2 ( 3680): Thread[GAThread,5,main]: Service unavailable (code=1), will retry.
I/GAV2 ( 3680): Thread[GAThread,5,main]: No campaign data found.
I/GAV2 ( 3680): Thread[GAThread,5,main]: putHit called

And then some:

I/GAV2 ( 3680): Thread[Service Reconnect,5,main]: connecting to Analytics service
I/GAV2 ( 3680): Thread[Service Reconnect,5,main]: connect: bindService returned false for Intent { act=com.google.android.gms
.analytics.service.START (has extras) }
W/GAV2 ( 3680): Thread[Service Reconnect,5,main]: Service unavailable (code=1), using local store.
I/GAV2 ( 3680): Thread[Service Reconnect,5,main]: falling back to local store
I/GAV2 ( 3680): Thread[GAThread,5,main]: Sending hit to store

It appears when the Analytics service tries to dispatch the data. This may be a few seconds after the app started, or maybe later if ga_dispatchPeriod is high. Anyway, it seems to happen consistently for me. It’s fine to ignore it, assuming it’s followed by good stuff.

Good stuff

Hopefully you’ll see something like this:

V/GAV2 ( 4211): Thread[GAThread,5,main]: dispatch running...
I/GAV2 ( 4211): Thread[GAThread,5,main]: User-Agent: GoogleAnalytics/2.0 (Linux; U; Android 4.0.4; en-us; GT-P7500 Build/IMM76D)
I/GAV2 ( 4211): Host: ssl.google-analytics.com
I/GAV2 ( 4211): GET /collect?................. HTTP/1.1
V/GAV2 ( 4211): Thread[GAThread,5,main]: sent 1 of 1 hits

Hits sent? You’re good to go.

A word on legality

The Google Analytics Terms of Service prohibit sending of any personally identifiable information (PII) to Google Analytics servers. So don’t.

The Duplicator duplicates

Duplicator Plugin

Just popped in to say thanks to the guys at Life in the Grid for their awesome WordPress plugin, Duplicator.

I had to switch to another host as my previous one, 8Tar, was crumbling down to the ground. (I guess that’s what you get for $0…) I installed the Duplicator plugin and created a backup in seconds. The next day 8Tar exhaled its final breath. After finding a new host (paid this time) I uploaded the backup I created and activated it. It just worked. Everything was restored in one go: database and files (posts, images, plugins, permalinks, users) – the whole package.

When thanks are due…