forked from organicmaps/organicmaps
[alohalytics][ios] Measure total time spent in the app, get install/update/build/first launch date.
This commit is contained in:
parent
0cd24f656e
commit
9145157339
4 changed files with 120 additions and 23 deletions
|
@ -44,6 +44,12 @@
|
|||
// Used for example purposes only to upload statistics (unpredictable) in background, when system wakes app up.
|
||||
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
|
||||
|
||||
NSLog(@"isFirstSession: %d", [Alohalytics isFirstSession]);
|
||||
NSLog(@"firstLaunchDate: %@", [Alohalytics firstLaunchDate]);
|
||||
NSLog(@"installDate: %@", [Alohalytics installDate]);
|
||||
NSLog(@"updateDate: %@", [Alohalytics updateDate]);
|
||||
NSLog(@"buildDate: %@", [Alohalytics buildDate]);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,11 +23,13 @@
|
|||
*******************************************************************************/
|
||||
|
||||
// Convenience header to use from pure Objective-C project.
|
||||
// TODO(AlexZ): Refactor it to use instance and it's methods instead of static functions.
|
||||
|
||||
#ifndef ALOHALYTICS_OBJC_H
|
||||
#define ALOHALYTICS_OBJC_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Foundation/NSDate.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
@interface Alohalytics : NSObject
|
||||
|
@ -35,9 +37,6 @@
|
|||
// Should be called in application:didFinishLaunchingWithOptions: or in application:willFinishLaunchingWithOptions:
|
||||
// Final serverUrl is modified to $(serverUrl)/[ios|mac]/your.bundle.id/app.version
|
||||
+ (void)setup:(NSString *)serverUrl withLaunchOptions:(NSDictionary *)options;
|
||||
// Alternative to the previous setup method if you integrated Alohalytics after initial release
|
||||
// and don't want to count app upgrades as new installs (and definitely know that it's an already existing user).
|
||||
+ (void)setup:(NSString *)serverUrl andFirstLaunch:(BOOL)isFirstLaunch withLaunchOptions:(NSDictionary *)options;
|
||||
+ (void)forceUpload;
|
||||
+ (void)logEvent:(NSString *)event;
|
||||
+ (void)logEvent:(NSString *)event atLocation:(CLLocation *)location;
|
||||
|
@ -48,6 +47,21 @@
|
|||
+ (void)logEvent:(NSString *)event withKeyValueArray:(NSArray *)array atLocation:(CLLocation *)location;
|
||||
+ (void)logEvent:(NSString *)event withDictionary:(NSDictionary *)dictionary;
|
||||
+ (void)logEvent:(NSString *)event withDictionary:(NSDictionary *)dictionary atLocation:(CLLocation *)location;
|
||||
// Returns YES if it is a first session, before app goes into background.
|
||||
+ (BOOL)isFirstSession;
|
||||
// Returns summary time of all active user sessions up to now.
|
||||
+ (NSInteger)totalSecondsSpentInTheApp;
|
||||
|
||||
// Returns the date when app was launched for the first time (usually the same as install date).
|
||||
+ (NSDate *)firstLaunchDate;
|
||||
// When app was installed (it's Documents folder creation date).
|
||||
// Note: firstLaunchDate is usually later than installDate.
|
||||
+ (NSDate *)installDate;
|
||||
// When app was updated (~== installDate for the first installation, it's Resources folder creation date).
|
||||
+ (NSDate *)updateDate;
|
||||
// When the binary was built.
|
||||
// Hint: if buildDate > installDate then this is not a new app install, but an existing old user.
|
||||
+ (NSDate *)buildDate;
|
||||
@end
|
||||
|
||||
#endif // #ifndef ALOHALYTICS_OBJC_H
|
||||
|
|
|
@ -123,11 +123,9 @@ static Location ExtractLocation(CLLocation * l) {
|
|||
return extracted;
|
||||
}
|
||||
|
||||
// Returns string representing uint64_t timestamp of given file or directory (modification date in millis from 1970).
|
||||
static std::string PathTimestampMillis(NSString * path) {
|
||||
NSDictionary * attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
|
||||
if (attributes) {
|
||||
NSDate * date = [attributes objectForKey:NSFileModificationDate];
|
||||
// Internal helper.
|
||||
static std::string NSDateToMillisFrom1970(NSDate * date) {
|
||||
if (date) {
|
||||
return std::to_string(static_cast<uint64_t>([date timeIntervalSince1970] * 1000.));
|
||||
}
|
||||
return std::string("0");
|
||||
|
@ -328,6 +326,14 @@ bool IsConnectionActive() {
|
|||
|
||||
// Keys for NSUserDefaults.
|
||||
static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion";
|
||||
static NSString * const kFirstLaunchDateKey = @"AlohalyticsFirstLaunchDate";
|
||||
static NSString * const kTotalSecondsInTheApp = @"AlohalyticsTotalSecondsInTheApp";
|
||||
|
||||
// Used to calculate session length and total time spent in the app.
|
||||
// setup should be called to activate counting.
|
||||
static NSDate * gSessionStartTime = nil;
|
||||
static BOOL gIsFirstSession = NO;
|
||||
|
||||
@implementation Alohalytics
|
||||
|
||||
+ (void)setDebugMode:(BOOL)enable {
|
||||
|
@ -335,10 +341,6 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion";
|
|||
}
|
||||
|
||||
+ (void)setup:(NSString *)serverUrl withLaunchOptions:(NSDictionary *)options {
|
||||
[Alohalytics setup:serverUrl andFirstLaunch:YES withLaunchOptions:options];
|
||||
}
|
||||
|
||||
+ (void)setup:(NSString *)serverUrl andFirstLaunch:(BOOL)isFirstLaunch withLaunchOptions:(NSDictionary *)options {
|
||||
const NSBundle * bundle = [NSBundle mainBundle];
|
||||
NSString * bundleIdentifier = [bundle bundleIdentifier];
|
||||
NSString * version = [[bundle infoDictionary] objectForKey:@"CFBundleShortVersionString"];
|
||||
|
@ -372,21 +374,26 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion";
|
|||
NSUserDefaults * userDataBase = [NSUserDefaults standardUserDefaults];
|
||||
NSString * installedVersion = [userDataBase objectForKey:kInstalledVersionKey];
|
||||
BOOL shouldSendUpdatedSystemInformation = NO;
|
||||
if (installationId.second && isFirstLaunch && installedVersion == nil) {
|
||||
// Documents folder modification time can be interpreted as a "first app launch time" or an approx. "app install time".
|
||||
// App bundle modification time can be interpreted as an "app update time".
|
||||
instance.LogEvent("$install", {{"CFBundleShortVersionString", [version UTF8String]},
|
||||
{"documentsTimestampMillis", PathTimestampMillis([NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject])},
|
||||
{"bundleTimestampMillis", PathTimestampMillis([bundle executablePath])}});
|
||||
// Do not generate $install event for old users who did not have Alohalytics installed but already used the app.
|
||||
const BOOL appWasNeverInstalledAndLaunchedBefore = (NSOrderedAscending == [[Alohalytics buildDate] compare:[Alohalytics installDate]]);
|
||||
if (installationId.second && appWasNeverInstalledAndLaunchedBefore && installedVersion == nil) {
|
||||
gIsFirstSession = YES;
|
||||
instance.LogEvent("$install", [Alohalytics bundleInformation:version]);
|
||||
[userDataBase setValue:version forKey:kInstalledVersionKey];
|
||||
// Also store first launch date for future use.
|
||||
if (nil == [userDataBase objectForKey:kFirstLaunchDateKey]) {
|
||||
[userDataBase setObject:[NSDate date] forKey:kFirstLaunchDateKey];
|
||||
}
|
||||
[userDataBase synchronize];
|
||||
shouldSendUpdatedSystemInformation = YES;
|
||||
} else {
|
||||
if (installedVersion == nil || ![installedVersion isEqualToString:version]) {
|
||||
instance.LogEvent("$update", {{"CFBundleShortVersionString", [version UTF8String]},
|
||||
{"documentsTimestampMillis", PathTimestampMillis([NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject])},
|
||||
{"bundleTimestampMillis", PathTimestampMillis([bundle executablePath])}});
|
||||
instance.LogEvent("$update", [Alohalytics bundleInformation:version]);
|
||||
[userDataBase setValue:version forKey:kInstalledVersionKey];
|
||||
// Also store first launch date for future use, if Alohalytics was integrated only in this update.
|
||||
if (nil == [userDataBase objectForKey:kFirstLaunchDateKey]) {
|
||||
[userDataBase setObject:[NSDate date] forKey:kFirstLaunchDateKey];
|
||||
}
|
||||
[userDataBase synchronize];
|
||||
shouldSendUpdatedSystemInformation = YES;
|
||||
}
|
||||
|
@ -409,6 +416,14 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion";
|
|||
#endif // TARGET_OS_IPHONE
|
||||
}
|
||||
|
||||
+(alohalytics::TStringMap)bundleInformation:(NSString *)version {
|
||||
return {{"CFBundleShortVersionString", [version UTF8String]},
|
||||
{"installTimestampMillis", NSDateToMillisFrom1970([Alohalytics installDate])},
|
||||
{"updateTimestampMillis", NSDateToMillisFrom1970([Alohalytics updateDate])},
|
||||
{"buildTimestampMillis", NSDateToMillisFrom1970([Alohalytics buildDate])},
|
||||
};
|
||||
}
|
||||
|
||||
+ (void)forceUpload {
|
||||
Stats::Instance().Upload();
|
||||
}
|
||||
|
@ -448,11 +463,24 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion";
|
|||
#pragma mark App lifecycle notifications used to calculate basic metrics.
|
||||
#if (TARGET_OS_IPHONE > 0)
|
||||
+ (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
gSessionStartTime = [NSDate date];
|
||||
Stats::Instance().LogEvent("$applicationDidBecomeActive");
|
||||
}
|
||||
|
||||
+ (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
Stats::Instance().LogEvent("$applicationWillResignActive");
|
||||
// Calculate session length.
|
||||
NSInteger seconds = static_cast<NSInteger>(-gSessionStartTime.timeIntervalSinceNow);
|
||||
// nil it to filter time when the app is in the background, but totalSecondsSpentInTheApp is called.
|
||||
gSessionStartTime = nil;
|
||||
Stats & instance = Stats::Instance();
|
||||
instance.LogEvent("$applicationWillResignActive", std::to_string(seconds));
|
||||
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
|
||||
seconds += [defaults integerForKey:kTotalSecondsInTheApp];
|
||||
[defaults setInteger:seconds forKey:kTotalSecondsInTheApp];
|
||||
[defaults synchronize];
|
||||
if (instance.DebugMode()) {
|
||||
ALOG("Total seconds spent in the app:", seconds);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)applicationWillEnterForeground:(NSNotificationCenter *)notification {
|
||||
|
@ -479,10 +507,59 @@ static NSString * const kInstalledVersionKey = @"AlohalyticsInstalledVersion";
|
|||
ALOG("Skipped statistics uploading as connection is not active.");
|
||||
}
|
||||
}
|
||||
if (gIsFirstSession) {
|
||||
gIsFirstSession = NO;
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)applicationWillTerminate:(NSNotification *)notification {
|
||||
Stats::Instance().LogEvent("$applicationWillTerminate");
|
||||
}
|
||||
#endif // TARGET_OS_IPHONE
|
||||
|
||||
#pragma mark Utility methods
|
||||
|
||||
+ (BOOL)isFirstSession {
|
||||
return gIsFirstSession;
|
||||
}
|
||||
|
||||
+ (NSDate *)firstLaunchDate {
|
||||
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSDate * date = [defaults objectForKey:kFirstLaunchDateKey];
|
||||
if (!date) {
|
||||
// Non-standard situation: this method is called before calling setup. Return current date.
|
||||
date = [NSDate date];
|
||||
[defaults setObject:date forKey:kFirstLaunchDateKey];
|
||||
[defaults synchronize];
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
+ (NSInteger)totalSecondsSpentInTheApp {
|
||||
NSInteger seconds = [[NSUserDefaults standardUserDefaults] integerForKey:kTotalSecondsInTheApp];
|
||||
// Take into an account currently active session.
|
||||
if (gSessionStartTime) {
|
||||
seconds += static_cast<NSInteger>(-gSessionStartTime.timeIntervalSinceNow);
|
||||
}
|
||||
return seconds;
|
||||
}
|
||||
|
||||
// Internal helper, returns nil for invalid paths.
|
||||
+ (NSDate *)fileCreationDate:(NSString *)fullPath {
|
||||
NSDictionary * attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:nil];
|
||||
return attributes ? [attributes objectForKey:NSFileCreationDate] : nil;
|
||||
}
|
||||
|
||||
+ (NSDate *)installDate {
|
||||
return [Alohalytics fileCreationDate:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]];
|
||||
}
|
||||
|
||||
+ (NSDate *)updateDate {
|
||||
return [Alohalytics fileCreationDate:[[NSBundle mainBundle] resourcePath]];
|
||||
}
|
||||
|
||||
+ (NSDate *)buildDate {
|
||||
return [Alohalytics fileCreationDate:[[NSBundle mainBundle] executablePath]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -167,7 +167,7 @@ void InitLocalizedStrings()
|
|||
#ifndef OMIM_PRODUCTION
|
||||
[Alohalytics setDebugMode:YES];
|
||||
#endif
|
||||
[Alohalytics setup:@"http://localhost:8080" andFirstLaunch:[MapsAppDelegate isFirstAppLaunch] withLaunchOptions:launchOptions];
|
||||
[Alohalytics setup:@"http://localhost:8080" withLaunchOptions:launchOptions];
|
||||
|
||||
NSURL *url = launchOptions[UIApplicationLaunchOptionsURLKey];
|
||||
if (url != nil)
|
||||
|
|
Loading…
Add table
Reference in a new issue