#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