I was working on a mobile app analytics system recent months, how
well the system works relies on the app usage data captured on the
users' devices.
This post is about technically how I did it in both iOS and Android
operating systems.
Making Thread
When a user opens an app, an open session event needs to be captured,
when a user tap the home button, app transitions to the background, the
session needs to be marked as closed, when a user view an important
page, or click a fancy button, counters need to be increased. Those
needs must be handled in background threads since I don't want my app UI
freezing by some time-consuming "needs".
In iOS, Grand
Central Dispatch (GCD) is amazing, create a serial dispatch queue,
and a background thread is alive.
If these tracking methods are provided as an SDK, app developers may
not want to add those tracking methods one by one in their code base.
So, the interfaces of this kind of SDK should be minimum. There are
things can be done automatically.
As in iOS, we could bind method calls to system events, for example,
when app transitions to background or foreground.
I added below code to main method in SDK, app developers only need to
call this main method in their code base, believe me, they will love
your SDK.
In Android, sadly, I couldn't find anything similar. It does provide
system event bindings in Application,
but only onCreate and onTerminate, useless for
my needs. So I gave up, and looked for alternatives in Activity,
maybe Application.ActivityLifecycleCallbacks
could be helpful, but API level (14) is too high for me.
Sending Data
Process
The data captured are on the users' devices, they should be
transferred to server for analysis.
Although the data is anonymous, it's about users' behavior,
encryption is needed to keep it safe during transfer.
The target devices often connect to cellular network, it's much
expensive than WiFi, compression is needed to keep the data size as
small as possible.
For encryption, I implemented two ways, AES
and RSA, RSA is more
secure, but AES is easy to implement and use. Below are AES
implementation in iOS and Android.
Why I use dispatch group is because I don't want the uploading task
block the tracking tasks, so the task is dispatched to another queue, in
many circumstances, we have to wait all the tasks in tracking thread and
uploading thread are finished, then do somethings.
For example, if I want to send all the data captured every time app
transitions to background, five seconds is not enough sometimes, app
will crash and it's a bad user experience. We can add a long-running
task by using beginBackgroundTaskWithExpirationHandler,
when all the tasks in this dispatch group are finished, notify the
system our long-running task is finished.
In Android, as I did before, create another handler
UploadHandler in SessionHandler for the
uploading tasks, HttpClient can be used to send the data to
server via HTTP.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
final HttpClient httpClient = new DefaultHttpClient(); final HttpPost httpPost = new HttpPost(url); httpPost.addHeader("Content-Type", "application/x-gzip"); httpPost.addHeader("Content-Encoding", "gzip"); httpPost.setEntity(new ByteArrayEntity(dataEncrypted)); try { final HttpResponse response = httpClient.execute(httpPost); finalint statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { // do things } else { // handle failure } } catch (Exception e) { Log.w(TAG, "Error occured during data sending, abort reason: " + e.getMessage()); // handle failure }
In iOS 6, Apple provides ADID,
we could use it to identify people, but app user can switch it off if
they don't want to be tracked.
I use OpenUDID
and ADID to identify app user, what OpenUDID does is generating a UUID
and caching it for later look up.
In Android, it has IMEI, ANDROID_ID, and mac address, none of them is
reliable, so I hash those three with device model use SHA to
identify the app user.