埋点统计自定义上报

#import 
#import "SingtonDefine.h"

typedef void (^SuccessResponseHandler)(id responseData);
typedef void (^FailureResponseHandler)(NSError *error);

typedef enum {
    
    /**启动APP */
    ReportTypeStart = 1,
    /**关闭APP */
    ReportTypeClose = 2,
    /**APP从后台切换到前台 */
    ReportTypeToForeground = 3,
    /**APP从前台切换到后台 */
    ReportTypeEnterBackground = 4,
    /**打开页面(新创建或者从后台切换到前台 */
    ReportTypeOpenPage = 5,
    /**关闭页面(释放或者从前台切换到后台) */
    ReportTypeClosePage = 6,
    /**页面从后台切换到前台 */
    ReportTypePageForeground = 7,
    /**页面从前台切换到后台 */
    ReportTypePageBackground = 8,
    /**页面从前台切换到后台 */
    ReportTypeOpenNewPage = 9,
    /**关闭页面(释放) */
    ReportTypeClosePageRelease = 10,
    /**用户触发(点击/触摸) */
    ReportTypeUserTouchOrClick = 11,
    /**系统触发 */
    ReportTypeSystemTrigger = 12
    
} ReportType;

@interface ReportEngine : NSObject

/**开始 */
- (void)start;
/**结束 */
- (void)stop;

/**创建单例 */
SHARED_INTERFACE(ReportEngine)

/**捕获程序异常 */
void UncaughtExceptionHandler(NSException *exception);

/**获取用户基本信息 */
- (NSString *)getUserBaseInformation;

/**上传错误日志 */
- (void)setupTrack;

/**上传数据 */
#pragma mark - Post
- (void)post:(NSString *)uri params:(NSDictionary *)params success:(SuccessResponseHandler)successHandler failure:(FailureResponseHandler)failureHandler;

/**程序切前台,后台 用户登录 页面载入载出 上报*/
- (void)reportActivePageID:(NSString *)pageId Status:(ReportType)status;

/**app启动 */

/**app关闭 */

/**TEST */
- (void)test;

@end

#import "ReportEngine.h"
#import 
#include 
#include 
#import "AFNetworking.h"
#import "ServerHostDefines.h"

#define XcodeAppVersion        [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]
#define kTimeoutTimeinterval   10.0

#define kPageLogRequestServer  RequestURL([EnvironmentManager sharedInstance].environmentModel.ReportRequestServer, @"/monitor/collectPageLog")
#define kErrorLogRequestServer RequestURL([EnvironmentManager sharedInstance].environmentModel.ReportRequestServer, @"/monitor/collectPageLog")

@interface ReportEngine()

/**用户ID 如果没有获取到用户ID则为0 */
@property (nonatomic, strong) NSString *userID;
/**token 缺省:空字符串*/
@property (nonatomic, copy) NSString *token;
/**城市ID 缺省:0*/
@property (nonatomic, assign) NSInteger cityID;
/**经纬度 格式为:经度,纬度(中间用逗号分隔)*/
@property (nonatomic, copy) NSString *accuracyandLatitude;
/**应用ID 用户APPiOS版:200*/
@property (nonatomic, assign) NSInteger applicationID;
/**应用版本Code APP项目编译打包时配置的版本Code*/
@property (nonatomic, assign) NSInteger versionID;
/**渠道号 缺省:0 APP打包时配置的渠道号数值*/
@property (nonatomic, assign) NSInteger channelID;
/**最近升级时间 格式为:yyyyMMdd*/
@property (nonatomic, assign) NSInteger updateRecentlyTime;
/**手机品牌 调用系统API获取的手机品牌参数*/
@property (nonatomic, copy) NSString  *mobileBrand;
/**手机型号 调用系统API获取的手机型号参数*/
@property (nonatomic, copy) NSString *mobileModel;
/**操作系统 iOS:2*/
@property (nonatomic, assign) NSInteger systermModel;
/**系统版本Code 调用系统API获取的系统版本整数值*/
@property (nonatomic, assign) NSInteger systermVersionCode;
/**DeviceID 设备的唯一ID*/
@property (nonatomic, copy) NSString *deviceID;
/**调用系统API获取的网络接入点参数 */
@property (nonatomic, copy) NSString *mobileAPN;

@end

@implementation ReportEngine

SHARED_IMPLEMENTATION(ReportEngine)

/**开始 */
- (void)start {
    
    @synchronized (self)
    {
        
        [self runMonitoring];
        // [self threadForMonitoring];
        // [self report];
    }
}

/**结束 */
- (void)stop {
    
//    [[NSUserDefaults standardUserDefaults] setObject:self.reportURLArray forKey:kUserDefaultsReport];
//    
//#ifdef DEBUG
//    [self saveReportLog];
//#endif
}

/**获取日志崩溃信息 */
void UncaughtExceptionHandler(NSException *exception) {
    /**
     *  获取异常崩溃信息
     */
    NSArray *callStack = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    NSString *content = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[callStack componentsJoinedByString:@"\n"]];
    
    // NSLog(@"--------%@---------",content);
    /**
     *  把异常崩溃信息写入文件
     */
    NSUserDefaults *errLog = [NSUserDefaults standardUserDefaults];
    
    // 首先判断之前是否有内容 如果有则插入
    NSString *errLogStr = [errLog objectForKey:@"ErrorLog"];

    if ([errLogStr isEqualToString:@""] || errLogStr == nil) {
        [errLog setObject:content forKey:@"ECEJErrorLog"];
        
    } else {
        
        NSMutableString *logStr = [NSMutableString stringWithString:errLogStr];
        [logStr appendString:content];
        [errLog setObject:logStr forKey:@"ECEJErrorLog"];
    }
}

- (void)runMonitoring {
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        
        CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
        
        // 和模式相关的源才会被监视并允许他们传递事件消息,源有 输入源 定时源
        CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
        
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:90.0
                                                          target:self
                                                        selector:@selector(report)
                                                        userInfo:nil
                                                         repeats:YES];
        
        CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
        
        // 输入源和runloop模式关联
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), (__bridge CFRunLoopTimerRef)timer, kCFRunLoopDefaultMode);
        
        [timer fire];
        
        BOOL runAlways = YES;
        while (runAlways)
        {
            @autoreleasepool
            {
                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
            }
        }
        
        [timer invalidate];
        CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), (__bridge CFRunLoopTimerRef)timer, kCFRunLoopDefaultMode);
        CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
        CFRelease(source);
    });
}

#pragma mark - Runloop
- (NSThread *)threadForMonitoring
{
    static NSThread* s_thread;
    
    if (!s_thread)
    {
        @synchronized (self)
        {
            if (!s_thread)
            {
                s_thread = [[NSThread alloc] initWithTarget:self selector:@selector(runMonitoring) object:nil];
                [s_thread setName:@"LTReportEngine"];
                [s_thread start];
            }
        }
    }
    return s_thread;
}

#pragma mark - Post
- (void)post:(NSString *)uri params:(NSDictionary *)params success:(SuccessResponseHandler)successHandler failure:(FailureResponseHandler)failureHandler
{
    if (!uri)
    {
        NSLog(@"Error, requestWithCmd cmd is nil");
        return;
    }
    
    // 创建管理类
    AFHTTPSessionManager *manager = [self sharedHTTPSessionManager];
    
    // 设置请求体的Header 头部
    [manager.requestSerializer setValue:[self getUserBaseInformation] forHTTPHeaderField:@"baseMsg"];
    
    // 利用方法请求数据
    // NSLog(@"Request URL:\n%@\n\n%@", uri, [params description]);
    [manager POST:uri parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (successHandler)
        {
            [self handleRequest:task resesponseData:responseObject forSuccessHandler:successHandler];
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        if (error && failureHandler)
        {
            failureHandler(error);
            return;
        }
    }];
}

#pragma mark - Response Handler
- (void)handleRequest:(NSURLSessionDataTask *)task resesponseData:(id)responseData forSuccessHandler:(SuccessResponseHandler)successHandler
{
    // NSLog(@"response for %@", task.originalRequest.URL.absoluteURL);
    
    if (successHandler)
    {
        successHandler(responseData);
    }
}

#pragma mark - Config
- (AFHTTPSessionManager *)sharedHTTPSessionManager
{
    // 创建管理类
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    
    // 设置超时时间
    [manager.requestSerializer willChangeValueForKey:@"timeoutInterval"];
    manager.requestSerializer.timeoutInterval = kTimeoutTimeinterval;
    [manager.requestSerializer didChangeValueForKey:@"timeoutInterval"];
    
    // 设置二进制数据,数据格式默认json
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    
    return manager;
}

- (void)report {
    
    // 读取文件 每隔90秒上报一次 并且将上次数据清除
    NSError *error;
    NSArray *paths  = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
    NSString *homePath = [paths objectAtIndex:0];
    
    NSString *filePath = [homePath stringByAppendingPathComponent:@"logMsg.txt"];

    NSString *contentString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
    
    if (contentString == nil) {
        return;
    } else {
        
        [self post:kPageLogRequestServer params:@{@"logMsg":contentString} success:^(id responseData) {
            
                [self removeFile];
                NSLog(@"YES");
        } failure:^(NSError *error) {
        
            NSLog(@"NO");
        }];
    }
    
}

/**删除本地文件 */
- (void)removeFile {
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    
    NSFileManager *fileMgr = [NSFileManager defaultManager];
    NSString *MapLayerDataPath = [documentsDirectory stringByAppendingPathComponent:@"logMsg.txt"];
    BOOL bRet = [fileMgr fileExistsAtPath:MapLayerDataPath];
    if (bRet) {
        // 删除
        NSError *err;
        [fileMgr removeItemAtPath:MapLayerDataPath error:&err];
    }
}

/**程序切前台,后台 用户登录 页面载入载出 上报*/
- (void)reportActivePageID:(NSString *)pageId Status:(ReportType)status {
    
    // 用户行为数据格式如下:
    // 时间戳/PageID或事件ID/事件类型
    
    /**
     *  时间戳: 发生事件时的时间,时间戳格式,单位毫秒 比如:1469529583356
        PageID: 在APP里为每个页面定义一个Page ID,可以是英文、中文,也可以是类名 比如:home、个人中心
        事件ID: 为事件命名一个名称,比如:定位城市、点击首页广告页
        事件类型:
        常量值:
        1:启动APP
        2:关闭APP
        3:APP从后台切换到前台
        4:APP从前台切换到后台
        5:打开页面(新创建或者从后台切换到前台)
        6:关闭页面(释放或者从前台切换到后台)
        7:页面从后台切换到前台
        8:页面从前台切换到后台
        9:打开页面(新创建)
        10:关闭页面(释放)
        11:用户触发(点击/触摸)
        12:系统触发
     */
    
    //时间戳  格式化时间
    NSDate * date = [NSDate date];
    NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateFormat = @"yyyyMMddhhmmssSSS";
    NSString * dateStr = [dateFormatter stringFromDate:date];
    
    NSString *writeToFileString = [NSString stringWithFormat:@"%@/%@/%u",dateStr,pageId,status];
    /**将事件以条目形式写入文件 */
    [self writefile:writeToFileString];
}

/**获取用户基础数据 */
- (NSString *)getUserBaseInformation {
    
    NSMutableString *getUserBaseStr = [[NSMutableString alloc] init];
    // 用户ID
    self.userID = PublicData.sharedInstance.loginUser.phone ? : @"0";
    [getUserBaseStr appendFormat:@"%@/",self.userID];
    // token
    self.token = [PublicData sharedInstance].loginUser.token;
    [getUserBaseStr appendFormat:@"%@/",self.self.token];
    // 城市ID
    self.cityID = PublicData.sharedInstance.cityModel.cityId.integerValue?:0;
    [getUserBaseStr appendFormat:@"%ld/",(long)self.self.cityID];
    // 经纬度
    self.accuracyandLatitude = [NSString stringWithFormat:@"%@,%@",PublicData.sharedInstance.cityModel.latitude,PublicData.sharedInstance.cityModel.longitude];
    [getUserBaseStr appendFormat:@"%@/",self.self.accuracyandLatitude];
    // 应用ID
    self.applicationID = 200;
    [getUserBaseStr appendFormat:@"%ld/",(long)self.applicationID];
    // 应用版本Code
    self.versionID = ((NSString *)XcodeAppVersion).integerValue;
    [getUserBaseStr appendFormat:@"%ld/",(long)self.versionID];
    // 渠道号
    self.channelID = 0;
    [getUserBaseStr appendFormat:@"%ld/",(long)self.channelID];
    // 最近升级时间
    self.updateRecentlyTime = 0;
    [getUserBaseStr appendFormat:@"%ld/",(long)self.updateRecentlyTime
     ];
    // 手机品牌 手机厂商
    self.mobileBrand = @"";
    [getUserBaseStr appendFormat:@"%@/",self.mobileBrand];
    // 手机型号
    self.mobileModel =  [[UIDevice currentDevice] model];
    [getUserBaseStr appendFormat:@"%@/",self.mobileModel];
    // 操作系统
    self.systermModel = 2;
    [getUserBaseStr appendFormat:@"%ld/",(long)self.systermModel];
    // 系统版本
    self.systermVersionCode = [[UIDevice currentDevice] systemVersion].integerValue;
    [getUserBaseStr appendFormat:@"%ld/",(long)self.systermVersionCode];
    self.deviceID = [[UIDevice currentDevice] identifierForVendor].stringObject;
    [getUserBaseStr appendFormat:@"%@/",self.deviceID];
    self.mobileAPN = [self getNetWorkStates];// [self deviceIPAdress];
    [getUserBaseStr appendFormat:@"%@/",self.mobileAPN];
    
    return getUserBaseStr;
}

/**上传错误日志 */
- (void)setupECEJTrack {
    
    NSUserDefaults *errLog = [NSUserDefaults standardUserDefaults];
    NSString *errLogStr = [errLog objectForKey:@"errorLog"];
    
    if ([errLogStr isEqualToString:@""] || errLogStr == nil) {
        return;
    } else {
    
        [self post:kErrorLogRequestServer params:@{@"logMsg":errLogStr?:@""} success:^(id responseData) {
       
            NSLog(@"YES");
            [errLog setObject:@"" forKey:@"ECEJErrorLog"];
        } failure:^(NSError *error) {
        
            NSLog(@"NO");
        }];
    }
}

/**将用户触发事件插入文件 */
- (void)writefile:(NSString *)string {
    
    NSArray *paths  = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
    NSString *homePath = [paths objectAtIndex:0];
    
    NSString *filePath = [homePath stringByAppendingPathComponent:@"logMsg.txt"];
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    // 如果不存在
    if(![fileManager fileExistsAtPath:filePath]) {
        
     [@"" writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
        
    }
    
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
    
    // 将节点跳到文件的末尾
    [fileHandle seekToEndOfFile];
    // NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    // [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    //  NSString *datestr = [dateFormatter stringFromDate:[NSDate date]];
    
    NSString *str = [NSString stringWithFormat:@"%@\n",string];
    
    NSData* stringData  = [str dataUsingEncoding:NSUTF8StringEncoding];
    
    // 追加写入数据
    [fileHandle writeData:stringData];
    
    [fileHandle closeFile];
}


/**获取IP地址 */
- (NSString *)deviceIPAdress {
    
    NSString *address = @"an error occurred when obtaining ip address";
    struct ifaddrs *interfaces = NULL;
    struct ifaddrs *temp_addr = NULL;
    int success = 0;
    
    success = getifaddrs(&interfaces);
    
    if (success == 0) { // 0 表示获取成功
        
        temp_addr = interfaces;
        while (temp_addr != NULL) {
            if( temp_addr->ifa_addr->sa_family == AF_INET) {
                // Check if interface is en0 which is the wifi connection on the iPhone
                if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
                    // Get NSString from C String
                    address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
                }
            }
            
            temp_addr = temp_addr->ifa_next;
        }
    }
    freeifaddrs(interfaces);
    // NSLog(@"手机的IP是:%@", address);
    return address;
}

/**获取当前网络设备连接网络状态 */
- (NSString *)getNetWorkStates{
   
    UIApplication *app = [UIApplication sharedApplication];
    NSArray *children = [[[app valueForKeyPath:@"statusBar"]valueForKeyPath:@"foregroundView"]subviews];
    NSString *state = [[NSString alloc]init];
    int netType = 0;
    //获取到网络返回码
    for (id child in children) {
        if ([child isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {
            //获取到状态栏
            netType = [[child valueForKeyPath:@"dataNetworkType"]intValue];
            
            switch (netType) {
                case 0:
                    state = @"无网络";
                    //无网模式
                    break;
                case 1:
                    state =  @"2G";
                    break;
                case 2:
                    state =  @"3G";
                    break;
                case 3:
                    state =   @"4G";
                    break;
                case 5:
                {
                    state =  @"wifi";
                    break;
                default:
                    break;
                }
            }
        }
        //根据状态选择
    }
    return state;
}

/**TEST */
- (void)test {
    
//    // 日志保存 设置成功 每一次启动将上次的崩溃日志发送
//    NSUserDefaults *errLog = [NSUserDefaults standardUserDefaults];
//    NSLog(@"%@", [errLog objectForKey:@"errorLog"]);
    
}

@end


你可能感兴趣的:(iOS,埋点,统计,上报,app,自定义事件)